I am an iPhone and Android developer and I was both surprised and upset that Android's text view did not have an auto fit to size option, nor did my searches turn up anything that worked for me. In the end, I spent the better half of my weekend and created my own auto resize text view This class uses a static layout with the text paint of the original text view to draw the text in a separate canvas and measure the height. From here, like the iPhone, I step down by 2 font sizes and remeasure until I have a size that fits.
If at the end, the text still does not I used a very rough binary search to find where I can cut off the text and append an ellipsis I had requirements to animate the text and reuse views and this seems to work well on the devices I have. Although it runs fast enough for me, I am sure there are ways to improve it. I will post the code here and hopefully it will be useful for someone else EDIT(2011/4/6): After looking closer at the static layout class, I found a better and faster way to add an ellipsis EDIT(2011/9/14): Looking back, I found that by putting resizeText in onLayout as oppose to onDraw that there was less visual resizing import android.content.
Context; import android.graphics. Canvas; 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); } // Off screen canvas for text size rendering private static final Canvas sTextResizeCanvas = new Canvas(); // 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() { 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? 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 StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false); layout.
Draw(sTextResizeCanvas); int lastLine = layout. GetLineForVertical(height) - 1; 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 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); 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) { // Update the text paint object paint. SetTextSize(textSize); // Draw using a static layout StaticLayout layout = new StaticLayout(source, paint, width, Alignment.
ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); layout. Draw(sTextResizeCanvas); return layout.getHeight(); } }.
I am an iPhone and Android developer and I was both surprised and upset that Android's text view did not have an auto fit to size option, nor did my searches turn up anything that worked for me. In the end, I spent the better half of my weekend and created my own auto resize text view. This class uses a static layout with the text paint of the original text view to draw the text in a separate canvas and measure the height.
From here, like the iPhone, I step down by 2 font sizes and remeasure until I have a size that fits. If at the end, the text still does not, I used a very rough binary search to find where I can cut off the text and append an ellipsis. I had requirements to animate the text and reuse views and this seems to work well on the devices I have.
Although it runs fast enough for me, I am sure there are ways to improve it. I will post the code here and hopefully it will be useful for someone else. EDIT(2011/4/6): After looking closer at the static layout class, I found a better and faster way to add an ellipsis.
EDIT(2011/9/14): Looking back, I found that by putting resizeText in onLayout as oppose to onDraw that there was less visual resizing. Import android.content. Context; import android.graphics.
Canvas; 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); } // Off screen canvas for text size rendering private static final Canvas sTextResizeCanvas = new Canvas(); // 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() { 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? 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 StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false); layout. Draw(sTextResizeCanvas); int lastLine = layout.
GetLineForVertical(height) - 1; 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 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); 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) { // Update the text paint object paint.
SetTextSize(textSize); // Draw using a static layout StaticLayout layout = new StaticLayout(source, paint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); layout. Draw(sTextResizeCanvas); return layout.getHeight(); } }.
This is actually the best solution I've seen yet, very well done. It's not completely suited to my needs because of the way Android likes to break words when wrapping, so I'll be sticking with my custom solution- but your class is much better for general use. – Nathan Fig Apr 5 at 16:11 Seeing the following on changing from Portrait to Landscape: java.lang.
ArrayIndexOutOfBoundsException at android.text.StaticLayout. GetLineStart(StaticLayout. Java:1243) at com.groupon.view.
AutoResizeTextView. ResizeText(AutoResizeTextView. Java:248) at com.groupon.view.
AutoResizeTextView. OnDraw(AutoResizeTextView. Java:199) – emmby Jun 16 at 22:57 2 where is the donate button?
Thanks man! – passsy Aug 8 at 10:48 There is still a Bug with padding. You need to add return layout.getHeight()-getPaddingBottom()-getPaddingTop(); at the end of getTextHeight() – passsy Aug 11 at 11:16 I don't think that is a bug actually.
That method is trying to find the required height for the text. It then compares against the view height which has had its padding subtracted. Have you had any problems with this?
– Chase Aug 26 at 7:47.
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 :).
This works for a single line of text- but doesn't work for wrapping text. Unless there's something I missed? Measure() seems to measure width on the assumption that all text will be on the same line.
– Nathan Fig Mar 15 at 19:25 Yes, only one line. – Macarse Mar 15 at 21:20 I noticed your implementation does not center the text by default. It floats left.
Thoughts? – Donn Felker May 31 at 21:33.
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 stackoverflow.com/questions/2617266/how-... */ 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.
1 Hmm- the use of "setTextScaleX" just seems to cause the text to get crunched horizontally, rather than resize the text to a smaller (yet readable) format. Also, it seems text-wrap unfriendly. – Nathan Fig Mar 15 at 20:17.
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 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); } }.
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 started with Chase's AutoResizeTextView class, and made a minor change so it would fit both vertically and horizontally. I also discovered a bug which causes a Null Pointer Exception in the Layout Editor (in Eclipse) under some rather obscure conditions. Change 1: Fit the text both vertically and horizontally Chase's original version reduces the text size until it fits vertically, but allows the text to be wider than the target.In my case, I needed the text to fit a specified width.
This change makes it resize until the text fits both vertically and horizontally. In resizeText(int,int) change from: // 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); } to: // Get the required text height int textHeight = getTextHeight(text, textPaint, width, targetTextSize); int textWidth = getTextWidth(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) || (textWidth >= width) ) && targetTextSize > mMinTextSize) { targetTextSize = Math.
Max(targetTextSize - 2, mMinTextSize); textHeight = getTextHeight(text, textPaint, width, targetTextSize); textWidth = getTextWidth(text, textPaint, width, targetTextSize); } Then, at the end of the file, append the getTextWidth() routine; it's just a slightly modified getTextHeight(). It probably would be more efficient to combine them to one routine which returns both height and width. // 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 paint, int width, float textSize) { // Update the text paint object paint.
SetTextSize(textSize); // Draw using a static layout StaticLayout layout = new StaticLayout(source, paint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); layout. Draw(sTextResizeCanvas); return layout.getWidth(); } Change 2: Fix a EmptyStackException in the Eclipse Android Layout Editor Under rather obscure and very precise conditions, the Layout Editor will fail to display the graphical display of the layout; it will throw an "EmptyStackException: null" exception in com.android.ide.eclipse.adt.
The conditions required are: - create an AutoResizeTextView widget - create a style for that widget - specify the text item in the style; not in the widget definition as in: res/layout/main. Xml: res/values/myStyles. Xml: With these files, selecting the Graphical Layout tab when editing main.
Xml will display: error! EmptyStackException: null Exception details are logged in Window > Show View > Error Log instead of the graphical view of the layout.To keep an already too-long story shorter, I tracked this down to the following lines (again in resizeText): // 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; The problem is that under the specific conditions, mTextSize is never initialized; it has the value 0. With the above, targetTextSize is set to zero (as a result of Math. Min).
That zero is passed to getTextHeight() (and getTextWidth()) as the textSize argument. When it gets to layout. Draw(sTextResizeCanvas); we get the exception.
It's more efficient to test if (mTextSize == 0) at the beginning of resizeText() rather than testing in getTextHeight() and getTextWidth(); testing earlier saves all the intervening work. With these updates, the file (as in my crash-demo test app) is now: // // from: http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds // // package com.ajw. DemoCrashInADT; import android.content.
Context; import android.graphics. Canvas; 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.
* * 2011-10-29 changes by Alan Jay Weiner * * change to fit both vertically and horizontally * * test mTextSize for 0 in resizeText() to fix exception in Layout Editor * * @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); } // Off screen canvas for text size rendering private static final Canvas sTextResizeCanvas = new Canvas(); // 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() { 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 // or if mTextSize has not been initialized if (text == null || text.length() == 0 || height 0? Math.
Min(mTextSize, mMaxTextSize) : mTextSize; // Get the required text height int textHeight = getTextHeight(text, textPaint, width, targetTextSize); int textWidth = getTextWidth(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) || (textWidth > width)) && targetTextSize > mMinTextSize) { targetTextSize = Math. Max(targetTextSize - 2, mMinTextSize); textHeight = getTextHeight(text, textPaint, width, targetTextSize); textWidth = getTextWidth(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 StaticLayout layout = new StaticLayout(text, textPaint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, false); layout.
Draw(sTextResizeCanvas); int lastLine = layout. GetLineForVertical(height) - 1; 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 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); 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) { // Update the text paint object paint. SetTextSize(textSize); // Draw using a static layout StaticLayout layout = new StaticLayout(source, paint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); layout.
Draw(sTextResizeCanvas); 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 paint, int width, float textSize) { // Update the text paint object paint. SetTextSize(textSize); // Draw using a static layout StaticLayout layout = new StaticLayout(source, paint, width, Alignment. ALIGN_NORMAL, mSpacingMult, mSpacingAdd, true); layout.
Draw(sTextResizeCanvas); return layout.getWidth(); } } A big thank you to Chase for posting the initial code. I enjoyed reading through it to see how it worked, and I'm pleased to be able to add to it.
You can use the android.text. StaticLayout class for this. That's what TextView uses internally.
– Nathan Fig Feb 17 at 20:34 Refer to my comment on the OP- I had no luck using StaticLayout... – Nathan Fig Mar 11 at 21:05.
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. Anyway, if anyone finds a best-practice, one-off solution to my original question please let me know!
Text view that auto adjusts text size to fit within the view. // Text size that is set from code. = oldw || h!
Float targetTextSize = mMaxTextSize > 0? TargetTextSize = Math. Int lastLine = layout.
Int start = layout.
I cant really gove you an answer,but what I can give you is a way to a solution, that is you have to find the anglde that you relate to or peaks your interest. A good paper is one that people get drawn into because it reaches them ln some way.As for me WW11 to me, I think of the holocaust and the effect it had on the survivors, their families and those who stood by and did nothing until it was too late.