Skip to content

Instantly share code, notes, and snippets.

@marcosrocha85
Created December 19, 2016 13:05
Show Gist options
  • Select an option

  • Save marcosrocha85/1d2a5560bba533c067a465398238bade to your computer and use it in GitHub Desktop.

Select an option

Save marcosrocha85/1d2a5560bba533c067a465398238bade to your computer and use it in GitHub Desktop.

Revisions

  1. marcosrocha85 created this gist Dec 19, 2016.
    297 changes: 297 additions & 0 deletions AutoResizeTextView.java
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,297 @@
    package com.example.AutoResizeTextView;

    import android.annotation.TargetApi;
    import android.content.Context;
    import android.content.res.Resources;
    import android.graphics.RectF;
    import android.os.Build;
    import android.text.Layout.Alignment;
    import android.text.StaticLayout;
    import android.text.TextPaint;
    import android.util.AttributeSet;
    import android.util.SparseIntArray;
    import android.util.TypedValue;
    import android.widget.TextView;

    /*
    * Special thanks to M-WaJeEh (http://stackoverflow.com/users/826606/m-wajeeh)
    * from StackOverflow to share this beauty code without needed to add dependency
    * on project. I hope this will be helpful. Comments, patches and etc will be welcome.
    */
    public class AutoResizeTextView extends TextView {
    private interface SizeTester {
    /**
    * @param suggestedSize Size of text to be tested
    * @param availableSpace available space in which text must fit
    * @return an integer < 0 if after applying {@code suggestedSize} to
    * text, it takes less space than {@code availableSpace}, > 0
    * otherwise
    */
    public int onTestSize(int suggestedSize, RectF availableSpace);
    }

    private RectF mTextRect = new RectF();

    private RectF mAvailableSpaceRect;

    private SparseIntArray mTextCachedSizes;

    private TextPaint mPaint;

    private float mMaxTextSize;

    private float mSpacingMult = 1.0f;

    private float mSpacingAdd = 0.0f;

    private float mMinTextSize = 20;

    private int mWidthLimit;

    private static final int NO_LINE_LIMIT = -1;
    private int mMaxLines;

    private boolean mEnableSizeCache = true;
    private boolean mInitiallized;

    public AutoResizeTextView(Context context) {
    super(context);
    initialize();
    }

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

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

    private void initialize() {
    mPaint = new TextPaint(getPaint());
    mMaxTextSize = getTextSize();
    mAvailableSpaceRect = new RectF();
    mTextCachedSizes = new SparseIntArray();
    if (mMaxLines == 0) {
    // no value was assigned during construction
    mMaxLines = NO_LINE_LIMIT;
    }
    mInitiallized = true;
    }

    @Override
    public void setText(final CharSequence text, BufferType type) {
    super.setText(text, type);
    adjustTextSize(text.toString());
    }

    @Override
    public void setTextSize(float size) {
    mMaxTextSize = size;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
    }

    @Override
    public void setMaxLines(int maxlines) {
    super.setMaxLines(maxlines);
    mMaxLines = maxlines;
    reAdjust();
    }

    public int getMaxLines() {
    return mMaxLines;
    }

    @Override
    public void setSingleLine() {
    super.setSingleLine();
    mMaxLines = 1;
    reAdjust();
    }

    @Override
    public void setSingleLine(boolean singleLine) {
    super.setSingleLine(singleLine);
    if (singleLine) {
    mMaxLines = 1;
    } else {
    mMaxLines = NO_LINE_LIMIT;
    }
    reAdjust();
    }

    @Override
    public void setLines(int lines) {
    super.setLines(lines);
    mMaxLines = lines;
    reAdjust();
    }

    @Override
    public void setTextSize(int unit, float size) {
    Context c = getContext();
    Resources r;

    if (c == null)
    r = Resources.getSystem();
    else
    r = c.getResources();
    mMaxTextSize = TypedValue.applyDimension(unit, size,
    r.getDisplayMetrics());
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
    }

    @Override
    public void setLineSpacing(float add, float mult) {
    super.setLineSpacing(add, mult);
    mSpacingMult = mult;
    mSpacingAdd = add;
    }

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

    private void reAdjust() {
    adjustTextSize(getText().toString());
    }

    private void adjustTextSize(String string) {
    if (!mInitiallized) {
    return;
    }
    int startSize = (int) mMinTextSize;
    int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom()
    - getCompoundPaddingTop();
    mWidthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
    - getCompoundPaddingRight();
    mAvailableSpaceRect.right = mWidthLimit;
    mAvailableSpaceRect.bottom = heightLimit;
    super.setTextSize(
    TypedValue.COMPLEX_UNIT_PX,
    efficientTextSizeSearch(startSize, (int) mMaxTextSize,
    mSizeTester, mAvailableSpaceRect));
    }

    private final SizeTester mSizeTester = new SizeTester() {
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public int onTestSize(int suggestedSize, RectF availableSPace) {
    mPaint.setTextSize(suggestedSize);
    String text = getText().toString();
    boolean singleline = getMaxLines() == 1;
    if (singleline) {
    mTextRect.bottom = mPaint.getFontSpacing();
    mTextRect.right = mPaint.measureText(text);
    } else {
    StaticLayout layout = new StaticLayout(text, mPaint,
    mWidthLimit, Alignment.ALIGN_NORMAL, mSpacingMult,
    mSpacingAdd, true);
    // return early if we have more lines
    if (getMaxLines() != NO_LINE_LIMIT
    && layout.getLineCount() > getMaxLines()) {
    return 1;
    }
    mTextRect.bottom = layout.getHeight();
    int maxWidth = -1;
    for (int i = 0; i < layout.getLineCount(); i++) {
    if (maxWidth < layout.getLineWidth(i)) {
    maxWidth = (int) layout.getLineWidth(i);
    }
    }
    mTextRect.right = maxWidth;
    }

    mTextRect.offsetTo(0, 0);
    if (availableSPace.contains(mTextRect)) {
    // may be too small, don't worry we will find the best match
    return -1;
    } else {
    // too big
    return 1;
    }
    }
    };

    /**
    * Enables or disables size caching, enabling it will improve performance
    * where you are animating a value inside TextView. This stores the font
    * size against getText().length() Be careful though while enabling it as 0
    * takes more space than 1 on some fonts and so on.
    *
    * @param enable enable font size caching
    */
    public void enableSizeCache(boolean enable) {
    mEnableSizeCache = enable;
    mTextCachedSizes.clear();
    adjustTextSize(getText().toString());
    }

    private int efficientTextSizeSearch(int start, int end,
    SizeTester sizeTester, RectF availableSpace) {
    if (!mEnableSizeCache) {
    return binarySearch(start, end, sizeTester, availableSpace);
    }
    String text = getText().toString();
    int key = text == null ? 0 : text.length();
    int size = mTextCachedSizes.get(key);
    if (size != 0) {
    return size;
    }
    size = binarySearch(start, end, sizeTester, availableSpace);
    mTextCachedSizes.put(key, size);
    return size;
    }

    private static int binarySearch(int start, int end, SizeTester sizeTester,
    RectF availableSpace) {
    int lastBest = start;
    int lo = start;
    int hi = end - 1;
    int mid = 0;
    while (lo <= hi) {
    mid = (lo + hi) >>> 1;
    int midValCmp = sizeTester.onTestSize(mid, availableSpace);
    if (midValCmp < 0) {
    lastBest = lo;
    lo = mid + 1;
    } else if (midValCmp > 0) {
    hi = mid - 1;
    lastBest = hi;
    } else {
    return mid;
    }
    }
    // make sure to return last best
    // this is what should always be returned
    return lastBest;

    }

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

    @Override
    protected void onSizeChanged(int width, int height, int oldwidth,
    int oldheight) {
    mTextCachedSizes.clear();
    super.onSizeChanged(width, height, oldwidth, oldheight);
    if (width != oldwidth || height != oldheight) {
    reAdjust();
    }
    }
    }
    30 changes: 30 additions & 0 deletions your_layout.xml
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,30 @@
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:orientation="vertical"
    android:padding="16dp" >

    <com.example.widgets.AutoResizeTextView
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:ellipsize="none"
    android:maxLines="2"
    android:text="Auto Resized Text, max 2 lines"
    android:textSize="100sp" /> <!-- maximum size -->

    <com.example.widgets.AutoResizeTextView
    android:layout_width="match_parent"
    android:layout_height="100dp"
    android:ellipsize="none"
    android:gravity="center"
    android:maxLines="1"
    android:text="Auto Resized Text, max 1 line"
    android:textSize="100sp" /> <!-- maximum size -->

    <com.example.widgets.AutoResizeTextView
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:text="Auto Resized Text"
    android:textSize="500sp" /> <!-- maximum size -->

    </LinearLayout>