How to adjust text font size to fit textview

后端 未结 22 1724
無奈伤痛
無奈伤痛 2020-11-22 05:15

Is there any way in android to adjust the textsize in a textview to fit the space it occupies?

E.g. I\'m using a TableLayout and adding several Te

相关标签:
22条回答
  • 2020-11-22 05:50

    This is speedplane's FontFitTextView, but it only decreases font size if needed to make the text fit, and keeps its font size otherwise. It does not increase the font size to fit height.

    public class FontFitTextView extends TextView {
    
        // Attributes
        private Paint mTestPaint;
        private float defaultTextSize;
    
        public FontFitTextView(Context context) {
            super(context);
            initialize();
        }
    
        public FontFitTextView(Context context, AttributeSet attrs) {
            super(context, attrs);
            initialize();
        }
    
        private void initialize() {
            mTestPaint = new Paint();
            mTestPaint.set(this.getPaint());
            defaultTextSize = getTextSize();
        }
    
        /* Re size the font so the specified text fits in the text box
         * assuming the text box is the specified width.
         */
        private void refitText(String text, int textWidth) {
    
            if (textWidth <= 0 || text.isEmpty())
                return;
    
            int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
    
            // this is most likely a non-relevant call
            if( targetWidth<=2 )
                return;
    
            // text already fits with the xml-defined font size?
            mTestPaint.set(this.getPaint());
            mTestPaint.setTextSize(defaultTextSize);
            if(mTestPaint.measureText(text) <= targetWidth) {
                this.setTextSize(TypedValue.COMPLEX_UNIT_PX, defaultTextSize);
                return;
            }
    
            // adjust text size using binary search for efficiency
            float hi = defaultTextSize;
            float lo = 2;
            final float threshold = 0.5f; // How close we have to be
            while (hi - lo > threshold) {
                float size = (hi + lo) / 2;
                mTestPaint.setTextSize(size);
                if(mTestPaint.measureText(text) >= targetWidth ) 
                    hi = size; // too big
                else 
                    lo = size; // too small
    
            }
    
            // Use lo so that we undershoot rather than overshoot
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
            int height = getMeasuredHeight();
            refitText(this.getText().toString(), parentWidth);
            this.setMeasuredDimension(parentWidth, height);
        }
    
        @Override
        protected void onTextChanged(final CharSequence text, final int start,
                final int before, final int after) {
            refitText(text.toString(), this.getWidth());
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh) {
                refitText(this.getText().toString(), w);
            }
        }
    
    }
    

    Here is an example how it could be used in xml:

    <com.your.package.activity.widget.FontFitTextView
        android:id="@+id/my_id"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:text="My Text"
        android:textSize="60sp" />
    

    This would keep the font size to 60sp as long as the text fits in width. If the text is longer, it will decrease font size. In this case, the TextViews height will also change because of height=wrap_content.

    If you find any bugs, feel free to edit.

    0 讨论(0)
  • 2020-11-22 05:50

    Slight modification to onMeasure:

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        refitText(this.getText().toString(), parentWidth);
        this.setMeasuredDimension(parentWidth, parentHeight);
    }
    

    And binary search on refitText:

    private void refitText(String text, int textWidth) 
    { 
        if (textWidth > 0) 
        {
            int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();         
            int trySize = (int)maxTextSize;
            int increment = ~( trySize - (int)minTextSize ) / 2;
    
            testPaint.setTextSize(trySize);
            while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) 
            {
                trySize += increment;
                increment = ( increment == 0 ) ? -1 : ~increment / 2;
                if (trySize <= minTextSize) 
                {
                    trySize = (int)minTextSize;
                    break;
                }
                testPaint.setTextSize(trySize);
            }
    
            this.setTextSize( TypedValue.COMPLEX_UNIT_PX, trySize);
        }
    }
    
    0 讨论(0)
  • 2020-11-22 05:50

    Use app:autoSizeTextType="uniform" for backward compatibility because android:autoSizeTextType="uniform" only work in API Level 26 and higher.

    0 讨论(0)
  • 2020-11-22 05:51

    I had this pain in my projects for soooo long until I found this library:

    compile 'me.grantland:autofittextview:0.2.+'
    

    You just need to add the xml by your needs and it's done. For example:

    <me.grantland.widget.AutofitTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="true"
    android:maxLines="2"
    android:textSize="40sp"
    autofit:minTextSize="16sp"
    />
    
    0 讨论(0)
  • 2020-11-22 05:51

    Extend TextView and override onDraw with the code below. It will keep text aspect ratio but size it to fill the space. You could easily modify code to stretch if necessary.

      @Override
      protected void onDraw(@NonNull Canvas canvas) {
        TextPaint textPaint = getPaint();
        textPaint.setColor(getCurrentTextColor());
        textPaint.setTextAlign(Paint.Align.CENTER);
        textPaint.drawableState = getDrawableState();
    
        String text = getText().toString();
        float desiredWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight() - 2;
        float desiredHeight = getMeasuredHeight() - getPaddingTop() - getPaddingBottom() - 2;
        float textSize = textPaint.getTextSize();
    
        for (int i = 0; i < 10; i++) {
          textPaint.getTextBounds(text, 0, text.length(), rect);
          float width = rect.width();
          float height = rect.height();
    
          float deltaWidth = width - desiredWidth;
          float deltaHeight = height - desiredHeight;
    
          boolean fitsWidth = deltaWidth <= 0;
          boolean fitsHeight = deltaHeight <= 0;
    
          if ((fitsWidth && Math.abs(deltaHeight) < 1.0)
              || (fitsHeight && Math.abs(deltaWidth) < 1.0)) {
            // close enough
            break;
          }
    
          float adjustX = desiredWidth / width;
          float adjustY = desiredHeight / height;
    
          textSize = textSize * (adjustY < adjustX ? adjustY : adjustX);
    
          // adjust text size
          textPaint.setTextSize(textSize);
        }
        float x = desiredWidth / 2f;
        float y = desiredHeight / 2f - rect.top - rect.height() / 2f;
        canvas.drawText(text, x, y, textPaint);
      }
    
    0 讨论(0)
  • 2020-11-22 05:54

    Thanks a lot to https://stackoverflow.com/users/234270/speedplane. Great answer!

    Here is an improved version of his response that also take care of height and comes with a maxFontSize attribute to limit font size (was useful in my case, so I wanted to share it) :

    package com.<your_package>;
    
    import android.content.Context;
    import android.content.res.TypedArray;
    import android.graphics.Paint;
    import android.graphics.Rect;
    import android.util.AttributeSet;
    import android.util.TypedValue;
    import android.widget.TextView;
    
    
    public class FontFitTextView extends TextView
    {
    
        private Paint mTestPaint;
        private float maxFontSize;
        private static final float MAX_FONT_SIZE_DEFAULT_VALUE = 20f;
    
        public FontFitTextView(Context context)
        {
            super(context);
            initialise(context, null);
        }
    
        public FontFitTextView(Context context, AttributeSet attributeSet)
        {
            super(context, attributeSet);
            initialise(context, attributeSet);
        }
    
        public FontFitTextView(Context context, AttributeSet attributeSet, int defStyle)
        {
            super(context, attributeSet, defStyle);
            initialise(context, attributeSet);
        }
    
        private void initialise(Context context, AttributeSet attributeSet)
        {
            if(attributeSet!=null)
            {
                TypedArray styledAttributes = context.obtainStyledAttributes(attributeSet, R.styleable.FontFitTextView);
                maxFontSize = styledAttributes.getDimension(R.styleable.FontFitTextView_maxFontSize, MAX_FONT_SIZE_DEFAULT_VALUE);
                styledAttributes.recycle();
            }
            else
            {
                maxFontSize = MAX_FONT_SIZE_DEFAULT_VALUE;
            }
    
            mTestPaint = new Paint();
            mTestPaint.set(this.getPaint());
            //max size defaults to the initially specified text size unless it is too small
        }
    
        /* Re size the font so the specified text fits in the text box
         * assuming the text box is the specified width.
         */
        private void refitText(String text, int textWidth, int textHeight)
        {
            if (textWidth <= 0)
                return;
            int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
            int targetHeight = textHeight - this.getPaddingTop() - this.getPaddingBottom();
            float hi = maxFontSize;
            float lo = 2;
    //      final float threshold = 0.5f; // How close we have to be
            final float threshold = 1f; // How close we have to be
    
            mTestPaint.set(this.getPaint());
    
            Rect bounds = new Rect();
    
            while ((hi - lo) > threshold)
            {
                float size = (hi + lo) / 2;
                mTestPaint.setTextSize(size);
    
                mTestPaint.getTextBounds(text, 0, text.length(), bounds);
    
                if (bounds.width() >= targetWidth || bounds.height() >= targetHeight)
                    hi = size; // too big
                else
                    lo = size; // too small
    
    //          if (mTestPaint.measureText(text) >= targetWidth)
    //              hi = size; // too big
    //          else
    //              lo = size; // too small
            }
            // Use lo so that we undershoot rather than overshoot
            this.setTextSize(TypedValue.COMPLEX_UNIT_PX, lo);
        }
    
        @Override
        protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
        {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
            int height = getMeasuredHeight();
            refitText(this.getText().toString(), parentWidth, height);
            this.setMeasuredDimension(parentWidth, height);
        }
    
        @Override
        protected void onTextChanged(final CharSequence text, final int start, final int before, final int after)
        {
            refitText(text.toString(), this.getWidth(), this.getHeight());
        }
    
        @Override
        protected void onSizeChanged(int w, int h, int oldw, int oldh)
        {
            if (w != oldw)
            {
                refitText(this.getText().toString(), w, h);
            }
        }
    }
    

    Corresponding /res/values/attr.xml file:

    <?xml version="1.0" encoding="utf-8"?>
    <resources>
    
        <declare-styleable name="FontFitTextView">
            <attr name="maxFontSize" format="dimension" />
        </declare-styleable>
    
    </resources>
    

    Example:

    <RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:res-auto="http://schemas.android.com/apk/res-auto"
        android:id="@+id/home_Layout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:background="@drawable/background"
        tools:ignore="ContentDescription" >
    ...
    
     <com.<your_package>.FontFitTextView
                        android:layout_width="match_parent"
                        android:layout_height="wrap_content"
                        android:singleLine="true"
                        android:text="Sample Text"
                        android:textSize="28sp"
                        res-auto:maxFontSize="35sp"/>
    
    ...
    </RelativeLayout>
    

    To use the new maxFontSize attribute, don't forget to add xmlns:res-auto="http://schemas.android.com/apk/res-auto" as show in the example.

    0 讨论(0)
提交回复
热议问题