How to adjust text font size to fit textview

AndroidFont SizeTextview

Android Problem Overview


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 TextViews to each row. Since I don't want the TextViews to wrap the text I rather see that it lowers the font size of the content.

Any ideas?

I have tried measureText, but since I don't know the size of the column it seems troublesome to use. This is the code where I want to change the font size to something that fits

TableRow row = new TableRow(this);   
for (int i=0; i < ColumnNames.length; i++) {    
    TextView textColumn = new TextView(this);      
    textColumn.setText(ColumnNames[i]);
    textColumn.setPadding(0, 0, 1, 0);
    textColumn.setTextColor(getResources().getColor(R.drawable.text_default));          
    row.addView(textColumn, new TableRow.LayoutParams()); 
} 
table.addView(row, new TableLayout.LayoutParams());  

Android Solutions


Solution 1 - Android

The solution below incorporates all of the suggestions here. It starts with what was originally posted by Dunni. It uses a binary search like gjpc's, but it is a bit more readable. It also include's gregm's bug fixes and a bug-fix of my own.

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        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) 
    { 
        if (textWidth <= 0)
        	return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be
        
        mTestPaint.set(this.getPaint());
        
        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) {
            refitText(this.getText().toString(), w);
        }
    }

    //Attributes
    private Paint mTestPaint;
}

Solution 2 - Android

I've written a class that extends TextView and does this. It just uses measureText as you suggest. Basically it has a maximum text size and minimum text size (which can be changed) and it just runs through the sizes between them in decrements of 1 until it finds the biggest one that will fit. Not particularly elegant, but I don't know of any other way.

Here is the code:

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.widget.TextView;

public class FontFitTextView extends TextView {

	public FontFitTextView(Context context) {
		super(context);
		initialise();
	}
	
	public FontFitTextView(Context context, AttributeSet attrs) {
		super(context, attrs);
		initialise();
	}
	
	private void initialise() {
		testPaint = new Paint();
        testPaint.set(this.getPaint());
        //max size defaults to the intially specified text size unless it is too small
        maxTextSize = this.getTextSize();
        if (maxTextSize < 11) {
        	maxTextSize = 20;
        }
        minTextSize = 10;
	}
	
	/* 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) {
			int availableWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
			float trySize = maxTextSize;
			
			testPaint.setTextSize(trySize);
			while ((trySize > minTextSize) && (testPaint.measureText(text) > availableWidth)) {
				trySize -= 1;
				if (trySize <= minTextSize) {
					trySize = minTextSize;
					break;
				}
				testPaint.setTextSize(trySize);
			}
			
			this.setTextSize(trySize);
		}
	}
	
	@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) {
			refitText(this.getText().toString(), w);
		}
	}
	
	//Getters and Setters
	public float getMinTextSize() {
		return minTextSize;
	}

	public void setMinTextSize(int minTextSize) {
		this.minTextSize = minTextSize;
	}
	
	public float getMaxTextSize() {
		return maxTextSize;
	}

	public void setMaxTextSize(int minTextSize) {
		this.maxTextSize = minTextSize;
	}
		
	//Attributes
	private Paint testPaint;
	private float minTextSize;
	private float maxTextSize;
	
}

Solution 3 - Android

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.

Solution 4 - Android

Here is my solution which works on emulator and phones but not very well on Eclipse layout editor. It's inspired from kilaka's code but the size of the text is not obtained from the Paint but from measuring the TextView itself calling measure(0, 0).

The Java class :

public class FontFitTextView extends TextView
{
    private static final float THRESHOLD = 0.5f;
    
    private enum Mode { Width, Height, Both, None }
    
    private int minTextSize = 1;
    private int maxTextSize = 1000;
    
    private Mode mode = Mode.None;
    private boolean inComputation;
    private int widthMeasureSpec;
    private int heightMeasureSpec;

    public FontFitTextView(Context context) {
            super(context);
    }
    
    public FontFitTextView(Context context, AttributeSet attrs) {
            this(context, attrs, 0);
    }
    
    public FontFitTextView(Context context, AttributeSet attrs, int defStyle) {
            super(context, attrs, defStyle);
            
            TypedArray tAttrs = context.obtainStyledAttributes(attrs, R.styleable.FontFitTextView, defStyle, 0);
            maxTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_maxTextSize, maxTextSize);
            minTextSize = tAttrs.getDimensionPixelSize(R.styleable.FontFitTextView_minTextSize, minTextSize);
            tAttrs.recycle();
    }
    
    private void resizeText() {
            if (getWidth() <= 0 || getHeight() <= 0)
                    return;
            if(mode == Mode.None)
                    return;
            
            final int targetWidth = getWidth();
            final int targetHeight = getHeight();
            
            inComputation = true;
            float higherSize = maxTextSize;
            float lowerSize = minTextSize;
            float textSize = getTextSize();
            while(higherSize - lowerSize > THRESHOLD) {
                    textSize = (higherSize + lowerSize) / 2;
                    if (isTooBig(textSize, targetWidth, targetHeight)) {
                            higherSize = textSize; 
                    } else {
                            lowerSize = textSize;
                    }
            }
            setTextSize(TypedValue.COMPLEX_UNIT_PX, lowerSize);
            measure(widthMeasureSpec, heightMeasureSpec);
            inComputation = false;
    }
    
    private boolean isTooBig(float textSize, int targetWidth, int targetHeight) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
            measure(0, 0);
            if(mode == Mode.Both)
                    return getMeasuredWidth() >= targetWidth || getMeasuredHeight() >= targetHeight;
            if(mode == Mode.Width)
                    return getMeasuredWidth() >= targetWidth;
            else
                    return getMeasuredHeight() >= targetHeight;
    }
    
    private Mode getMode(int widthMeasureSpec, int heightMeasureSpec) {
            int widthMode = MeasureSpec.getMode(widthMeasureSpec);
            int heightMode = MeasureSpec.getMode(heightMeasureSpec);
            if(widthMode == MeasureSpec.EXACTLY && heightMode == MeasureSpec.EXACTLY)
                    return Mode.Both;
            if(widthMode == MeasureSpec.EXACTLY)
                    return Mode.Width;
            if(heightMode == MeasureSpec.EXACTLY)
                    return Mode.Height;
            return Mode.None;
    }
    
    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
            super.onMeasure(widthMeasureSpec, heightMeasureSpec);
            if(!inComputation) {
                    this.widthMeasureSpec = widthMeasureSpec;
                    this.heightMeasureSpec = heightMeasureSpec;
                    mode = getMode(widthMeasureSpec, heightMeasureSpec);
                    resizeText();
            }
    }

    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
            resizeText();
    }

    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
            if (w != oldw || h != oldh)
                    resizeText();
    }

    public int getMinTextSize() {
            return minTextSize;
    }

    public void setMinTextSize(int minTextSize) {
            this.minTextSize = minTextSize;
            resizeText();
    }

    public int getMaxTextSize() {
            return maxTextSize;
    }

    public void setMaxTextSize(int maxTextSize) {
            this.maxTextSize = maxTextSize;
            resizeText();
    }
}

The XML attribute file :

<resources>
    <declare-styleable name="FontFitTextView">
        <attr name="minTextSize" format="dimension" />
        <attr name="maxTextSize" format="dimension" />
    </declare-styleable>
</resources>

Check my github for the latest version of this class. I hope it can be useful for someone. If a bug is found or if the code needs explaination, feel free to open an issue on Github.

Solution 5 - Android

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.

Solution 6 - Android

You can now do this without a third party library or a widget. It's built into TextView in API level 26. Add android:autoSizeTextType="uniform" to your TextView and set height to it. That's all. Use app:autoSizeTextType="uniform" for backward compatibility

https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html

<?xml version="1.0" encoding="utf-8"?>
<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform" />

You can also use TextViewCompat for compatibility.

Solution 7 - Android

I had the same problem and wrote a class that seems to work for me. Basically, I used a static layout to draw the text in a separate canvas and remeasure until I find a font size that fits. You can see the class posted in the topic below. I hope it helps.

https://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds/5535672#5535672

Solution 8 - Android

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

Solution 9 - Android

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);
    }
}

Solution 10 - Android

I found the following to work nicely for me. It doesn't loop and accounts for both height and width. Note that it is important to specify the PX unit when calling setTextSize on the view. Thanks to the tip in a previous post for this!

Paint paint = adjustTextSize(getPaint(), numChars, maxWidth, maxHeight);
setTextSize(TypedValue.COMPLEX_UNIT_PX,paint.getTextSize());

Here is the routine I use, passing in the getPaint() from the view. A 10 character string with a 'wide' character is used to estimate the width independent from the actual string.

private static final String text10="OOOOOOOOOO";
public static Paint adjustTextSize(Paint paint, int numCharacters, int widthPixels, int heightPixels) {
	float width = paint.measureText(text10)*numCharacters/text10.length();
	float newSize = (int)((widthPixels/width)*paint.getTextSize());
	paint.setTextSize(newSize);
	
	// remeasure with font size near our desired result
	width = paint.measureText(text10)*numCharacters/text10.length();
	newSize = (int)((widthPixels/width)*paint.getTextSize());
	paint.setTextSize(newSize);

	// Check height constraints
	FontMetricsInt metrics = paint.getFontMetricsInt();
	float textHeight = metrics.descent-metrics.ascent;
	if (textHeight > heightPixels) {
		newSize = (int)(newSize * (heightPixels/textHeight));
    	paint.setTextSize(newSize);
	}

	return paint;
}

Solution 11 - Android

Works with modification

You need to set the text view size like this because otherwise setTextSize assumes the value is in SP units:

setTextSize(TypedValue.COMPLEX_UNIT_PX, trySize);

And you needed to explicitly add this code.

@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);
}

Solution 12 - Android

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"
/>

Solution 13 - Android

I used a variation of Dunni solution above, but that particular code didn't work for me. In particular, when trying to use the Paint object set to have the traits of the view's Paint object, and then calling measureText(), it doesn't return the same value as directly calling the view's Paint object. Perhaps there are some differences in the way my views are set up that make the behavior different.

My solution was to directly use the view's Paint, even though there might be some performance penalties in changing the font size for the view multiple times.

Solution 14 - Android

I've been working on improving the excellent solution from speedplane, and came up with this. It manages the height, including setting the margin such that the text should be centered correctly vertically.

This uses the same function to get the width, as it seems to work the best, but it uses a different function to get the height, as the height isn't provided anywhere. There are some corrections that need to be made, but I figured out a way to do that, while looking pleasing to the eye.

import android.content.Context;
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 {

    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());
        
        //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 = Math.min(targetHeight,100);
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be
        
        Rect bounds = new Rect();

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            mTestPaint.setTextSize(size);
            mTestPaint.getTextBounds(text, 0, text.length(), bounds);
            if((mTestPaint.measureText(text)) >= targetWidth || (1+(2*(size+(float)bounds.top)-bounds.bottom)) >=targetHeight) 
                hi = size; // too big
            else
                lo = size; // too small
        }
        // Use lo so that we undershoot rather than overshoot
        this.setTextSize(TypedValue.COMPLEX_UNIT_PX,(float) lo);

    }

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec)
    {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int parentWidth = MeasureSpec.getSize(widthMeasureSpec);
        int parentHeight = MeasureSpec.getSize(heightMeasureSpec);
        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);
        }
    }

    //Attributes
    private Paint mTestPaint;
}

Solution 15 - Android

Google already made this feature.

<TextView
    android:layout_width="match_parent"
    android:layout_height="200dp"
    android:autoSizeTextType="uniform" />

https://developer.android.com/guide/topics/ui/look-and-feel/autosizing-textview.html

Solution 16 - Android

Inspired by the previous posters I wanted to share my solution. It works with a scale factor which is applied to the previous font size to make it fit the available space. In addition to prevent unexpected behaviour of TextViews onDraw method, it simply draws the text on its own.

public class FontFitTextView extends TextView {

	// How much of the available space should be used in percent.
	private static final float MARGINHEIGHT = 0.8f;
	private static final float MARGINWIDTH = 0.8f;

	private Paint paint;
	private int viewWidth;
	private int viewHeight;
	private float textHeight;
	private float textWidth;

	public FontFitTextView(Context c) {
		this(c, null);
	}

	public FontFitTextView(Context c, AttributeSet attrs) {
		super(c, attrs);
		initComponent();
	}

	// Default constructor override
	public FontFitTextView(Context c, AttributeSet attrs, int defStyle) {
		super(c, attrs, defStyle);
		initComponent();
	}

	private void initComponent() {
		paint = new Paint();
		paint.setTextSize(30);
		paint.setTextAlign(Align.CENTER);
		paint.setAntiAlias(true);
	}

	public void setFontColor(int c) {
		paint.setColor(c);
	}

	private void calcTextSize(String s, Canvas c) {

		float availableHeight = viewHeight;
		float availableWidth = viewWidth;

		// This value scales the old font up or down to match the available
		// space.
		float scale = 1.0f;

		// Rectangle for measuring the text dimensions
		Rect rect = new Rect();
		float oldFontSize = paint.getTextSize();

		// Calculate the space used with old font size
		paint.getTextBounds(s, 0, s.length(), rect);
		textWidth = rect.width();
		textHeight = rect.height();

		// find scale-value to fit the text horizontally
		float scaleWidth = 1f;
		if (textWidth > 0.0f) {
			scaleWidth = (availableWidth) / textWidth * MARGINWIDTH;
		}

		// find scale-value to fit the text vertically
		float scaleHeight = 1f;
		if (textHeight > 0.0f) {
			scaleHeight = (availableHeight) / textHeight * MARGINHEIGHT;
		}

		// We are always limited by the smaller one
		if (scaleWidth < scaleHeight) {
			scale = scaleWidth;
		} else {
			scale = scaleHeight;
		}

		// We apply the scale to the old font size to make it bigger or smaller
		float newFontSize = (oldFontSize * scale);
		paint.setTextSize(newFontSize);
	}

	/**
	 * Calculates the origin on the Y-Axis (width) for the text in this view.
	 * 
	 * @return
	 */
	private float calcStartDrawingPosX() {
		float left = getMeasuredWidth();
		float centerY = left - (viewWidth / 2);
		return centerY;
	}

	/**
	 * Calculates the origin on the Y-Axis (height) for the text in this view.
	 * 
	 * @return
	 */
	private float calcStartDrawingPosY() {
		float bottom = getMeasuredHeight();
		// The paint only centers horizontally, origin on the Y-Axis stays at
		// the bottom, thus we have to lift the origin additionally by the
		// height of the font.
		float centerX = bottom - (viewHeight / 2) + (textHeight / 2);
		return centerX;
	}

	@Override
	protected void onDraw(Canvas canvas) {
		String text = getText().toString();
		if (text.length() > 0) {
			calcTextSize(text, canvas);
			canvas.drawText(text, calcStartDrawingPosX(),
					calcStartDrawingPosY(), paint);
		}
	};

	@Override
	protected void onSizeChanged(int w, int h, int oldw, int oldh) {
		viewWidth = w;
		viewHeight = h;
		super.onSizeChanged(w, h, oldw, oldh);
	}
}

Solution 17 - Android

/* get your context */
Context c = getActivity().getApplicationContext();
	
LinearLayout l = new LinearLayout(c);
l.setOrientation(LinearLayout.VERTICAL);
LayoutParams params = new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT, 0);
 
l.setLayoutParams(params);
l.setBackgroundResource(R.drawable.border);
   
TextView tv=new TextView(c);
tv.setText(" your text here");

/* set typeface if needed */
Typeface tf = Typeface.createFromAsset(c.getAssets(),"fonts/VERDANA.TTF");  
tv.setTypeface(tf);

// LayoutParams lp = new LayoutParams();

tv.setTextColor(Color.parseColor("#282828"));

tv.setGravity(Gravity.CENTER | Gravity.BOTTOM);
//  tv.setLayoutParams(lp);
     
tv.setTextSize(20);
l.addView(tv);

return l;

Solution 18 - Android

This should be a simple solution:

public void correctWidth(TextView textView, int desiredWidth)
{
	Paint paint = new Paint();
	Rect bounds = new Rect();

	paint.setTypeface(textView.getTypeface());
	float textSize = textView.getTextSize();
	paint.setTextSize(textSize);
	String text = textView.getText().toString();
	paint.getTextBounds(text, 0, text.length(), bounds);

	while (bounds.width() > desiredWidth)
	{
		textSize--;
		paint.setTextSize(textSize);
		paint.getTextBounds(text, 0, text.length(), bounds);
	}

	textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}

Solution 19 - Android

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);
  }

Solution 20 - Android

I wrote a short helper class that makes a textview fit within a certain width and adds ellipsize "..." at the end if the minimum textsize cannot be achieved.

Keep in mind that it only makes the text smaller until it fits or until the minimum text size is reached. To test with large sizes, set the textsize to a large number before calling the help method.

It takes Pixels, so if you are using values from dimen, you can call it like this:


float minTextSizePx = getResources().getDimensionPixelSize(R.dimen.min_text_size);
float maxTextWidthPx = getResources().getDimensionPixelSize(R.dimen.max_text_width);
WidgetUtils.fitText(textView, text, minTextSizePx, maxTextWidthPx);

This is the class I use:


public class WidgetUtils {

    public static void fitText(TextView textView, String text, float minTextSizePx, float maxWidthPx) {
        textView.setEllipsize(null);
        int size = (int)textView.getTextSize();
        while (true) {
            Rect bounds = new Rect();
            Paint textPaint = textView.getPaint();
            textPaint.getTextBounds(text, 0, text.length(), bounds);
            if(bounds.width() < maxWidthPx){
                break;
            }
            if (size <= minTextSizePx) {
                textView.setEllipsize(TextUtils.TruncateAt.END);
                break;
            }
            size -= 1;
            textView.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
        }
    }
}

Solution 21 - Android

If a tranformation like allCaps is set, speedplane's approach is buggy. I fixed it, resulting in the following code (sorry, my reputation does not allow me to add this as a comment to speedplane's solution):

import android.content.Context;
import android.graphics.Paint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

public class FontFitTextView extends TextView {

    public FontFitTextView(Context context) {
        super(context);
        initialise();
    }

    public FontFitTextView(Context context, AttributeSet attrs) {
        super(context, attrs);
        initialise();
    }

    private void initialise() {
        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) 
    { 
        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this).toString();
        }
        
        if (textWidth <= 0)
            return;
        int targetWidth = textWidth - this.getPaddingLeft() - this.getPaddingRight();
        float hi = 100;
        float lo = 2;
        final float threshold = 0.5f; // How close we have to be

        mTestPaint.set(this.getPaint());

        while((hi - lo) > threshold) {
            float size = (hi+lo)/2;
            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) {
            refitText(this.getText().toString(), w);
      }
    }

    //Attributes
    private Paint mTestPaint;
}

Solution 22 - Android

I don't known this is correct way or not bt its working ...take your view and check OnGlobalLayoutListener() and get textview linecount then set textSize.

 yourView.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() {
        @Override
        public void onGlobalLayout() {
            if (textView.getLineCount()>=3) {
                textView.setTextSize(20);
            }else{
                //add somthing
              }
        }
    });

Its very simple few line code..

Solution 23 - Android

In my case using app:autoSize was not solving all cases, for example it doesn't prevent word breaking

This is what I ended up using, it will resize down the text so that there are no word breaks on multiple lines

/**
 * Resizes down the text size so that there are no word breaks
 */
class AutoFitTextView @JvmOverloads constructor(
    context: Context,
    attrs: AttributeSet? = null,
    defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {

    private val paint = Paint()
    private val bounds = Rect()

    override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec)
        var shouldResize = false
        paint.typeface = typeface
        var textSize = textSize
        paint.textSize = textSize
        val biggestWord: String = text.split(" ").maxByOrNull { it.count() } ?: return

        // Set bounds equal to the biggest word bounds
        paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)

        // Iterate to reduce the text size so that it makes the biggest word fit the line
        while ((bounds.width() + paddingStart + paddingEnd + paint.fontSpacing) > measuredWidth) {
            textSize--
            paint.textSize = textSize
            paint.getTextBounds(biggestWord, 0, biggestWord.length, bounds)
            shouldResize = true
        }
        if (shouldResize) {
            setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize)
        }
    }
}

Attributions

All content for this solution is sourced from the original question on Stackoverflow.

The content on this page is licensed under the Attribution-ShareAlike 4.0 International (CC BY-SA 4.0) license.

Content TypeOriginal AuthorOriginal Content on Stackoverflow
QuestionrudasView Question on Stackoverflow
Solution 1 - AndroidspeedplaneView Answer on Stackoverflow
Solution 2 - AndroiddunniView Answer on Stackoverflow
Solution 3 - AndroidsulaiView Answer on Stackoverflow
Solution 4 - AndroidyDelouisView Answer on Stackoverflow
Solution 5 - AndroidPascalView Answer on Stackoverflow
Solution 6 - AndroidarsentView Answer on Stackoverflow
Solution 7 - AndroidChaseView Answer on Stackoverflow
Solution 8 - AndroidSuraj VaishnavView Answer on Stackoverflow
Solution 9 - AndroidgjpcView Answer on Stackoverflow
Solution 10 - AndroidGlennView Answer on Stackoverflow
Solution 11 - AndroidgregmView Answer on Stackoverflow
Solution 12 - AndroidGiedrius Å likasView Answer on Stackoverflow
Solution 13 - AndroidThomasWView Answer on Stackoverflow
Solution 14 - AndroidPearsonArtPhotoView Answer on Stackoverflow
Solution 15 - AndroidtseView Answer on Stackoverflow
Solution 16 - AndroidunSinnView Answer on Stackoverflow
Solution 17 - AndroidDhwanik GandhiView Answer on Stackoverflow
Solution 18 - AndroidHamzeh SobohView Answer on Stackoverflow
Solution 19 - AndroidGreg BacchusView Answer on Stackoverflow
Solution 20 - AndroidBjörn KechelView Answer on Stackoverflow
Solution 21 - AndroiddpoetzschView Answer on Stackoverflow
Solution 22 - AndroidMathan ChinnaView Answer on Stackoverflow
Solution 23 - AndroidMohamed KhaledView Answer on Stackoverflow