I have TextView:
text.setTypeface(font);
text.setWidth((int)width);
text.setGravity(Gravity.CENTER);
text.setHeight((int)height);
text.setIncludeFontPadding(false);
text.setPadding(0,0,0,0);
However even with the padding set to 0, and setIncludeFontPadding to false and I set the font size to the height of the TextView I still get this:
If I set the padding to have negative values like -30, It fixes it:
text.setPadding(0,-30,0,-30);
My question is why do I have to do this? Where is this phantom padding coming from? And where am I finding this arbitrary (-30 in this specific case) value I need to set it to, so that the text fills the height?
Update
Trying #Mike M. 's solution I got these results. When it was a positive number, it was a smaller size than when it was a negative one. Both of them still had padding:
Update 2 Here the custom class in full:
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.Canvas;
import android.graphics.Rect;
import android.graphics.RectF;
import android.os.Build;
import android.text.Layout;
import android.text.Layout.Alignment;
import android.text.StaticLayout;
import android.text.TextPaint;
import android.util.AttributeSet;
import android.util.Log;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;
/**
* Special textview which gracefully handles resizing.
*/
public class iTextView extends TextView
{
// Set true to remove phantom padding
public boolean trimPadding = false;
//region Interfaces
private interface SizeTester
{
/**
* Interface for scaling text to fit.
* #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.
*/
int onTestSize(int suggestedSize, RectF availableSpace);
}
//endregion
//region Variables
private static final int NO_LINE_LIMIT = -1;
private RectF _textRect = new RectF();
private RectF _availableSpaceRect;
private SparseIntArray _textCachedSizes;
private TextPaint _paint;
private float _maxTextSize;
private float _spacingMult;
private float _spacingAdd;
private float _minTextSize;
private int _widthLimit;
private int _maxLines;
private boolean _enableSizeCache;
private boolean _initialized;
//endregion
//region Constructors
public iTextView(Context context)
{
super(context);
initialize();
}
public iTextView(Context context, AttributeSet attrs)
{
super(context, attrs);
initialize();
}
public iTextView(Context context, AttributeSet attrs, int defStyle)
{
super(context, attrs, defStyle);
initialize();
}
//endregion
//region Initialization
private void initialize()
{
_spacingMult = 1.0f;
_spacingAdd = 0.0f;
_minTextSize = 20;
_enableSizeCache = true;
_paint = new TextPaint(getPaint());
_maxTextSize = getTextSize();
_availableSpaceRect = new RectF();
_textCachedSizes = new SparseIntArray();
if (_maxLines == 0)
{
// No value was assigned during construction
_maxLines = NO_LINE_LIMIT;
}
_initialized = true;
}
//endregion
//region Text Value
#Override
public void setText(final CharSequence text, BufferType type)
{
super.setText(text, type);
adjustTextSize(text.toString());
}
#Override
protected void onTextChanged(final CharSequence text, final int start, final int before,
final int after)
{
super.onTextChanged(text, start, before, after);
reAdjust();
}
//endregion
//region Text Sizing
#Override
public void setTextSize(float size)
{
_maxTextSize = size;
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
/**
* Ensures the text is as big as possible for the text area.
*/
public void setTextSizeToMaxFit()
{
_maxTextSize = 999;
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
#Override
public void setTextSize(int unit, float size)
{
Context context = getContext();
Resources resources;
if (context == null)
{
resources = Resources.getSystem();
} else
{
resources = context.getResources();
}
_maxTextSize = TypedValue.applyDimension(unit, size, resources.getDisplayMetrics());
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(float minTextSize)
{
_minTextSize = minTextSize;
reAdjust();
}
private void reAdjust() {
adjustTextSize(getText().toString());
}
private void adjustTextSize(String string)
{
if (!_initialized)
{
return;
}
int startSize = (int) _minTextSize;
int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
_widthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
_availableSpaceRect.right = _widthLimit;
_availableSpaceRect.bottom = heightLimit;
super.setTextSize
(
TypedValue.COMPLEX_UNIT_PX,
efficientTextSizeSearch(startSize, (int)_maxTextSize, mSizeTester, _availableSpaceRect)
);
}
private final SizeTester mSizeTester = new SizeTester()
{
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public int onTestSize(int suggestedSize, RectF availableSPace)
{
_paint.setTextSize(suggestedSize);
String text = getText().toString();
boolean singleLine = getMaxLines() == 1;
if (singleLine)
{
_textRect.bottom = _paint.getFontSpacing();
_textRect.right = _paint.measureText(text);
} else
{
StaticLayout layout = new StaticLayout
(
text,
_paint,
_widthLimit,
Alignment.ALIGN_NORMAL,
_spacingMult,
_spacingAdd,
true
);
// Return early if no more lines
if (getMaxLines() != NO_LINE_LIMIT && layout.getLineCount() > getMaxLines())
{
return 1;
}
_textRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++)
{
if (maxWidth < layout.getLineWidth(i))
{
maxWidth = (int)layout.getLineWidth(i);
}
}
_textRect.right = maxWidth;
}
_textRect.offsetTo(0, 0);
if (availableSPace.contains(_textRect))
{
// May be too small, Will find the best match later
return -1;
} else
{
// Too big
return 1;
}
}
};
/**
* Enables or disables size caching, enabling it will improve performance
* where its animating a value inside TextView. This stores the font
* size against getText().length() 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)
{
_enableSizeCache = enable;
_textCachedSizes.clear();
adjustTextSize(getText().toString());
}
private int efficientTextSizeSearch(int start, int end, SizeTester sizeTester, RectF availableSpace)
{
if (!_enableSizeCache)
{
return binarySearch(start, end, sizeTester, availableSpace);
}
String text = getText().toString();
int key = text == null ? 0 : text.length();
int size = _textCachedSizes.get(key);
if (size != 0)
{
return size;
}
size = binarySearch(start, end, sizeTester, availableSpace);
_textCachedSizes.put(key, size);
return size;
}
#Override
protected void onSizeChanged(int width, int height, int oldwidth,int oldheight)
{
_textCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight)
{
reAdjust();
}
}
private static int binarySearch(int start, int end, SizeTester sizeTester,RectF availableSpace)
{
int lastBest = start;
int low = start;
int high = end - 1;
int mid = 0;
while (low <= high)
{
mid = (low + high) >>> 1;
int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0)
{
lastBest = low;
low = mid + 1;
} else if (midValCmp > 0)
{
high = mid - 1;
lastBest = high;
} else
{
return mid;
}
}
// Make sure to return last best
// This is what should always be returned
return lastBest;
}
//endregion
//region Text Lines
#Override
public void setMaxLines(int maxlines)
{
super.setMaxLines(maxlines);
_maxLines = maxlines;
reAdjust();
}
public int getMaxLines()
{
return _maxLines;
}
#Override
public void setSingleLine()
{
super.setSingleLine();
_maxLines = 1;
reAdjust();
}
#Override
public void setSingleLine(boolean singleLine)
{
super.setSingleLine(singleLine);
if (singleLine)
{
_maxLines = 1;
} else
{
_maxLines = NO_LINE_LIMIT;
}
reAdjust();
}
#Override
public void setLines(int lines)
{
super.setLines(lines);
_maxLines = lines;
reAdjust();
}
#Override
public void setLineSpacing(float add, float mult)
{
super.setLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
//endregion
//region Padding Fix
#Override
protected void onDraw(Canvas canvas)
{
if (trimPadding)
{
trimVertical();
}
super.onDraw(canvas);
}
private void trimVertical()
{
final Layout layout = getLayout();
final Rect textBounds = new Rect();
if (layout == null)
{
Log.d("Layout is null","" + layout);
return;
}
int baseline = layout.getLineBaseline(0);
getTextBounds(0, textBounds);
final int pTop = baseline + textBounds.top;
final int lastLine = getLineCount() - 1;
baseline = layout.getLineBaseline(lastLine);
getTextBounds(lastLine, textBounds);
final int pBottom = layout.getHeight() - baseline - textBounds.bottom + 1;
setPadding(getPaddingLeft(), -pTop, getPaddingRight(), -pBottom);
}
private void getTextBounds(int line, Rect bounds)
{
final String s = getText().toString();
final int start = getLayout().getLineStart(line);
final int end = getLayout().getLineEnd(line);
getPaint().getTextBounds(s, start, end, bounds);
}
//endregion
}
Related
I am getting different text size for same screen size, different dpi devices. I want the text to be the exact same size on all devices (like Inshorts app).
As can be seen in these screenshots text size of xxhdpi is slightly bigger than 560 dpi device.
My xml layout:
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity"
android:layout_margin="30dp">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="Lorem Ipsum is simply dummy text of the printing and typesetting industry. Lorem Ipsum has been the industry's standard dummy text ever since the 1500s, when an unknown printer took a galley of type and scrambled it to make a type specimen book. It has survived not only five centuries, but also the leap into electronic typesetting, remaining essentially unchanged. It was popularised in the 1960s with the release of Letraset sheets containing Lorem Ipsum passages, and more recently with desktop publishing software like Aldus PageMaker including versions of Lorem Ipsum."
android:textSize="14sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent" />
</android.support.constraint.ConstraintLayout>
I have also tried using dp in place of sp without any success.
Please help.
If you want your text sizes to be density-independent, you should use dp instead of sp.
With this way, your text sizes also won't be affected by user textsize preferences in phone settings.
Add the follow class (AutoResizeTextView) into your project and then add the follow code into your xml
at value android:maxLines="1" put whatever you want, so the auto calculation change the font size
<com.example.youpackage.AutoResizeTextView
android:id="#+id/textViewTitle"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="0.40"
android:text="#string/title"
android:textColor="#FFFFFF"
android:maxLines="1"
android:paddingBottom="5dp"
android:layout_marginBottom="5dp"
android:gravity="center_vertical|center_horizontal"
android:textSize="50sp" />
here is the AutoResizeTextView class
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.text.method.TransformationMethod;
import android.util.AttributeSet;
import android.util.SparseIntArray;
import android.util.TypedValue;
import android.widget.TextView;
/**
* http://stackoverflow.com/questions/16017165/auto-fit-textview-for-android/21851239
*/
public class AutoResizeTextView extends TextView {
public AutoResizeTextView(Context context) {
super(context);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs) {
super(context, attrs);
initialize();
}
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
initialize();
}
#TargetApi(Build.VERSION_CODES.LOLLIPOP)
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
initialize();
}
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 = 10;
private int mWidthLimit;
private static final int NO_LINE_LIMIT = -1;
private int mMaxLines;
private boolean mEnableSizeCache = true;
private boolean mInitializedDimens;
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;
}
}
#Override
public void setTextSize(float size) {
mMaxTextSize = size;
mTextCachedSizes.clear();
adjustTextSize();
}
#Override
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
mMaxLines = maxlines;
adjustTextSize();
}
public int getMaxLines() {
return mMaxLines;
}
#Override
public void setSingleLine() {
super.setSingleLine();
mMaxLines = 1;
adjustTextSize();
}
#Override
public void setSingleLine(boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine) {
mMaxLines = 1;
} else {
mMaxLines = NO_LINE_LIMIT;
}
adjustTextSize();
}
#Override
public void setLines(int lines) {
super.setLines(lines);
mMaxLines = lines;
adjustTextSize();
}
#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();
}
#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;
adjustTextSize();
}
private void adjustTextSize() {
if (!mInitializedDimens) {
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 = getTransformedText();
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();
}
private int efficientTextSizeSearch(int start, int end,
SizeTester sizeTester, RectF availableSpace) {
if (!mEnableSizeCache) {
return binarySearch(start, end, sizeTester, availableSpace);
}
int key = getText().toString().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;
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);
adjustTextSize();
}
#Override
protected void onSizeChanged(int width, int height, int oldwidth,
int oldheight) {
mInitializedDimens = true;
mTextCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight) {
adjustTextSize();
}
}
private String getTransformedText() {
CharSequence text = getText();
if (text != null) {
TransformationMethod transformationMethod = getTransformationMethod();
if (transformationMethod != null) {
text = transformationMethod.getTransformation(text, this);
}
}
return text == null ? null : text.toString();
}
}
You need to use dimens to understand that please visit following links:
For further details visit
Dimens
Also look answer of following question:
How to use dimens.xml
try different dimens
create file dimens.xml(sw-320dp-xhdpi)
<resources>
<dimen name="textSize">16sp</dimen>
</resources>
create file dimens.xml(sw-320dp-xxhdpi)
<resources>
<dimen name="textSize">14sp</dimen>
</resources>
adjust textSize as you required
Here is a part of my project that resize the text based on screen size.
Is this what you want?
double height;
double width = parent.getMeasuredWidth();
boolean isLandscape;
if ( getResources().getConfiguration().orientation == Configuration.ORIENTATION_PORTRAIT ) {
isLandscape = false;
height = parent.getMeasuredHeight() / 3;
width = width + (parent.getPaddingLeft() + parent.getPaddingRight());
} else {
isLandscape = true;
height = parent.getMeasuredHeight() / 2;
width = parent.getMeasuredWidth() / 2;
width = width + ((parent.getPaddingLeft() + parent.getPaddingRight()) / 2);
}
height = height + (parent.getPaddingTop() + parent.getPaddingBottom());
view.setMinimumHeight((int) Math.round(height));
double h = height / 100;
double w = width / 100;
if ( isLandscape ) {
textViewService.setX((int) Math.round(w * 24));
textViewService.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) Math.round(16 * h));
} else {
textViewService.setX((int) Math.round(w * 23));
textViewService.setTextSize(TypedValue.COMPLEX_UNIT_PX, (int) Math.round(18 * h));
}
In my xml I have already added following attributes.
<com.project.current.widget.AutoResizeEditText
android:id="#+id/edittext_add_text_new"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:focusable="true"
android:focusableInTouchMode="true"
android:hint="#string/text_sticker_hint"
android:inputType="textNoSuggestions|textVisiblePassword|textMultiLine"
android:isScrollContainer="false"
android:padding="#dimen/dimen_margin_small"
android:textColor="#color/black"
android:textColorHint="#color/blue_popover"
android:textSize="#dimen/text_font_edit_text_default"
android:layout_alignParentTop="true"
android:layout_centerHorizontal="true"
android:background="#drawable/bg_text_sticker"
android:layout_toRightOf="#+id/resize_icon_left"
android:cursorVisible="true"
android:textCursorDrawable="#null"
android:textIsSelectable="true" />
I have set Focusable and FocusableOnTouch to true. I also have cursorVisible set to true. And just to make sure the cursor isn't taking white color(the background is whitish), I have set the textCursorDrawable to '#null'
But the cursor still isnt visible in the view. I have a touchListener implemented to the edittext (since I also need to move it around the parent layout). So I have set the keyboard to pop up at ACTION_UP. At that point I when I check
editTextViewNew.isCursorVisible()
It returns true. But the cursor is actually not visible. How do I make it visible and the text within my edittext selectable? (The long press option to 'paste' copied text also doesn't work.)
I am using the following Custom AutoResizeEditText to resize the text within editText as per my need.
public class AutoResizeEditText extends android.support.v7.widget.AppCompatEditText {
private static final int NO_LINE_LIMIT = -1;
private final RectF _availableSpaceRect = new RectF();
private final SparseIntArray _textCachedSizes = new SparseIntArray();
private final SizeTester _sizeTester;
private float _maxTextSize;
private float _spacingMult = 1.0f;
private float _spacingAdd = 0.0f;
private float _minTextSize;
private int _widthLimit;
private int _maxLines;
private boolean _enableSizeCache = true;
private boolean _initiallized = false;
private TextPaint paint;
//set Boolean for disabling auto-resize when user is resizing using controls
public static boolean isUserTyping = false;
//disable autoresizing when user is not typing text
public static void checkIfUserTyping(boolean isActive){
isUserTyping = isActive;
}
private interface SizeTester {
/**
* AutoResizeEditText
*
* #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);
}
public AutoResizeEditText(final Context context) {
this(context, null, 0);
}
public AutoResizeEditText(final Context context, final AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoResizeEditText(final Context context, final AttributeSet attrs,
final int defStyle) {
super(context, attrs, defStyle);
// using the minimal recommended font size
_minTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP,
12, getResources().getDisplayMetrics());
_maxTextSize = getTextSize();
if (_maxLines == 0)
// no value was assigned during construction
_maxLines = NO_LINE_LIMIT;
// prepare size tester:
_sizeTester = new SizeTester() {
final RectF textRect = new RectF();
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public int onTestSize(final int suggestedSize,
final RectF availableSPace) {
paint.setTextSize(suggestedSize);
final String text = getText().toString();
final boolean singleline = getMaxLines() == 1;
if (singleline) {
textRect.bottom = paint.getFontSpacing();
textRect.right = paint.measureText(text);
} else {
final StaticLayout layout = new StaticLayout(text, paint,
_widthLimit, Layout.Alignment.ALIGN_NORMAL, _spacingMult,
_spacingAdd, true);
// return early if we have more lines
Log.d("NLN", "Current Lines = " + Integer.toString(layout.getLineCount()));
Log.d("NLN", "Max Lines = " + Integer.toString(getMaxLines()));
if (getMaxLines() != NO_LINE_LIMIT
&& layout.getLineCount() > getMaxLines())
return 1;
textRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++)
if (maxWidth < layout.getLineWidth(i))
maxWidth = (int) layout.getLineWidth(i);
textRect.right = maxWidth;
}
textRect.offsetTo(0, 0);
if (availableSPace.contains(textRect))
// may be too small, don't worry we will find the best match
return -1;
// else, too big
return 1;
}
};
setEnabled(true);
setFocusableInTouchMode(true);
setFocusable(true);
setEnableSizeCache(false);
setMovementMethod(null);
_initiallized = true;
}
#Override
public void setTypeface(final Typeface tf) {
if (paint == null)
paint = new TextPaint(getPaint());
paint.setTypeface(tf);
super.setTypeface(tf);
}
#Override
public void setTextSize(final float size) {
_maxTextSize = size;
_textCachedSizes.clear();
adjustTextSize();
}
#Override
public void setMaxLines(final int maxlines) {
super.setMaxLines(maxlines);
_maxLines = maxlines;
reAdjust();
}
#Override
public int getMaxLines() {
return _maxLines;
}
#Override
public void setSingleLine() {
super.setSingleLine();
_maxLines = 1;
reAdjust();
}
#Override
public void setSingleLine(final boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine)
_maxLines = 1;
else
_maxLines = NO_LINE_LIMIT;
reAdjust();
}
#Override
public void setLines(final int lines) {
super.setLines(lines);
_maxLines = lines;
reAdjust();
}
#Override
public void setTextSize(final int unit, final float size) {
final Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else
r = c.getResources();
_maxTextSize = TypedValue.applyDimension(unit, size,
r.getDisplayMetrics());
_textCachedSizes.clear();
adjustTextSize();
}
#Override
public void setLineSpacing(final float add, final float mult) {
super.setLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* #param
*/
public void setMinTextSize(final float minTextSize) {
_minTextSize = minTextSize;
reAdjust();
}
private void reAdjust() {
adjustTextSize();
}
private void adjustTextSize() {
if (!_initiallized)
return;
Log.d("IsUserTyping"," "+isUserTyping);
if(!isUserTyping)
return;
final int startSize = (int) _minTextSize;
/*final int heightLimit = getMeasuredHeight()
- getCompoundPaddingBottom() - getCompoundPaddingTop();
_widthLimit = getMeasuredWidth() - getCompoundPaddingLeft()
- getCompoundPaddingRight();*/
//User maxWidth and maxHeight the textBox can attain to calculate resize values
final int heightLimit = getMaxHeight()
- getCompoundPaddingBottom() - getCompoundPaddingTop();
_widthLimit = getMaxWidth() - getCompoundPaddingLeft()
- getCompoundPaddingRight();
if (_widthLimit <= 0)
return;
_availableSpaceRect.right = _widthLimit;
_availableSpaceRect.bottom = heightLimit;
super.setTextSize(
TypedValue.COMPLEX_UNIT_PX,
efficientTextSizeSearch(startSize, (int) _maxTextSize,
_sizeTester, _availableSpaceRect));
}
/**
* 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 setEnableSizeCache(final boolean enable) {
_enableSizeCache = enable;
_textCachedSizes.clear();
adjustTextSize();
}
private int efficientTextSizeSearch(final int start, final int end,
final SizeTester sizeTester, final RectF availableSpace) {
if (!_enableSizeCache)
return binarySearch(start, end, sizeTester, availableSpace);
final String text = getText().toString();
final int key = text == null ? 0 : text.length();
int size = _textCachedSizes.get(key);
if (size != 0)
return size;
size = binarySearch(start, end, sizeTester, availableSpace);
_textCachedSizes.put(key, size);
return size;
}
private int binarySearch(final int start, final int end,
final SizeTester sizeTester, final RectF availableSpace) {
int lastBest = start;
int lo = start;
int hi = end - 1;
int mid = 0;
while (lo <= hi) {
mid = lo + hi >>> 1;
final 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(final int width, final int height,
final int oldwidth, final int oldheight) {
_textCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight)
reAdjust();
}
}
I found these details implemented here, which was easy to emulate on my end.
android:textIsSelectable="true"
Set it to false if you wanna see your cursor
you don't have to set textCursorDrawable to #null because the color only depends on your choice of accent color
Please remove the line that has setMovementMethod from null, this will appear the cursor to change the cursor drawable you can use textCursorDrawable
So in first look at xml:
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginTop="0dp"
android:orientation="horizontal"
android:weightSum="10">
<TextView
android:id="#+id/twEventDescription"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="10dp"
android:layout_weight="10"
android:background="#drawable/title_event"
android:paddingBottom="5dp"
android:paddingEnd="5dp"
android:paddingStart="15dp"
android:paddingTop="5dp"
android:text="Short text"
android:textColor="#color/black" />
<TextView
android:id="#+id/twCountParticipants"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_marginStart="7dp"
android:layout_weight="0"
android:gravity="bottom|end"
android:text="4"
android:textColor="#color/white"
android:textSize="30sp" />
<TextView
android:id="#+id/twMaxParticipants"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_weight="0"
android:gravity="bottom|end"
android:maxLines="1"
android:text="/5"
android:textColor="#color/white"
android:textSize="15sp" />
</LinearLayout>
How you can see I have horizontal LinearLayout and few TextViews. When the first textView in yellow cloud has long text it's look pretty fine.
good
But if text in this TextView is short.. in this way all isn't good
bad
So how I make background of this TextView wrap text correctly?
P.S. I tried do it with relative layout, but it doesn't help
Try something like that:
<RelativeLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_toLeftOf="#+id/twCountParticipants"
android:layout_toStartOf="#+id/twCountParticipants">
<TextView
android:id="#+id/twEventDescription"
tools:text="bla bla bla bla bla bla bla "
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
</LinearLayout>
<TextView
android:id="#+id/twCountParticipants"
tools:text="1/"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_toLeftOf="#+id/twMaxParticipants"
android:layout_toStartOf="#+id/twMaxParticipants"/>
<TextView
android:id="#+id/twMaxParticipants"
tools:text="4"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentRight="true"
android:layout_alignParentEnd="true"/>
</RelativeLayout>
For this, you need an auto resizable TextView,
<com.yourapp.AutoResizeTextView
android:id="#+id/twEventDescription"
tools:text="bla bla bla bla bla bla bla "
android:layout_width="wrap_content"
android:layout_height="wrap_content"/>
The AutoResizeTextView class,
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;
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();
}
}
}
Background
After a lot of searching for the best solution of auto-resizing TextView (according to content, size, min&max lines, and font-size restrictions), I've made a merged solution for it all, here.
NOTE: I don't use other solutions because they don't work well, each has its own issues (something isn't supported, text goes outside of TextView, text get truncated,...) .
Demonstration of it works:
https://raw.githubusercontent.com/AndroidDeveloperLB/AutoFitTextView/master/animationPreview.gif
The problem
On some cases, the last character of one line wraps to the next line, as such:
Green is the boundaries of the TextView, red is outside of it.
The code
Basically, given the size of the TextView, its min&max font size and min&max lines, and the content (text) that's supposed to be within, it finds (using binary search) what font size should fit within the boundaries of the TextView.
The code is available in Github already, but here it is just in case :
public class AutoResizeTextView extends AppCompatTextView {
private static final int NO_LINE_LIMIT = -1;
private final RectF _availableSpaceRect = new RectF();
private final SizeTester _sizeTester;
private float _maxTextSize, _spacingMult = 1.0f, _spacingAdd = 0.0f, _minTextSize;
private int _widthLimit, _maxLines;
private boolean _initialized = false;
private TextPaint _paint;
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
*/
int onTestSize(int suggestedSize, RectF availableSpace);
}
public AutoResizeTextView(final Context context) {
this(context, null, android.R.attr.textViewStyle);
}
public AutoResizeTextView(final Context context, final AttributeSet attrs) {
this(context, attrs, android.R.attr.textViewStyle);
}
public AutoResizeTextView(final Context context, final AttributeSet attrs, final int defStyle) {
super(context, attrs, defStyle);
// using the minimal recommended font size
_minTextSize = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics());
_maxTextSize = getTextSize();
_paint = new TextPaint(getPaint());
if (_maxLines == 0)
// no value was assigned during construction
_maxLines = NO_LINE_LIMIT;
// prepare size tester:
_sizeTester = new SizeTester() {
final RectF textRect = new RectF();
#TargetApi(Build.VERSION_CODES.JELLY_BEAN)
#Override
public int onTestSize(final int suggestedSize, final RectF availableSPace) {
_paint.setTextSize(suggestedSize);
final TransformationMethod transformationMethod = getTransformationMethod();
final String text;
if (transformationMethod != null)
text = transformationMethod.getTransformation(getText(), AutoResizeTextView.this).toString();
else
text = getText().toString();
final boolean singleLine = getMaxLines() == 1;
if (singleLine) {
textRect.bottom = _paint.getFontSpacing();
textRect.right = _paint.measureText(text);
} else {
final StaticLayout layout = new StaticLayout(text, _paint, _widthLimit, Alignment.ALIGN_NORMAL, _spacingMult, _spacingAdd, true);
// return early if we have more lines
if (getMaxLines() != NO_LINE_LIMIT && layout.getLineCount() > getMaxLines())
return 1;
textRect.bottom = layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++)
if (maxWidth < layout.getLineRight(i) - layout.getLineLeft(i))
maxWidth = (int) layout.getLineRight(i) - (int) layout.getLineLeft(i);
textRect.right = maxWidth;
}
textRect.offsetTo(0, 0);
if (availableSPace.contains(textRect))
// may be too small, don't worry we will find the best match
return -1;
// else, too big
return 1;
}
};
_initialized = true;
}
#Override
public void setAllCaps(boolean allCaps) {
super.setAllCaps(allCaps);
adjustTextSize();
}
#Override
public void setTypeface(final Typeface tf) {
super.setTypeface(tf);
adjustTextSize();
}
#Override
public void setTextSize(final float size) {
_maxTextSize = size;
adjustTextSize();
}
#Override
public void setMaxLines(final int maxlines) {
super.setMaxLines(maxlines);
_maxLines = maxlines;
adjustTextSize();
}
#Override
public int getMaxLines() {
return _maxLines;
}
#Override
public void setSingleLine() {
super.setSingleLine();
_maxLines = 1;
adjustTextSize();
}
#Override
public void setSingleLine(final boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine)
_maxLines = 1;
else _maxLines = NO_LINE_LIMIT;
adjustTextSize();
}
#Override
public void setLines(final int lines) {
super.setLines(lines);
_maxLines = lines;
adjustTextSize();
}
#Override
public void setTextSize(final int unit, final float size) {
final Context c = getContext();
Resources r;
if (c == null)
r = Resources.getSystem();
else r = c.getResources();
_maxTextSize = TypedValue.applyDimension(unit, size, r.getDisplayMetrics());
adjustTextSize();
}
#Override
public void setLineSpacing(final float add, final float mult) {
super.setLineSpacing(add, mult);
_spacingMult = mult;
_spacingAdd = add;
}
/**
* Set the lower text size limit and invalidate the view
*
* #param minTextSize
*/
public void setMinTextSize(final float minTextSize) {
_minTextSize = minTextSize;
adjustTextSize();
}
private void adjustTextSize() {
// This is a workaround for truncated text issue on ListView, as shown here: https://github.com/AndroidDeveloperLB/AutoFitTextView/pull/14
// TODO think of a nicer, elegant solution.
// post(new Runnable()
// {
// #Override
// public void run()
// {
if (!_initialized)
return;
final int startSize = (int) _minTextSize;
final int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop();
_widthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight();
if (_widthLimit <= 0)
return;
_paint = new TextPaint(getPaint());
_availableSpaceRect.right = _widthLimit;
_availableSpaceRect.bottom = heightLimit;
superSetTextSize(startSize);
// }
// });
}
private void superSetTextSize(int startSize) {
int textSize = binarySearch(startSize, (int) _maxTextSize, _sizeTester, _availableSpaceRect);
super.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize);
}
private int binarySearch(final int start, final int end, final SizeTester sizeTester, final RectF availableSpace) {
int lastBest = start, lo = start, hi = end - 1, mid;
while (lo <= hi) {
mid = lo + hi >>> 1;
final 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);
adjustTextSize();
}
#Override
protected void onSizeChanged(final int width, final int height, final int oldwidth, final int oldheight) {
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight)
adjustTextSize();
}
}
The question
Why does it occur? What can I do to fix this?
Seems it's possible using support library:
<TextView
android:layout_width="250dp" android:layout_height="wrap_content" android:background="#f00"
android:breakStrategy="balanced" android:hyphenationFrequency="none"
android:text="This is an example text" android:textSize="30dp" app:autoSizeTextType="uniform"/>
Sadly, it has 2 disadvantages:
Doesn't always spread words nicely. Reported here.
Requires Android API 23 and above (here).
More information here.
I had similar problem in my project. Long-time Googling, StackOverflow (with your question among others). Nothing.
And my final "solution" was BreakIterator for words + measure them all to check this situation.
UPDATE (2018-08-10):
static public boolean isTextFitWidth(final #Nullable String source, final #NonNull BreakIterator bi, final #NonNull TextPaint paint, final int width, final float textSize)
{
if (null == source || source.length() <= 0) {
return true;
}
TextPaint paintCopy = new TextPaint();
paintCopy.set(paint);
paintCopy.setTextSize(textSize);
bi.setText(source);
int start = bi.first();
for (int end = bi.next(); BreakIterator.DONE != end; start = end, end = bi.next()) {
int wordWidth = (int)Math.ceil(paintCopy.measureText(source, start, end));
if (wordWidth > width) {
return false;
}
}
return true;
}
static public boolean isTextFitWidth(final #NonNull TextView textView, final #NonNull BreakIterator bi, final int width, final #Nullable Float textSize)
{
final int textWidth = width - textView.getPaddingLeft() - textView.getPaddingRight();
return isTextFitWidth(textView.getText().toString(), bi, textView.getPaint(), textWidth,
null != textSize ? textSize : textView.getTextSize());
}
I have created a class to auto resize TextView here is the code:
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;
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 = 5;
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();
}
}
}
As you can see in this class I have set the minimum text size to 5. The resizing works perfectly when I type in the text in my xml file. However when I try to change the text during the activity after grabbing the text from a network connection, I change the text in my onPostExecute() method, however when the text loads on screen it appears really small (i'm assuming it is appearing as the minimum size 5 text I set in the class). I don't understand why it is not getting resized bigger since it fits. In my xml file I set the textSize to 12 (which means 12 would be the max size). I have even tried changing the text size manually in the activity but this does not work either, the text still displays as size 5. Here is my code:
import android.app.Activity;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.LinearLayout;
import android.widget.ProgressBar;
import android.widget.TextView;
import org.apache.http.HttpEntity;
import org.apache.http.HttpResponse;
import org.apache.http.client.HttpClient;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.impl.client.DefaultHttpClient;
import org.json.JSONArray;
import org.json.JSONObject;
import org.w3c.dom.Text;
import java.io.BufferedReader;
import java.io.InputStream;
import java.io.InputStreamReader;
public class ConfirmBookBuy extends Activity
{
Globals g;
LinearLayout mainLinearLayout;
ProgressBar loadingWheel;
TextView titleInfo;
TextView authorInfo;
TextView editionInfo;
TextView publisherInfo;
TextView datePublishedInfo;
TextView bindingInfo;
#Override
protected void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.buy_confirm_book_layout);
g = Globals.getInstance();
mainLinearLayout = (LinearLayout)findViewById(R.id.mainLinearLayout);
loadingWheel = (ProgressBar)findViewById(R.id.loadingWheel);
titleInfo = (TextView)findViewById(R.id.titleInfo);
authorInfo = (TextView)findViewById(R.id.authorInfo);
editionInfo = (TextView)findViewById(R.id.editionInfo);
publisherInfo = (TextView)findViewById(R.id.publisherInfo);
datePublishedInfo = (TextView)findViewById(R.id.datePublishedInfo);
bindingInfo = (TextView)findViewById(R.id.bindingInfo);
new ConfirmIsbn().execute();
}
public class ConfirmIsbn extends AsyncTask<String, Void, Void>
{
#Override
protected Void doInBackground(String... params) {
// grabbing the text from internet, this works, I have tested to see that it is actually grabbing the text
return null;
}
#Override
protected void onPostExecute(Void aVoid)
{
super.onPostExecute(aVoid);
titleInfo.setText(g.getBuyingTitle());
authorInfo.setText(g.getBuyingAuthor());
editionInfo.setText(g.getBuyingEdition());
publisherInfo.setText(g.getBuyingPublisher());
datePublishedInfo.setText(g.getBuyingDatePublished());
bindingInfo.setText(g.getBuyingBinding());
titleInfo.setTextSize(12);
authorInfo.setTextSize(12);
editionInfo.setTextSize(12);
publisherInfo.setTextSize(12);
datePublishedInfo.setTextSize(12);
bindingInfo.setTextSize(12);
}
}
}
I just want my text view to auto resize to the biggest text size that will fit (max of 12). I don't get why when I set the text in xml the resizing works perfectly, however when I try to set it during the running of activity it will always just display the minimum text of size 5... even if I try to manually set the text in code too.