android - dynamically - textview dynamic size




TextView-Text automatisch skalieren, um innerhalb von Grenzen zu passen (20)

Als ein mobiler Entwickler war ich traurig, nichts natives zu finden, das die automatische Größenanpassung unterstützt. Meine Suchen ergaben nichts, was für mich funktionierte, und am Ende verbrachte ich die bessere Hälfte meines Wochenendes und erstellte meine eigene Textansicht mit automatischer Größenänderung. Ich werde den Code hier posten und hoffentlich wird es für jemand anderen nützlich sein.

Diese Klasse verwendet ein statisches Layout mit der Textfarbe der ursprünglichen Textansicht, um die Höhe zu messen. Von dort aus gehe ich um 2 Font-Pixel und wiederhole, bis ich eine Größe habe, die passt. Wenn der Text am Ende immer noch nicht passt, füge ich eine Ellipse hinzu. Ich hatte Anforderungen, den Text zu animieren und Ansichten wiederzuverwenden, und das scheint auf den Geräten, die ich habe, gut zu funktionieren und scheint schnell genug für mich zu laufen.

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <[email protected]>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */

import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * Text view that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if (mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {

        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if (text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        if (getTransformationMethod() != null) {
            text = getTransformationMethod().getTransformation(text, this);
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();
        // If there is a max text size set, use the lesser of that and the default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
        while (textHeight > height && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paint = new TextPaint(textPaint);
            // Draw using a static layout
            StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if (layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if (lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = textPaint.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while (width < lineWidth + ellipseWidth) {
                        lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint paint, int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paintCopy = new TextPaint(paint);
        // Update the text paint object
        paintCopy.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

Warnung. Es gibt einen wichtigen, behobenen Fehler, der Android 3.1 - 4.04 betrifft und dazu führt, dass alle AutoResizingTextView-Widgets nicht funktionieren. Bitte lesen Sie: https://stackoverflow.com/a/21851157/2075875

Ich bin auf der Suche nach einer optimalen Möglichkeit, die Größe von TextView in TextView so zu ändern, dass sie in die Grenzen von getHeight und getWidth passt. Ich suche nicht einfach nach einer Möglichkeit, den Text zu umbrechen. Ich möchte sicherstellen, dass er beide umschließt und klein genug ist, um vollständig auf den Bildschirm zu passen.

Ich habe einige Fälle in StackOverflow gesehen, wo eine automatische Größenanpassung erforderlich war, aber sie sind entweder sehr spezielle Fälle mit Hack-Lösungen, haben keine Lösung oder beinhalten das TextView des TextView rekursiv, bis es klein genug ist (was Speicherintensität und Kräfte erfordert) der Benutzer den Text Schritt für Schritt mit jeder Rekursion zu beobachten.

Aber ich bin sicher, dass jemand da draußen eine gute Lösung gefunden hat, die nichts mit dem zu tun hat, was ich tue: mehrere schwere Routinen schreiben, die den Text parsen und messen, den Text skalieren und wiederholen, bis eine passende kleine Größe gefunden wurde.

Mit welchen Routinen kann TextView den Text TextView ? Könnten diese nicht irgendwie verwendet werden, um vorherzusagen, ob der Text klein genug ist?

tl; dr : TextView es eine Best-Practice-Methode, um TextView automatisch so TextView , dass es in seine getHeight- und getWidth-Grenzen passt?


Ich begann mit Chases Lösung, musste aber zwei Dinge anpassen, bevor es wie erwartet auf meinem Gerät funktionierte (Galaxy Nexus, Android 4.1):

  1. Verwenden einer Kopie von TextPaint zum Messen des Layouts Die Dokumentation von TextView.getPaint() gibt an, dass es schreibgeschützt verwendet werden soll, also habe ich an beiden Stellen, an denen wir das Paint-Objekt zum Messen verwenden, eine Kopie erstellt:

    // 1. in resizeText()
    if (mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
      // Draw using a static layout
      // modified: use a copy of TextPaint for measuring
      TextPaint paint = new TextPaint(textPaint);
    
    // 2. in getTextHeight()
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
      // modified: make a copy of the original TextPaint object for measuring
      // (apparently the object gets modified while measuring, see also the
      // docs for TextView.getPaint() (which states to access it read-only)
      TextPaint paint = new TextPaint(originalPaint);
      // Update the text paint object
      paint.setTextSize(textSize);
      ...
    
  2. Hinzufügen einer Einheit zum Einstellen der Textgröße

    // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
    setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
    setLineSpacing(mSpacingAdd, mSpacingMult);
    

Mit diesen beiden Modifikationen funktioniert die Lösung perfekt für mich, danke Chase! Ich weiß nicht, ob aufgrund von Android 4.x die ursprüngliche Lösung nicht funktionierte. Wenn Sie es in Aktion sehen oder testen möchten, ob es wirklich auf Ihrem Gerät funktioniert, können Sie sich meine Flashcard-App Flashcards ToGo ansehen, in der ich diese Lösung zum Skalieren des Textes einer Flashcard verwende. Der Text kann beliebig lang sein, und die Karteikarten werden in verschiedenen Aktivitäten angezeigt, manchmal kleiner, manchmal größer, und im Querformat + Hochformat, und ich habe keinen Eckfall gefunden, wo die Lösung nicht richtig funktionieren würde ...



Tatsächlich ist eine Lösung in der Google- DialogTitle Klasse ... obwohl sie nicht so effektiv ist wie die akzeptierte, ist sie viel einfacher und einfach anzupassen.

public class SingleLineTextView extends TextView {

  public SingleLineTextView(Context context, AttributeSet attrs, int defStyle) {
    super(context, attrs, defStyle);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  public SingleLineTextView(Context context, AttributeSet attrs) {
    super(context, attrs);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  public SingleLineTextView(Context context) {
    super(context);
    setSingleLine();
    setEllipsize(TruncateAt.END);
  }

  @Override
  protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);

    final Layout layout = getLayout();
    if (layout != null) {
      final int lineCount = layout.getLineCount();
      if (lineCount > 0) {
        final int ellipsisCount = layout.getEllipsisCount(lineCount - 1);
        if (ellipsisCount > 0) {

          final float textSize = getTextSize();

          // textSize is already expressed in pixels
          setTextSize(TypedValue.COMPLEX_UNIT_PX, (textSize - 1));

          super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        }
      }
    }
  }

}

Warning, bug in Android Honeycomb and Ice Cream Sandwich

Androids versions: 3.1 - 4.04 have a bug, that setTextSize() inside of TextView works only for the 1st time (1st invocation).

Bug is described here: http://code.google.com/p/android/issues/detail?id=22493 http://code.google.com/p/android/issues/detail?id=17343#c9

workaround is to add new line character to text assigned to TextView before changing size:

final String DOUBLE_BYTE_SPACE = "\u3000";
textView.append(DOUBLE_BYTE_SPACE);

I use it in my code as follow:

final String DOUBLE_BYTE_SPACE = "\u3000";
AutoResizeTextView textView = (AutoResizeTextView) view.findViewById(R.id.aTextView);
String fixString = "";
if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.HONEYCOMB_MR1
   && android.os.Build.VERSION.SDK_INT <= android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH_MR1) {  
    fixString = DOUBLE_BYTE_SPACE;
}
textView.setText(fixString + "The text" + fixString);

I add this "\u3000" character on left and right of my text, to keep it centered. If you have it aligned to left then append to the right only. Of course it can be also embedded with AutoResizeTextView widget, but I wanted to keep fix code outside.


A workaround for Android 4.x:

I found AutoResizeTextView and it works great on my Android 2.1 emulator. I loved it so much. But unfortunately it failed on my own 4.0.4 cellphone and 4.1 emulator. After trying I found it could be easily resolved by adding following attributes in AutoResizeTextView class in the xml:

android:ellipsize="none"

android:singleLine="true"

With the 2 lines above, now AutoResizeTextView working perfectly on my 2.1 & 4.1 emulators and my own 4.0.4 cellphone now.

Hoffe das hilft dir. :-)


At google IO conference in 2017, google introduced autoSize property of TextView

https://youtu.be/fjUdJ2aVqE4

<android.support.v7.widget.AppCompatTextView
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:text="@string/my_text"
        app:autoSizeTextType="uniform"
        app:autoSizeMaxTextSize="10sp"
        app:autoSizeMinTextSize="6sp"
        app:autoSizeStepGranularity="1sp"/>

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

Here's a simple solution that uses TextView itself with a TextChangedListened added to it:

expressionView = (TextView) findViewById(R.id.expressionView);
expressionView.addTextChangedListener(textAutoResizeWatcher(expressionView, 25, 55));

private TextWatcher textAutoResizeWatcher(final TextView view, final int MIN_SP, final int MAX_SP) {
    return new TextWatcher() {
        @Override
        public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

        @Override
        public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}

        @Override
        public void afterTextChanged(Editable editable) {

            final int widthLimitPixels = view.getWidth() - view.getPaddingRight() - view.getPaddingLeft();
            Paint paint = new Paint();
            float fontSizeSP = pixelsToSp(view.getTextSize());
            paint.setTextSize(spToPixels(fontSizeSP));

            String viewText = view.getText().toString();

            float widthPixels = paint.measureText(viewText);

            // Increase font size if necessary.
            if (widthPixels < widthLimitPixels){
                while (widthPixels < widthLimitPixels && fontSizeSP <= MAX_SP){
                    ++fontSizeSP;
                    paint.setTextSize(spToPixels(fontSizeSP));
                    widthPixels = paint.measureText(viewText);
                }
                --fontSizeSP;
            }
            // Decrease font size if necessary.
            else {
                while (widthPixels > widthLimitPixels || fontSizeSP > MAX_SP) {
                    if (fontSizeSP < MIN_SP) {
                        fontSizeSP = MIN_SP;
                        break;
                    }
                    --fontSizeSP;
                    paint.setTextSize(spToPixels(fontSizeSP));
                    widthPixels = paint.measureText(viewText);
                }
            }

            view.setTextSize(fontSizeSP);
        }
    };
}

private float pixelsToSp(float px) {
    float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
    return px/scaledDensity;
}

private float spToPixels(float sp) {
    float scaledDensity = getResources().getDisplayMetrics().scaledDensity;
    return sp * scaledDensity;
}

This approach will increase or decrease the font size as needed to fit the text, respecting the MIN_SP and MAX_SP bounds received as parameters.


Here's an enumeration of what else I've found for anyone still searching:

1) Here's a solution that recursively re-paints the textview until it fits. This means literally watching your text shrink into place, but at least it fits when it's done. The code will need some tweaking to implement, but it's mostly there.

2) You can try hacking together a custom solution like this , or dunni's class in this , which is what I did using the getPaint().measureText(str) to search for the right size, but it got a lot messier since I need it to wrap only on whitespace...

3) You can keep searching- I've tried more alternatives than I can count. Ted's advice on StaticLayout hasn't paid off for me but maybe there's something there; I tried using the StaticLayout.getEllipsis(line) to determine if text was going off screen, to no effect. See my (presently un-answered) post about that here .


I combined some of the above suggestions to make one that scales up and down, with bisection method. It also scales within the width.

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <[email protected]>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * Text view that auto adjusts text size to fit within the view. If the text
 * size equals the minimum text size and still does not fit, append with an
 * ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 10;

    // Minimum text size for this text view
    public static final float MAX_TEXT_SIZE = 128;

    private static final int BISECTION_LOOP_WATCH_DOG = 30;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for
    // resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = MAX_TEXT_SIZE;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text
     * size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * 
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * 
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * 
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * 
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * 
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * 
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text
     * size
     * 
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if (mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            // mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */

    @Override
    protected void onLayout(boolean changed, int left, int top, int right,
            int bottom) {
        if (changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft()
                    - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom()
                    - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }

    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {

        // Height and width with a padding as a percentage of height
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * 
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();

        // Do not resize if the view does not have dimensions or there is no
        // text
        if (text == null || text.length() == 0 || height <= 0 || width <= 0
                || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // Bisection method: fast & precise
        float lower = mMinTextSize;
        float upper = mMaxTextSize;
        int loop_counter = 1;
        float targetTextSize = (lower + upper) / 2;
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        int textWidth = getTextWidth(text, textPaint, width, targetTextSize);

        while (loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
            targetTextSize = (lower + upper) / 2;
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            textWidth = getTextWidth(text, textPaint, width, targetTextSize);
            if (textHeight > (height) || textWidth > (width))
                upper = targetTextSize;
            else
                lower = targetTextSize;
            loop_counter++;
        }

        targetTextSize = lower;
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // If we had reached our minimum text size and still don't fit, append
        // an ellipsis
        if (mAddEllipsis && targetTextSize == mMinTextSize
                && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paintCopy = new TextPaint(textPaint);
            paintCopy.setTextSize(targetTextSize);
            StaticLayout layout = new StaticLayout(text, paintCopy, width,
                    Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if (layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut
                // off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if (lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = paintCopy.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the
                    // ellipsis
                    while (width < lineWidth + ellipseWidth) {
                        lineWidth = paintCopy.measureText(text.subSequence(
                                start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line
        // spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if (mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint,
            int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paint = new TextPaint(originalPaint);
        // Update the text paint object
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

    // Set the text size of the text paint object and use a static layout to
    // render text off screen before measuring
    private int getTextWidth(CharSequence source, TextPaint originalPaint,
            int width, float textSize) {
        // Update the text paint object
        TextPaint paint = new TextPaint(originalPaint);
        // Draw using a static layout
        paint.setTextSize(textSize);

        StaticLayout layout = new StaticLayout(source, paint, width,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);

        return (int) layout.getLineWidth(0);
    }
}

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.

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

I hope this helps you

import android.content.Context;
import android.graphics.Rect;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.widget.TextView;

/* Based on 
 * from http://.com/questions/2617266/how-to-adjust-text-font-size-to-fit-textview
 */
public class FontFitTextView extends TextView {

private static float MAX_TEXT_SIZE = 20;

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

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

    float size = this.getTextSize();
    if (size > MAX_TEXT_SIZE)
        setTextSize(MAX_TEXT_SIZE);
}

private void refitText(String text, int textWidth) {
    if (textWidth > 0) {
        float availableWidth = textWidth - this.getPaddingLeft()
                - this.getPaddingRight();

        TextPaint tp = getPaint();
        Rect rect = new Rect();
        tp.getTextBounds(text, 0, text.length(), rect);
        float size = rect.width();

        if (size > availableWidth)
            setTextScaleX(availableWidth / size);
    }
}

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

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

NOTE: I use MAX_TEXT_SIZE in case of text size is bigger than 20 because I don't want to allow big fonts applies to my View, if this is not your case, you can just simply remove it.


I just created the following method (based on the ideas of Chase) which might help you if you want to draw text to any canvas:

private static void drawText(Canvas canvas, int xStart, int yStart,
        int xWidth, int yHeigth, String textToDisplay,
        TextPaint paintToUse, float startTextSizeInPixels,
        float stepSizeForTextSizeSteps) {

    // Text view line spacing multiplier
    float mSpacingMult = 1.0f;
    // Text view additional line spacing
    float mSpacingAdd = 0.0f;
    StaticLayout l = null;
    do {
        paintToUse.setTextSize(startTextSizeInPixels);
        startTextSizeInPixels -= stepSizeForTextSizeSteps;
        l = new StaticLayout(textToDisplay, paintToUse, xWidth,
                Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
    } while (l.getHeight() > yHeigth);

    int textCenterX = xStart + (xWidth / 2);
    int textCenterY = (yHeigth - l.getHeight()) / 2;

    canvas.save();
    canvas.translate(textCenterX, textCenterY);
    l.draw(canvas);
    canvas.restore();
}

This could be used eg in any onDraw() method of any custom view.


I wrote a blog post about this.

I created a component called ResizableButton based on Kirill Grouchnikov's blog post about custom components used in the new android market app. I placed the src code here .

On the other hand, mosabua read my post and told me he was going to open source his implementation which was faster than mine. I hope he release it soon enough :)


If anyone needs it, here is the same code snippet but for Xamarin.Android.

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 1, December 2017
 * 
 * Copyright (C) 2017 Nathan Westfall
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT YOU WANT TO.
 */

using Android.Content;
using Android.Runtime;
using Android.Widget;
using Android.Util;
using Android.Text;

using Java.Lang;

namespace My.Text
{
    public class AutoResizeTextView : TextView
    {
        public const float MIN_TEXT_SIZE = 20;

        public interface OnTextResizeListener
        {
            void OnTextResize(TextView textView, float oldSize, float newSize);
        }

        private const string mEllipsis = "...";

        private OnTextResizeListener mTextResizeListener;

        private bool mNeedsResize = false;

        private float mTextSize;

        private float mMaxTextSize = 0;

        private float mMinTextSize = MIN_TEXT_SIZE;

        private float mSpacingMult = 1.0f;

        private float mSpacingAdd = 0.0f;

        public bool AddEllipsis { get; set; } = true;

        public AutoResizeTextView(Context context) : this(context, null) { }

        public AutoResizeTextView(Context context, IAttributeSet attrs) : this(context, attrs, 0) { }

        public AutoResizeTextView(Context context, IAttributeSet attrs, int defStyle): base(context, attrs, defStyle)
        {
            mTextSize = TextSize;
        }

        protected override void OnTextChanged(ICharSequence text, int start, int lengthBefore, int lengthAfter)
        {
            base.OnTextChanged(text, start, lengthBefore, lengthAfter);

            mNeedsResize = true;

            ResetTextSize();
        }

        protected override void OnSizeChanged(int w, int h, int oldw, int oldh)
        {
            base.OnSizeChanged(w, h, oldw, oldh);

            if (w != oldw || h != oldh)
                mNeedsResize = true;
        }

        public void SetOnResizeListener(OnTextResizeListener listener)
        {
            mTextResizeListener = listener;
        }

        public override void SetTextSize([GeneratedEnum] ComplexUnitType unit, float size)
        {
            base.SetTextSize(unit, size);

            mTextSize = TextSize;
        }

        public override void SetLineSpacing(float add, float mult)
        {
            base.SetLineSpacing(add, mult);

            mSpacingMult = mult;
            mSpacingAdd = add;
        }

        public void SetMaxTextSize(float maxTextSize)
        {
            mMaxTextSize = maxTextSize;
            RequestLayout();
            Invalidate();
        }

        public float GetMaxTextSize()
        {
            return mMaxTextSize;
        }

        public void SetMinTextSize(float minTextSize)
        {
            mMinTextSize = minTextSize;
            RequestLayout();
            Invalidate();
        }

        public float GetMinTextSize()
        {
            return mMinTextSize;
        }

        public void ResetTextSize()
        {
            if(mTextSize > 0)
            {
                base.SetTextSize(ComplexUnitType.Px, mTextSize);
                mMaxTextSize = mTextSize;
            }
        }

        protected override void OnLayout(bool changed, int left, int top, int right, int bottom)
        {
            if(changed || mNeedsResize)
            {
                int widthLimit = (right - left) - CompoundPaddingLeft - CompoundPaddingRight;
                int heightLimit = (bottom - top) - CompoundPaddingBottom - CompoundPaddingTop;
                ResizeText(widthLimit, heightLimit);
            }
            base.OnLayout(changed, left, top, right, bottom);

            base.OnLayout(changed, left, top, right, bottom);
        }

        public void ResizeText()
        {
            int heightLimit = Height - PaddingBottom - PaddingTop;
            int widthLimit = Width - PaddingLeft - PaddingRight;
            ResizeText(widthLimit, heightLimit);
        }

        public void ResizeText(int width, int height)
        {
            var text = TextFormatted;
            if (text == null || text.Length() == 0 || height <= 0 || width <= 0 || mTextSize == 0)
                return;

            if (TransformationMethod != null)
                text = TransformationMethod.GetTransformationFormatted(TextFormatted, this);

            TextPaint textPaint = Paint;

            float oldTextSize = textPaint.TextSize;
            float targetTextSize = mMaxTextSize > 0 ? System.Math.Min(mTextSize, mMaxTextSize) : mTextSize;

            int textHeight = GetTextHeight(text, textPaint, width, targetTextSize);

            while(textHeight > height && targetTextSize > mMinTextSize)
            {
                targetTextSize = System.Math.Max(targetTextSize - 2, mMinTextSize);
                textHeight = GetTextHeight(text, textPaint, width, targetTextSize);
            }

            if(AddEllipsis && targetTextSize == mMinTextSize && textHeight > height)
            {
                TextPaint paint = new TextPaint(textPaint);

                StaticLayout layout = new StaticLayout(text, paint, width, Layout.Alignment.AlignNormal, mSpacingMult, mSpacingAdd, false);
                if(layout.LineCount > 0)
                {
                    int lastLine = layout.GetLineForVertical(height) - 1;
                    if (lastLine < 0)
                        SetText("", BufferType.Normal);
                    else
                    {
                        int start = layout.GetLineStart(lastLine);
                        int end = layout.GetLineEnd(lastLine);
                        float lineWidth = layout.GetLineWidth(lastLine);
                        float ellipseWidth = textPaint.MeasureText(mEllipsis);

                        while (width < lineWidth + ellipseWidth)
                            lineWidth = textPaint.MeasureText(text.SubSequence(start, --end + 1).ToString());
                        SetText(text.SubSequence(0, end) + mEllipsis, BufferType.Normal);
                    }
                }
            }

            SetTextSize(ComplexUnitType.Px, targetTextSize);
            SetLineSpacing(mSpacingAdd, mSpacingMult);

            mTextResizeListener?.OnTextResize(this, oldTextSize, targetTextSize);

            mNeedsResize = false;
        }

        private int GetTextHeight(ICharSequence source, TextPaint paint, int width, float textSize)
        {
            TextPaint paintCopy = new TextPaint(paint);
            paintCopy.TextSize = textSize;
            StaticLayout layout = new StaticLayout(source, paintCopy, width, Layout.Alignment.AlignNormal, mSpacingMult, mSpacingAdd, false);
            return layout.Height;
        }
    }
}

My need was to resize text in order to perfectly fit view bounds. Chase's solution only reduces text size, this one enlarges also the text if there is enough space.

To make all fast & precise i used a bisection method instead of an iterative while, as you can see in resizeText() method. That's why you have also a MAX_TEXT_SIZE option. I also included onoelle's tips.

Tested on Android 4.4

/**
 *               DO WHAT YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 *
 * Copyright (C) 2004 Sam Hocevar <[email protected]>
 *
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 *
 *            DO WHAT YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 *
 *  0. You just DO WHAT YOU WANT TO.
 */

import android.content.Context;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.TypedValue;
import android.widget.TextView;

/**
 * Text view that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 *
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextView extends TextView {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 26;

    // Maximum text size for this text view
    public static final float MAX_TEXT_SIZE = 128;

    private static final int BISECTION_LOOP_WATCH_DOG = 30;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(TextView textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = MAX_TEXT_SIZE;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextView(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextView(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if(mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            //mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if(changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();

        // Bisection method: fast & precise
        float lower = mMinTextSize;
        float upper = mMaxTextSize;
        int loop_counter=1;
        float targetTextSize = (lower+upper)/2;
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        while(loop_counter < BISECTION_LOOP_WATCH_DOG && upper - lower > 1) {
            targetTextSize = (lower+upper)/2;
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
            if(textHeight > height)
                upper = targetTextSize;
            else
                lower = targetTextSize;
            loop_counter++;
        }

        targetTextSize = lower;
        textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paintCopy = new TextPaint(textPaint);
            paintCopy.setTextSize(targetTextSize);
            StaticLayout layout = new StaticLayout(text, paintCopy, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if(layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if(lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = paintCopy.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while(width < lineWidth + ellipseWidth) {
                        lineWidth = paintCopy.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if(mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
        // modified: make a copy of the original TextPaint object for measuring
        // (apparently the object gets modified while measuring, see also the
        // docs for TextView.getPaint() (which states to access it read-only)
        TextPaint paint = new TextPaint(originalPaint);
        // Update the text paint object
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

Refer to the ScalableTextView.java here Auto-fit TextView for Android . I have added the code to shrink and expand the TextView based on the text length


Thanks to Chase and onoelle, for the lazy programmers, let me post here a working version of their fantastic merged code, adapted on a Button, instead of a TextView.

Substitute all your Buttons (not ImageButtons) with AutoResizeTextButtons and the same boring problem is fixed for them too.

Here is the code. I just removed the imports.

/**
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *                    Version 2, December 2004
 * 
 * Copyright (C) 2004 Sam Hocevar <[email protected]>
 * 
 * Everyone is permitted to copy and distribute verbatim or modified
 * copies of this license document, and changing it is allowed as long
 * as the name is changed.
 * 
 *            DO WHAT THE FUCK YOU WANT TO PUBLIC LICENSE
 *   TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION
 * 
 *  0. You just DO WHAT THE FUCK YOU WANT TO.
 *  made better by onoelle
 *  adapted for button by beppi
 */

/**
 * Text Button that auto adjusts text size to fit within the view.
 * If the text size equals the minimum text size and still does not
 * fit, append with an ellipsis.
 * 
 * @author Chase Colburn
 * @since Apr 4, 2011
 */
public class AutoResizeTextButton extends Button {

    // Minimum text size for this text view
    public static final float MIN_TEXT_SIZE = 20;

    // Interface for resize notifications
    public interface OnTextResizeListener {
        public void onTextResize(Button textView, float oldSize, float newSize);
    }

    // Our ellipse string
    private static final String mEllipsis = "...";

    // Registered resize listener
    private OnTextResizeListener mTextResizeListener;

    // Flag for text and/or size changes to force a resize
    private boolean mNeedsResize = false;

    // Text size that is set from code. This acts as a starting point for resizing
    private float mTextSize;

    // Temporary upper bounds on the starting text size
    private float mMaxTextSize = 0;

    // Lower bounds for text size
    private float mMinTextSize = MIN_TEXT_SIZE;

    // Text view line spacing multiplier
    private float mSpacingMult = 1.0f;

    // Text view additional line spacing
    private float mSpacingAdd = 0.0f;

    // Add ellipsis to text that overflows at the smallest text size
    private boolean mAddEllipsis = true;

    // Default constructor override
    public AutoResizeTextButton(Context context) {
        this(context, null);
    }

    // Default constructor when inflating from XML file
    public AutoResizeTextButton(Context context, AttributeSet attrs) {
        this(context, attrs, 0);
    }

    // Default constructor override
    public AutoResizeTextButton(Context context, AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mTextSize = getTextSize();
    }

    /**
     * When text changes, set the force resize flag to true and reset the text size.
     */
    @Override
    protected void onTextChanged(final CharSequence text, final int start, final int before, final int after) {
        mNeedsResize = true;
        // Since this view may be reused, it is good to reset the text size
        resetTextSize();
    }

    /**
     * If the text view size changed, set the force resize flag to true
     */
    @Override
    protected void onSizeChanged(int w, int h, int oldw, int oldh) {
        if (w != oldw || h != oldh) {
            mNeedsResize = true;
        }
    }

    /**
     * Register listener to receive resize notifications
     * @param listener
     */
    public void setOnResizeListener(OnTextResizeListener listener) {
        mTextResizeListener = listener;
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(float size) {
        super.setTextSize(size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set text size to update our internal reference values
     */
    @Override
    public void setTextSize(int unit, float size) {
        super.setTextSize(unit, size);
        mTextSize = getTextSize();
    }

    /**
     * Override the set line spacing to update our internal reference values
     */
    @Override
    public void setLineSpacing(float add, float mult) {
        super.setLineSpacing(add, mult);
        mSpacingMult = mult;
        mSpacingAdd = add;
    }

    /**
     * Set the upper text size limit and invalidate the view
     * @param maxTextSize
     */
    public void setMaxTextSize(float maxTextSize) {
        mMaxTextSize = maxTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return upper text size limit
     * @return
     */
    public float getMaxTextSize() {
        return mMaxTextSize;
    }

    /**
     * Set the lower text size limit and invalidate the view
     * @param minTextSize
     */
    public void setMinTextSize(float minTextSize) {
        mMinTextSize = minTextSize;
        requestLayout();
        invalidate();
    }

    /**
     * Return lower text size limit
     * @return
     */
    public float getMinTextSize() {
        return mMinTextSize;
    }

    /**
     * Set flag to add ellipsis to text that overflows at the smallest text size
     * @param addEllipsis
     */
    public void setAddEllipsis(boolean addEllipsis) {
        mAddEllipsis = addEllipsis;
    }

    /**
     * Return flag to add ellipsis to text that overflows at the smallest text size
     * @return
     */
    public boolean getAddEllipsis() {
        return mAddEllipsis;
    }

    /**
     * Reset the text to the original size
     */
    public void resetTextSize() {
        if(mTextSize > 0) {
            super.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
            mMaxTextSize = mTextSize;
        }
    }

    /**
     * Resize text after measuring
     */
    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        if(changed || mNeedsResize) {
            int widthLimit = (right - left) - getCompoundPaddingLeft() - getCompoundPaddingRight();
            int heightLimit = (bottom - top) - getCompoundPaddingBottom() - getCompoundPaddingTop();
            resizeText(widthLimit, heightLimit);
        }
        super.onLayout(changed, left, top, right, bottom);
    }


    /**
     * Resize the text size with default width and height
     */
    public void resizeText() {
        int heightLimit = getHeight() - getPaddingBottom() - getPaddingTop();
        int widthLimit = getWidth() - getPaddingLeft() - getPaddingRight();
        resizeText(widthLimit, heightLimit);
    }

    /**
     * Resize the text size with specified width and height
     * @param width
     * @param height
     */
    public void resizeText(int width, int height) {
        CharSequence text = getText();
        // Do not resize if the view does not have dimensions or there is no text
        if(text == null || text.length() == 0 || height <= 0 || width <= 0 || mTextSize == 0) {
            return;
        }

        // Get the text view's paint object
        TextPaint textPaint = getPaint();

        // Store the current text size
        float oldTextSize = textPaint.getTextSize();
        // If there is a max text size set, use the lesser of that and the default text size
        float targetTextSize = mMaxTextSize > 0 ? Math.min(mTextSize, mMaxTextSize) : mTextSize;

        // Get the required text height
        int textHeight = getTextHeight(text, textPaint, width, targetTextSize);

        // Until we either fit within our text view or we had reached our min text size, incrementally try smaller sizes
        while(textHeight > height && targetTextSize > mMinTextSize) {
            targetTextSize = Math.max(targetTextSize - 2, mMinTextSize);
            textHeight = getTextHeight(text, textPaint, width, targetTextSize);
        }

        // If we had reached our minimum text size and still don't fit, append an ellipsis
        if(mAddEllipsis && targetTextSize == mMinTextSize && textHeight > height) {
            // Draw using a static layout
            // modified: use a copy of TextPaint for measuring
            TextPaint paint = new TextPaint(textPaint);
            StaticLayout layout = new StaticLayout(text, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false);
            // Check that we have a least one line of rendered text
            if(layout.getLineCount() > 0) {
                // Since the line at the specific vertical position would be cut off,
                // we must trim up to the previous line
                int lastLine = layout.getLineForVertical(height) - 1;
                // If the text would not even fit on a single line, clear it
                if(lastLine < 0) {
                    setText("");
                }
                // Otherwise, trim to the previous line and add an ellipsis
                else {
                    int start = layout.getLineStart(lastLine);
                    int end = layout.getLineEnd(lastLine);
                    float lineWidth = layout.getLineWidth(lastLine);
                    float ellipseWidth = textPaint.measureText(mEllipsis);

                    // Trim characters off until we have enough room to draw the ellipsis
                    while(width < lineWidth + ellipseWidth) {
                        lineWidth = textPaint.measureText(text.subSequence(start, --end + 1).toString());
                    }
                    setText(text.subSequence(0, end) + mEllipsis);
                }
            }
        }

        // Some devices try to auto adjust line spacing, so force default line spacing
        // and invalidate the layout as a side effect
//      textPaint.setTextSize(targetTextSize);
     // modified: setting text size via this.setTextSize (instead of textPaint.setTextSize(targetTextSize))
        setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSize);
        setLineSpacing(mSpacingAdd, mSpacingMult);

        // Notify the listener if registered
        if(mTextResizeListener != null) {
            mTextResizeListener.onTextResize(this, oldTextSize, targetTextSize);
        }

        // Reset force resize flag
        mNeedsResize = false;
    }

    // Set the text size of the text paint object and use a static layout to render text off screen before measuring
    private int getTextHeight(CharSequence source, TextPaint originalPaint, int width, float textSize) {
          // modified: make a copy of the original TextPaint object for measuring
          // (apparently the object gets modified while measuring, see also the
          // docs for TextView.getPaint() (which states to access it read-only)
        // Update the text paint object
          TextPaint paint = new TextPaint(originalPaint);
        paint.setTextSize(textSize);
        // Measure using a static layout
        StaticLayout layout = new StaticLayout(source, paint, width, Alignment.ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true);
        return layout.getHeight();
    }

}

Verwendung:

put a AutoResizeTextButton inside your xml in replace of a normal Button, without changing anything else. Inside the onCreate() put (for example):

    myButton = (AutoResizeTextButton)getView().findViewById(id.myButton);
    myButton.setMinTextSize(8f);
    myButton.resizeText();

This solutions works for us:

public class CustomFontButtonTextFit extends CustomFontButton
{
    private final float DECREMENT_FACTOR = .1f;

    public CustomFontButtonTextFit(Context context) {
        super(context);
    }

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

    public CustomFontButtonTextFit(Context context, AttributeSet attrs,
            int defStyle) {
        super(context, attrs, defStyle);
    }

    private synchronized void refitText(String text, int textWidth) {
        if (textWidth > 0) 
        {
            float availableWidth = textWidth - this.getPaddingLeft()
                    - this.getPaddingRight();

            TextPaint tp = getPaint();
            Rect rect = new Rect();
            tp.getTextBounds(text, 0, text.length(), rect);
            float size = rect.width();

            while(size > availableWidth)
            {
                setTextSize( getTextSize() - DECREMENT_FACTOR );
                tp = getPaint();

                tp.getTextBounds(text, 0, text.length(), rect);
                size = rect.width();
            }
        }
    }

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

        if(parentWidth < getSuggestedMinimumWidth())
            parentWidth = getSuggestedMinimumWidth();

        if(parentHeight < getSuggestedMinimumHeight())
            parentHeight = getSuggestedMinimumHeight();

        this.setMeasuredDimension(parentWidth, parentHeight);
    }

    @Override
    protected void onTextChanged(final CharSequence text, final int start,
            final int before, final int after) 
    {
        super.onTextChanged(text, start, before, after);

        refitText(text.toString(), this.getWidth());
    }

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

        if (w != oldw) 
            refitText(this.getText().toString(), w);
    }
}




textwrapping