Android: Handling text size for different dip devices - android

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));
}

Related

Android edittext cursor not seen despite edittext.isCursorVisible() returning true

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

How to wrap text in TextView with background image

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();
}
}
}

Android TextView: Phantom Padding When Padding Set To 0

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
}

How to avoid character wrap of text in TextView, for auto-resizing text?

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());
}

How to autoresize textsize in TextView to fit available space vertically and horizontally?

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();
}
}
}
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.

Categories

Resources