3

I would like to know if there is an easy way to autoresize the text in a textview to fit its available space (vertically and horizontally) without a linebreak or overlapping. My goal is to make the text as large as possible on any device. This should work in landscape and in portrait.

I have it almost working (only in portrait-mode), it looks like this:

myapp in portrait

simplified javacode:

public class MainActivity extends Activity{

private TextView        mTV_veryTop;
private TextView        mTV_top;
private TextView        mTV_middle;
private TextView        mTV_bottomLeft;
private TextView        mTV_bottomRight;
private Boolean         resized = false;

@Override
protected void onCreate(Bundle savedInstanceState)
{
    super.onCreate(savedInstanceState);
    setContentView(R.layout.cockpit_layout);

    mTV_veryTop = (TextView) findViewById(R.id.veryTop);
    mTV_top = (TextView) findViewById(R.id.top);
    mTV_middle = (TextView) findViewById(R.id.middle);
    mTV_bottomLeft = (TextView) findViewById(R.id.bottomLeft);
    mTV_bottomRight = (TextView) findViewById(R.id.bottomRight);

}

@Override
public void onWindowFocusChanged(boolean hasFocus)
{
    // TODO Auto-generated method stub
    super.onWindowFocusChanged(hasFocus);

    if (resized == false)
    {

        shrinkTextToFit(mTV_veryTop.getWidth(), mTV_veryTop, 300, 10);  
        shrinkTextToFit(mTV_top.getWidth(), mTV_top, 300, 10);  
        shrinkTextToFit(mTV_middle.getWidth(), mTV_middle, 300, 10);    
        shrinkTextToFit(mTV_bottomLeft.getWidth(),mTV_bottomLeft, 300, 10); 
        shrinkTextToFit(mTV_bottomRight.getWidth(), mTV_bottomRight, 300, 10);  

        resized = true;
    }

}

public static void shrinkTextToFit(float availableWidth, TextView textView, float startingTextSize, float minimumTextSize)
{

    CharSequence text = textView.getText();
    float textSize = startingTextSize;
    textView.setTextSize(startingTextSize);

    while (text != (TextUtils.ellipsize(text, textView.getPaint(), availableWidth, TextUtils.TruncateAt.END)))
    {
        textSize -= 1;
        if (textSize < minimumTextSize)
        {
            break;
        }
        else
        {
            textView.setTextSize(textSize);
        }
    }
}

}

layout-file:

 <?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="#000000"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/veryTop"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="center"
        android:text="very top"
        android:textColor="@android:color/white"
        android:textSize="500dp" >
    </TextView>

     <TextView
        android:id="@+id/top"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="5"
        android:gravity="center"
        android:text="top"
        android:textColor="@android:color/white"
        android:textSize="500dp" />

    <TextView
        android:id="@+id/middle"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:layout_weight="1"
        android:gravity="middle"
        android:text="N.A."
        android:textColor="@android:color/white"
        android:textSize="500dp" />

    <LinearLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_weight="3"
        android:orientation="horizontal" >



        <TextView
            android:id="@+id/bottomLeft"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_weight="5"
            android:gravity="center"
         android:text="bottom left"
            android:textColor="@android:color/white"
            android:textSize="500dp" >
        </TextView>

        <TextView
            android:id="@+id/bottomRight"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_gravity="center"
            android:layout_weight="5"
            android:gravity="center"
            android:text="bottom right"
            android:textColor="@android:color/white"
            android:textSize="500dp" >
        </TextView>
    </LinearLayout>

</LinearLayout>

.......but this is not working in landscape and not even perfect in portrait (some pixels are missing at the bottom)

i have read several post like these:

How to scale/resize text to fit a TextView?

Auto Scale TextView Text to Fit within Bounds

Scale text in a view to fit?

......and tried different methods/textView classes, but there was always something that didnt work in case of my needs.

Any help?

Update:

I dont know why my question is downvoted, since it is not easy to find a class/textview (under the "million libraries") which does all i asked for. Here is the class which does all i need (might help someone):

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        = 3;

    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) {    //my comment-out
            // mTextRect.bottom = mPaint.getFontSpacing();  //my comment-out
            // mTextRect.right = mPaint.measureText(text);  //my comment-out
            // } else { //my comment-out
            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;
            // }    //my comment-out

            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();
        }
    }
}
Community
  • 1
  • 1
treesoft
  • 295
  • 2
  • 14

1 Answers1

-2

There are already about a million libraries that offer this. Look into their implementation, or just use it directly.

https://github.com/grantland/android-autofittextview

https://bitbucket.org/ankri/autoscaletextview/src

5 seconds on google gave me those.

r2DoesInc
  • 3,704
  • 3
  • 27
  • 58
  • thank you r2DoesInc, i tried the first one, was almost happy. works only in portrait though: i can only see the textviews "very top" and "middle" and only half of them. Trying to figure out whats causing this......might be something in my layout file f.i. the weights....i dont know. As said: i`ve tried classes like these but there was always something that didnt work in case of my needs, for instance: working only in landscape or only in portrait (like this one). – treesoft Aug 28 '14 at 20:33
  • ok i think i can say that the first one just doesnt consider the available height. So if i do: textview.setMinTextSize(20);textview.setMaxTextSize(200); but the available height is not high enough for 200, it does it anyway if it fits in the available width. Does anyone know a class that does it? – treesoft Aug 28 '14 at 22:16