Created
April 1, 2016 14:29
-
-
Save rafaelneiva/31e17a7a82278a711d599e3ef74b3d25 to your computer and use it in GitHub Desktop.
AutoFit TextView
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
import android.annotation.TargetApi; | |
import android.content.Context; | |
import android.content.res.Resources; | |
import android.content.res.TypedArray; | |
import android.graphics.RectF; | |
import android.os.Build; | |
import android.text.Layout; | |
import android.text.StaticLayout; | |
import android.text.TextPaint; | |
import android.util.AttributeSet; | |
import android.util.SparseIntArray; | |
import android.util.TypedValue; | |
import android.widget.TextView; | |
import br.com.zup.naturasemprepresente.R; | |
import br.com.zup.naturasemprepresente.util.Utils; | |
/** | |
* Created by rafaelneiva on 09/12/15. | |
*/ | |
public class AutoFitTextView extends TextView { | |
private static final int NO_LINE_LIMIT = -1; | |
private final RectF _availableSpaceRect = new RectF(); | |
private final SparseIntArray _textCachedSizes = new SparseIntArray(); | |
private 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; | |
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); | |
} | |
public AutoFitTextView(Context context) { | |
this(context, null, 0); | |
} | |
public AutoFitTextView(Context context, AttributeSet attrs) { | |
this(context, attrs, 0); | |
} | |
public AutoFitTextView(final Context context, final AttributeSet attrs, final int defStyleAttr) { | |
super(context, attrs, defStyleAttr); | |
if (paint == null) | |
paint = new TextPaint(getPaint()); | |
paint.setTypeface(this.getTypeface()); | |
adjustTextSize(); | |
// 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 | |
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; | |
} | |
}; | |
_initiallized = true; | |
} | |
@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 minTextSize | |
*/ | |
public void setMinTextSize(final float minTextSize) { | |
_minTextSize = minTextSize; | |
reAdjust(); | |
} | |
public void reAdjust() { | |
adjustTextSize(); | |
} | |
private void adjustTextSize() { | |
if (!_initiallized) | |
return; | |
final int startSize = (int) _minTextSize; | |
final int heightLimit = getMeasuredHeight() - getCompoundPaddingBottom() - getCompoundPaddingTop(); | |
_widthLimit = getMeasuredWidth() - getCompoundPaddingLeft() - getCompoundPaddingRight(); | |
if (_widthLimit <= 0) | |
return; | |
_availableSpaceRect.right = _widthLimit; | |
_availableSpaceRect.bottom = heightLimit; | |
superSetTextSize(startSize); | |
} | |
private void superSetTextSize(int startSize) { | |
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(); | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment