Created
October 25, 2018 03:55
-
-
Save arthtilva/c1014c0249567efb065c144a60281d5f to your computer and use it in GitHub Desktop.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Can you add support for letter spacing? I am not get perfect size if I adds letter spacing in the textview.