How to scale/resize text to fit a TextView?

94,519

Solution 1

The AutofitTextView library from MavenCentral handles this nicely. The source hosted on Github(1k+ stars) at https://github.com/grantland/android-autofittextview

Add the following to your app/build.gradle

repositories {
    mavenCentral()
}

dependencies {
    implementation 'me.grantland:autofittextview:0.2.+'
}

Enable any View extending TextView in code:

AutofitHelper.create(textView);

Enable any View extending TextView in XML:

<me.grantland.widget.AutofitLayout
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    >
    <Button
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:singleLine="true"
        />
</me.grantland.widget.AutofitLayout>

Use the built in Widget in code or XML:

<me.grantland.widget.AutofitTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:singleLine="true"
    />

Solution 2

New since Android O:

https://developer.android.com/preview/features/autosizing-textview.html

<TextView
  android:layout_width="wrap_content"
  android:layout_height="wrap_content"
  android:autoSizeTextType="uniform"
  android:autoSizeMinTextSize="12sp"
  android:autoSizeMaxTextSize="100sp"
  android:autoSizeStepGranularity="2sp"
/>

Solution 3

I have played with this for quite some time, trying to get my font sizes correct on a wide variety of 7" tablets (kindle fire, Nexus7, and some inexpensive ones in China with low-res screens) and devices.

The approach that finally worked for me is as follows. The "32" is an arbitrary factor that basically gives about 70+ characters across a 7" tablet horizontal line, which is a font size I was looking for. Adjust accordingly.

textView.setTextSize(getFontSize(activity));


public static int getFontSize (Activity activity) { 

    DisplayMetrics dMetrics = new DisplayMetrics();
    activity.getWindowManager().getDefaultDisplay().getMetrics(dMetrics);

    // lets try to get them back a font size realtive to the pixel width of the screen
    final float WIDE = activity.getResources().getDisplayMetrics().widthPixels;
    int valueWide = (int)(WIDE / 32.0f / (dMetrics.scaledDensity));
    return valueWide;
}

Solution 4

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.

Auto Scale TextView Text to Fit within Bounds

Solution 5

I was able to answer my own question using the following code (see below), but my solution was very specific to the application. For instance, this will probably only look good and/or work for a TextView sized to approx. 1/2 the screen (with also a 40px top margin and 20px side margins... no bottom margin).

The using this approach though, you can create your own similar implementation. The static method basically just looks at the number of characters and determines a scaling factor to apply to the TextView's text size, and then incrementally increases the text size until the overall height (an estimated height -- using the width of the text, the text height, and the width of the TextView) is just below that of the TextView. The parameters necessary to determine the scaling factor (i.e. the if/else if statements) were set by guess-and-check. You'll likely have to play around with the numbers to make it work for your particular application.

This isn't the most elegant solution, though it was easy to code and it works for me. Does anyone have a better approach?

public static void autoScaleTextViewTextToHeight(final TextView tv, String s)
    {       
        float currentWidth=tv.getPaint().measureText(s);
        int scalingFactor = 0;
        final int characters = s.length();
        //scale based on # of characters in the string
        if(characters<5)
        {
            scalingFactor = 1;
        }
        else if(characters>=5 && characters<10)
        {
            scalingFactor = 2;
        }
        else if(characters>=10 && characters<15)
        {
            scalingFactor = 3;
        }
        else if(characters>=15 && characters<20)
        {
            scalingFactor = 3;
        }
        else if(characters>=20 && characters<25)
        {
            scalingFactor = 3;
        }
        else if(characters>=25 && characters<30)
        {
            scalingFactor = 3;
        }
        else if(characters>=30 && characters<35)
        {
            scalingFactor = 3;
        }
        else if(characters>=35 && characters<40)
        {
            scalingFactor = 3;
        }
        else if(characters>=40 && characters<45)
        {
            scalingFactor = 3;
        }
        else if(characters>=45 && characters<50)
        {
            scalingFactor = 3;
        }
        else if(characters>=50 && characters<55)
        {
            scalingFactor = 3;
        }
        else if(characters>=55 && characters<60)
        {
            scalingFactor = 3;
        }
        else if(characters>=60 && characters<65)
        {
            scalingFactor = 3;
        }
        else if(characters>=65 && characters<70)
        {
            scalingFactor = 3;
        }
        else if(characters>=70 && characters<75)
        {
            scalingFactor = 3;
        }
        else if(characters>=75)
        {
            scalingFactor = 5;
        }

        //System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
        //the +scalingFactor is important... increase this if nec. later
        while((((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor)*tv.getTextSize())<tv.getHeight())
        {
            tv.setTextSize(TypedValue.COMPLEX_UNIT_SP, tv.getTextSize()+0.25f);
            currentWidth=tv.getPaint().measureText(s);
            //System.out.println(((int)Math.ceil(currentWidth)/tv.getWidth()+scalingFactor));
        }

        tv.setText(s);
    }

Thanks.

Share:
94,519
RyanM
Author by

RyanM

Updated on October 14, 2021

Comments

  • RyanM
    RyanM over 2 years

    I'm trying to create a method for resizing multi-line text in a TextView such that it fits within the bounds (both the X and Y dimensions) of the TextView.

    At present, I have something, but all it does is resize the text such that just the first letter/character of the text fills the dimensions of the TextView (i.e. only the first letter is viewable, and it's huge). I need it to fit all the lines of the text within the bounds of the TextView.

    Here is what I have so far:

    public static void autoScaleTextViewTextToHeight(TextView tv)
    {
        final float initSize = tv.getTextSize();
        //get the width of the view's back image (unscaled).... 
        float minViewHeight;
        if(tv.getBackground()!=null)
        {
          minViewHeight = tv.getBackground().getIntrinsicHeight();
        }
        else
        {
          minViewHeight = 10f;//some min.
        }
        final float maxViewHeight = tv.getHeight() - (tv.getPaddingBottom()+tv.getPaddingTop())-12;// -12 just to be sure
        final String s = tv.getText().toString();
    
        //System.out.println(""+tv.getPaddingTop()+"/"+tv.getPaddingBottom());
    
        if(minViewHeight >0 && maxViewHeight >2)
        {
          Rect currentBounds = new Rect();
          tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
          //System.out.println(""+initSize);
          //System.out.println(""+maxViewHeight);
          //System.out.println(""+(currentBounds.height()));
    
          float resultingSize = 1;
          while(currentBounds.height() < maxViewHeight)
          {
            resultingSize ++;
            tv.setTextSize(resultingSize);
    
            tv.getPaint().getTextBounds(s, 0, s.length(), currentBounds);
            //System.out.println(""+(currentBounds.height()+tv.getPaddingBottom()+tv.getPaddingTop()));
            //System.out.println("Resulting: "+resultingSize);
          }
          if(currentBounds.height()>=maxViewHeight)
          {
            //just to be sure, reduce the value
            tv.setTextSize(resultingSize-1);
          }
        }
    }
    

    I think the problem is in the use of tv.getPaint().getTextBounds(...). It always returns small numbers for the text bounds... small relative to the tv.getWidth() and tv.getHeight() values... even if the text size is far larger than the width or height of the TextView.

  • RyanM
    RyanM about 14 years
    Thanks, but I want the text to be displayed on multiple lines. I just need all lines to remain within the bounds of the textview and be as big as possible.
  • gh.
    gh. over 11 years
    Develop the working model for your native language (or the first one to be demonstrated) and swap counterparts for multi-language support after successful testing.
  • a54studio
    a54studio about 11 years
    Thanks for posting this. This works perfect and I am going to modify it to accept text size going in and then spit the correct one out.
  • Mark JW
    Mark JW about 11 years
    Nice suggested improvement, yes, you can specify a target size going in and then it can set up the universal size for all devices.
  • Mark
    Mark about 11 years
    Any reason not to collapse the range of 15 to 75 characters to one statement?
  • Mixaz
    Mixaz about 8 years
    Thanks, excellent answer, I would upvote twice for it! It took me a while to understand that TextViews textSize attribute is used now as a max size limit, so you must specify it as big as possible...
  • MK.
    MK. almost 8 years
    I liked the look of this library, but it looks like it doesn't really support StyleSpans, they are likely to cause text cutoff.
  • Offek
    Offek almost 7 years
    Perfect solution! Similar functionality to iOS's adjustsFontSizeToFitWidth
  • letroll
    letroll over 6 years
    or if you use support lib: app:autoSizeTextType="uniform" app:autoSizeMinTextSize="12sp" app:autoSizeMaxTextSize="100sp" app:autoSizeStepGranularity="2sp" with the use of : xmlns:app="schemas.android.com/apk/res-auto"