/* Source: http://adilatwork.blogspot.in/2014/08/android-textview-which-resizes-its-text.html */ package com.ronakmanglani.watchlist.ui.view; import android.content.Context; import android.text.Layout; import android.text.StaticLayout; import android.text.TextPaint; import android.text.TextUtils; import android.util.AttributeSet; import android.util.TypedValue; public class AutoResizeTextView extends RobotoLightTextView { /** Our ellipsis string. */ private static final String mEllipsis = "\u2026"; /** * Upper bounds for text size. * This acts as a starting point for resizing. */ private float mMaxTextSizePixels; /** Lower bounds for text size. */ private float mMinTextSizePixels; /** TextView line spacing multiplier. */ private float mLineSpacingMultiplier = 1.0f; /** TextView additional line spacing. */ private float mLineSpacingExtra = 0.0f; /** * Default constructor override. * * @param context */ public AutoResizeTextView(Context context) { this(context, null); initialise(); setMinTextSize(14); } /** * Default constructor when inflating from XML file. * * @param context * @param attrs */ public AutoResizeTextView(Context context, AttributeSet attrs) { this(context, attrs, 0); initialise(); setMinTextSize(14); } /** * Default constructor override. * * @param context * @param attrs * @param defStyle */ public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); initialise(); setMinTextSize(14); } @Override protected void onLayout (boolean changed, int left, int top, int right, int bottom) { super.onLayout(changed, left, top, right, bottom); resizeText(); } @Override protected void onTextChanged(CharSequence text, int start, int lengthBefore, int lengthAfter) { super.onTextChanged(text, start, lengthBefore, lengthAfter); requestLayout(); } @Override protected void onSizeChanged(int w, int h, int oldw, int oldh) { super.onSizeChanged(w, h, oldw, oldh); if (w != oldw || h != oldh) { requestLayout(); } } @Override public void setTextSize(float size) { setTextSize(TypedValue.COMPLEX_UNIT_SP, size); } @Override public void setTextSize(int unit, float size) { super.setTextSize(unit, size); mMaxTextSizePixels = getTextSize(); requestLayout(); } @Override public void setLineSpacing(float add, float mult) { super.setLineSpacing(add, mult); mLineSpacingMultiplier = mult; mLineSpacingExtra = add; requestLayout(); } @Override public void setEllipsize(TextUtils.TruncateAt where) { super.setEllipsize(where); requestLayout(); } /** * Sets the lower text size limit and invalidates the view. * * @param minTextSizeScaledPixels the minimum size to use for text in this view, * in scaled pixels. */ public void setMinTextSize(float minTextSizeScaledPixels) { mMinTextSizePixels = convertSpToPx(minTextSizeScaledPixels); requestLayout(); } /** * @return lower text size limit, in pixels. */ public float getMinTextSizePixels() { return mMinTextSizePixels; } private void initialise() { mMaxTextSizePixels = getTextSize(); } /** * Resizes this view's text size with respect to its width and height * (minus padding). */ private void resizeText() { final int availableHeightPixels = getHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop(); final int availableWidthPixels = getWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); final CharSequence text = getText(); // Safety check // (Do not resize if the view does not have dimensions or if there is no text) if (text == null || text.length() <= 0 || availableHeightPixels <= 0 || availableWidthPixels <= 0 || mMaxTextSizePixels <= 0) { return; } float targetTextSizePixels = mMaxTextSizePixels; int targetTextHeightPixels = getTextHeightPixels(text, availableWidthPixels, targetTextSizePixels); // Until we either fit within our TextView // or we have reached our minimum text size, // incrementally try smaller sizes while (targetTextHeightPixels > availableHeightPixels && targetTextSizePixels > mMinTextSizePixels) { targetTextSizePixels = Math.max( targetTextSizePixels - 2, mMinTextSizePixels); targetTextHeightPixels = getTextHeightPixels( text, availableWidthPixels, targetTextSizePixels); } // If we have reached our minimum text size and the text still doesn't fit, // append an ellipsis // (NOTE: Auto-ellipsize doesn't work hence why we have to do it here) // depending on the value of getEllipsize()) if (getEllipsize() != null && targetTextSizePixels == mMinTextSizePixels && targetTextHeightPixels > availableHeightPixels) { // Make a copy of the original TextPaint object for measuring TextPaint textPaintCopy = new TextPaint(getPaint()); textPaintCopy.setTextSize(targetTextSizePixels); // Measure using a StaticLayout instance StaticLayout staticLayout = new StaticLayout( text, textPaintCopy, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL, mLineSpacingMultiplier, mLineSpacingExtra, false); // Check that we have a least one line of rendered text if (staticLayout.getLineCount() > 0) { // Since the line at the specific vertical position would be cut off, // we must trim up to the previous line and add an ellipsis int lastLine = staticLayout.getLineForVertical(availableHeightPixels) - 1; if (lastLine >= 0) { int startOffset = staticLayout.getLineStart(lastLine); int endOffset = staticLayout.getLineEnd(lastLine); float lineWidthPixels = staticLayout.getLineWidth(lastLine); float ellipseWidth = textPaintCopy.measureText(mEllipsis); // Trim characters off until we have enough room to draw the ellipsis while (availableWidthPixels < lineWidthPixels + ellipseWidth) { endOffset--; lineWidthPixels = textPaintCopy.measureText( text.subSequence(startOffset, endOffset + 1).toString()); } setText(text.subSequence(0, endOffset) + mEllipsis); } } } super.setTextSize(TypedValue.COMPLEX_UNIT_PX, targetTextSizePixels); // Some devices try to auto adjust line spacing, so force default line spacing super.setLineSpacing(mLineSpacingExtra, mLineSpacingMultiplier); } /** * Sets the text size of a clone of the view's {@link TextPaint} object * and uses a {@link StaticLayout} instance to measure the height of the text. * * @param source * @param availableWidthPixels * @param textSizePixels * @return the height of the text when placed in a view * with the specified width * and when the text has the specified size. */ private int getTextHeightPixels( CharSequence source, int availableWidthPixels, float textSizePixels) { // Make a copy of the original TextPaint object // since the object gets modified while measuring // (see also the docs for TextView.getPaint() // which states to access it read-only) TextPaint textPaintCopy = new TextPaint(getPaint()); textPaintCopy.setTextSize(textSizePixels); // Measure using a StaticLayout instance StaticLayout staticLayout = new StaticLayout( source, textPaintCopy, availableWidthPixels, Layout.Alignment.ALIGN_NORMAL, mLineSpacingMultiplier, mLineSpacingExtra, true); return staticLayout.getHeight(); } /** * @param scaledPixels * @return the number of pixels which scaledPixels corresponds to on the device. */ private float convertSpToPx(float scaledPixels) { float pixels = scaledPixels * getContext().getResources().getDisplayMetrics().scaledDensity; return pixels; } }