Skip to content

Instantly share code, notes, and snippets.

@arthtilva
Created October 25, 2018 03:55
Show Gist options
  • Save arthtilva/c1014c0249567efb065c144a60281d5f to your computer and use it in GitHub Desktop.
Save arthtilva/c1014c0249567efb065c144a60281d5f to your computer and use it in GitHub Desktop.
package com.mfinity.doodlephotoeditor.customview;
import android.annotation.TargetApi;
import android.content.Context;
import android.content.res.Resources;
import android.graphics.RectF;
import android.graphics.Typeface;
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;
/**
* Text view that auto adjusts text size to fit within the view.
* If the text size equals the minimum text size and still does not
* fit, append with an ellipsis.
*
* @link http://stackoverflow.com/questions/5033012/auto-scale-textview-text-to-fit-within-bounds/17782522#17782522
*/
public class AutoResizeTextView extends MagicTextView {
private String fontName = "";
public String getFontName() {
return fontName;
}
public void setFontName(String fontName) {
this.fontName = fontName;
}
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 Typeface mTypeFace;
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());
if (mTypeFace != null) {
mPaint.setTypeface(mTypeFace);
}
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();
}
public 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();
if (mWidthLimit < 0) {
mWidthLimit = 0;
}
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 {
if (mTextRect.bottom < availableSPace.bottom && mTextRect.right > availableSPace.right) {
// hack :O
return -1;
}
// 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();
}
}
public void setmTypeFace(Context context, String fontName) {
this.fontName = fontName;
Typeface tf = Typeface.createFromAsset(context.getAssets(), fontName);
setTypeface(tf);
}
@Override
public void setTypeface(Typeface tf) {
super.setTypeface(tf);
mTypeFace = tf;
if (mInitiallized) {
mPaint.setTypeface(tf);
mTextCachedSizes.clear();
reAdjust();
}
}
}
/*
public class AutoResizeTextView extends MagicTextView {
private static final int NO_LINE_LIMIT = -1;
private final RectF _availableSpaceRect;
private boolean _enableSizeCache;
private boolean _initiallized;
private int _maxLines;
private float _maxTextSize;
private float _minTextSize;
private final SizeTester _sizeTester;
private float _spacingAdd;
private float _spacingMult;
private final SparseIntArray _textCachedSizes;
private int _widthLimit;
private interface SizeTester {
int onTestSize(int i, RectF rectF);
}
public AutoResizeTextView(Context context) {
this(context, null, 0);
}
public AutoResizeTextView(Context context, AttributeSet attrs) {
this(context, attrs, 0);
}
public AutoResizeTextView(Context context, AttributeSet attrs, int defStyle) {
super(context, attrs, defStyle);
this._availableSpaceRect = new RectF();
this._textCachedSizes = new SparseIntArray();
this._spacingMult = 1.0f;
this._spacingAdd = 0.0f;
this._enableSizeCache = true;
this._initiallized = false;
this._minTextSize = TypedValue.applyDimension(2, 12.0f, getResources().getDisplayMetrics());
this._maxTextSize = getTextSize();
if (this._maxLines == 0) {
this._maxLines = -1;
}
final TextPaint paint = new TextPaint(getPaint());
this._sizeTester = new SizeTester() {
final RectF textRect = new RectF();
@TargetApi(16)
public int onTestSize(int suggestedSize, RectF availableSPace) {
paint.setTextSize((float) suggestedSize);
String text = AutoResizeTextView.this.getText().toString();
if (AutoResizeTextView.this.getMaxLines() == 1) {
this.textRect.bottom = paint.getFontSpacing();
this.textRect.right = paint.measureText(text);
} else {
StaticLayout layout = new StaticLayout(text, paint, AutoResizeTextView.this._widthLimit, Alignment.ALIGN_NORMAL, AutoResizeTextView.this._spacingMult, AutoResizeTextView.this._spacingAdd, true);
if (AutoResizeTextView.this.getMaxLines() != -1 && layout.getLineCount() > AutoResizeTextView.this.getMaxLines()) {
return 1;
}
this.textRect.bottom = (float) layout.getHeight();
int maxWidth = -1;
for (int i = 0; i < layout.getLineCount(); i++) {
if (((float) maxWidth) < layout.getLineWidth(i)) {
maxWidth = (int) layout.getLineWidth(i);
}
}
this.textRect.right = (float) maxWidth;
}
this.textRect.offsetTo(0.0f, 0.0f);
if (availableSPace.contains(this.textRect)) {
return -1;
}
return 1;
}
};
this._initiallized = true;
}
public void setTextSize(float size) {
this._maxTextSize = size;
this._textCachedSizes.clear();
adjustTextSize();
}
public void setMaxLines(int maxlines) {
super.setMaxLines(maxlines);
this._maxLines = maxlines;
reAdjust();
}
public int getMaxLines() {
return this._maxLines;
}
public void setSingleLine() {
super.setSingleLine();
this._maxLines = 1;
reAdjust();
}
public void setSingleLine(boolean singleLine) {
super.setSingleLine(singleLine);
if (singleLine) {
this._maxLines = 1;
} else {
this._maxLines = -1;
}
reAdjust();
}
public void setLines(int lines) {
super.setLines(lines);
this._maxLines = lines;
reAdjust();
}
public void setTextSize(int unit, float size) {
Resources r;
Context c = getContext();
if (c == null) {
r = Resources.getSystem();
} else {
r = c.getResources();
}
this._maxTextSize = TypedValue.applyDimension(unit, size, r.getDisplayMetrics());
this._textCachedSizes.clear();
adjustTextSize();
}
public void setLineSpacing(float add, float mult) {
super.setLineSpacing(add, mult);
this._spacingMult = mult;
this._spacingAdd = add;
}
public void setMinTextSize(float minTextSize) {
this._minTextSize = minTextSize;
reAdjust();
}
public void reAdjust() {
adjustTextSize();
}
private void adjustTextSize() {
if (this._initiallized) {
int startSize = (int) this._minTextSize;
int heightLimit = (getMeasuredHeight() - getCompoundPaddingBottom()) - getCompoundPaddingTop();
this._widthLimit = (getMeasuredWidth() - getCompoundPaddingLeft()) - getCompoundPaddingRight();
this._availableSpaceRect.right = (float) this._widthLimit;
this._availableSpaceRect.bottom = (float) heightLimit;
super.setTextSize(0, (float) efficientTextSizeSearch(startSize, (int) this._maxTextSize, this._sizeTester, this._availableSpaceRect));
}
}
public void setEnableSizeCache(boolean enable) {
this._enableSizeCache = enable;
this._textCachedSizes.clear();
adjustTextSize();
}
private int efficientTextSizeSearch(int start, int end, SizeTester sizeTester, RectF availableSpace) {
if (!this._enableSizeCache) {
return binarySearch(start, end, sizeTester, availableSpace);
}
String text = getText().toString();
int key = text == null ? 0 : text.length();
int size = this._textCachedSizes.get(key);
if (size != 0) {
return size;
}
size = binarySearch(start, end, sizeTester, availableSpace);
this._textCachedSizes.put(key, size);
return size;
}
private int binarySearch(int start, int end, SizeTester sizeTester, RectF availableSpace) {
int lastBest = start;
int lo = start;
int hi = end - 1;
while (lo <= hi) {
int mid = (lo + hi) >>> 1;
int midValCmp = sizeTester.onTestSize(mid, availableSpace);
if (midValCmp < 0) {
lastBest = lo;
lo = mid + 1;
} else if (midValCmp <= 0) {
return mid;
} else {
hi = mid - 1;
lastBest = hi;
}
}
return lastBest;
}
protected void onTextChanged(CharSequence text, int start, int before, int after) {
super.onTextChanged(text, start, before, after);
reAdjust();
}
protected void onSizeChanged(int width, int height, int oldwidth, int oldheight) {
this._textCachedSizes.clear();
super.onSizeChanged(width, height, oldwidth, oldheight);
if (width != oldwidth || height != oldheight) {
reAdjust();
}
}
}
*/
@amrittrivedir
Copy link

Can you add support for letter spacing? I am not get perfect size if I adds letter spacing in the textview.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment