Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- /**
- * A {@link TextView} that re-sizes its text to be no larger than the width of the view.
- *
- * @attr ref R.styleable.AutofitTextView_sizeToFit
- * @attr ref R.styleable.AutofitTextView_minTextSize
- * @attr ref R.styleable.AutofitTextView_precision
- */
- public class AutofitTextView extends AppCompatTextView implements AutofitHelper.OnTextSizeChangeListener {
- private AutofitHelper mHelper;
- public AutofitTextView(Context context) {
- super(context);
- init(context, null, 0);
- }
- public AutofitTextView(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs, 0);
- }
- public AutofitTextView(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs, defStyle);
- }
- private void init(Context context, AttributeSet attrs, int defStyle) {
- mHelper = AutofitHelper.create(this, attrs, defStyle)
- .addOnTextSizeChangeListener(this);
- }
- // Getters and Setters
- /**
- * {@inheritDoc}
- */
- @Override
- public void setTextSize(int unit, float size) {
- super.setTextSize(unit, size);
- if (mHelper != null) {
- mHelper.setTextSize(unit, size);
- }
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public void setLines(int lines) {
- super.setLines(lines);
- if (mHelper != null) {
- mHelper.setMaxLines(lines);
- }
- }
- /**
- * {@inheritDoc}
- */
- @Override
- public void setMaxLines(int maxLines) {
- super.setMaxLines(maxLines);
- if (mHelper != null) {
- mHelper.setMaxLines(maxLines);
- }
- }
- /**
- * Returns the {@link AutofitHelper} for this View.
- */
- public AutofitHelper getAutofitHelper() {
- return mHelper;
- }
- /**
- * Returns whether or not the text will be automatically re-sized to fit its constraints.
- */
- public boolean isSizeToFit() {
- return mHelper.isEnabled();
- }
- /**
- * Sets the property of this field (sizeToFit), to automatically resize the text to fit its
- * constraints.
- */
- public void setSizeToFit() {
- setSizeToFit(true);
- }
- /**
- * If true, the text will automatically be re-sized to fit its constraints; if false, it will
- * act like a normal TextView.
- *
- * @param sizeToFit
- */
- public void setSizeToFit(boolean sizeToFit) {
- mHelper.setEnabled(sizeToFit);
- }
- /**
- * Returns the maximum size (in pixels) of the text in this View.
- */
- public float getMaxTextSize() {
- return mHelper.getMaxTextSize();
- }
- /**
- * Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size
- * is adjusted based on the current density and user font size preference.
- *
- * @param size The scaled pixel size.
- *
- * @attr ref android.R.styleable#TextView_textSize
- */
- public void setMaxTextSize(float size) {
- mHelper.setMaxTextSize(size);
- }
- /**
- * Set the maximum text size to a given unit and value. See TypedValue for the possible
- * dimension units.
- *
- * @param unit The desired dimension unit.
- * @param size The desired size in the given units.
- *
- * @attr ref android.R.styleable#TextView_textSize
- */
- public void setMaxTextSize(int unit, float size) {
- mHelper.setMaxTextSize(unit, size);
- }
- /**
- * Returns the minimum size (in pixels) of the text in this View.
- */
- public float getMinTextSize() {
- return mHelper.getMinTextSize();
- }
- /**
- * Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
- * is adjusted based on the current density and user font size preference.
- *
- * @param minSize The scaled pixel size.
- *
- * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
- */
- public void setMinTextSize(int minSize) {
- mHelper.setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize);
- }
- /**
- * Set the minimum text size to a given unit and value. See TypedValue for the possible
- * dimension units.
- *
- * @param unit The desired dimension unit.
- * @param minSize The desired size in the given units.
- *
- * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
- */
- public void setMinTextSize(int unit, float minSize) {
- mHelper.setMinTextSize(unit, minSize);
- }
- /**
- * Returns the amount of precision used to calculate the correct text size to fit within its
- * bounds.
- */
- public float getPrecision() {
- return mHelper.getPrecision();
- }
- /**
- * Set the amount of precision used to calculate the correct text size to fit within its
- * bounds. Lower precision is more precise and takes more time.
- *
- * @param precision The amount of precision.
- */
- public void setPrecision(float precision) {
- mHelper.setPrecision(precision);
- }
- @Override
- public void onTextSizeChange(float textSize, float oldTextSize) {
- // do nothing
- }
- }
- /**
- * A {@link ViewGroup} that re-sizes the text of it's children to be no larger than the width of the
- * view.
- *
- * @attr ref R.styleable.AutofitTextView_sizeToFit
- * @attr ref R.styleable.AutofitTextView_minTextSize
- * @attr ref R.styleable.AutofitTextView_precision
- */
- public class AutofitLayout extends FrameLayout {
- private boolean mEnabled;
- private float mMinTextSize;
- private float mPrecision;
- private WeakHashMap<View, AutofitHelper> mHelpers = new WeakHashMap<View, AutofitHelper>();
- public AutofitLayout(Context context) {
- super(context);
- init(context, null, 0);
- }
- public AutofitLayout(Context context, AttributeSet attrs) {
- super(context, attrs);
- init(context, attrs, 0);
- }
- public AutofitLayout(Context context, AttributeSet attrs, int defStyle) {
- super(context, attrs, defStyle);
- init(context, attrs, defStyle);
- }
- private void init(Context context, AttributeSet attrs, int defStyle) {
- boolean sizeToFit = true;
- int minTextSize = -1;
- float precision = -1;
- if (attrs != null) {
- TypedArray ta = context.obtainStyledAttributes(
- attrs,
- R.styleable.AutofitTextView,
- defStyle,
- 0);
- sizeToFit = ta.getBoolean(R.styleable.AutofitTextView_sizeToFit, sizeToFit);
- minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitTextView_minTextSize,
- minTextSize);
- precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision);
- ta.recycle();
- }
- mEnabled = sizeToFit;
- mMinTextSize = minTextSize;
- mPrecision = precision;
- }
- @Override
- public void addView(View child, int index, ViewGroup.LayoutParams params) {
- super.addView(child, index, params);
- TextView textView = (TextView) child;
- AutofitHelper helper = AutofitHelper.create(textView)
- .setEnabled(mEnabled);
- if (mPrecision > 0) {
- helper.setPrecision(mPrecision);
- }
- if (mMinTextSize > 0) {
- helper.setMinTextSize(TypedValue.COMPLEX_UNIT_PX, mMinTextSize);
- }
- mHelpers.put(textView, helper);
- }
- /**
- * Returns the {@link AutofitHelper} for this child View.
- */
- public AutofitHelper getAutofitHelper(TextView textView) {
- return mHelpers.get(textView);
- }
- /**
- * Returns the {@link AutofitHelper} for this child View.
- */
- public AutofitHelper getAutofitHelper(int index) {
- return mHelpers.get(getChildAt(index));
- }
- }
- /**
- * A helper class to enable automatically resizing {@link TextView}`s {@code textSize} to fit
- * within its bounds.
- *
- * @attr ref R.styleable.AutofitTextView_sizeToFit
- * @attr ref R.styleable.AutofitTextView_minTextSize
- * @attr ref R.styleable.AutofitTextView_precision
- */
- public class AutofitHelper {
- private static final int TAG_KEY_ON_LAYOUT_CHANGED_LISTENER = 0x31;
- private static final String TAG = "AutoFitTextHelper";
- private static final boolean SPEW = false;
- // Minimum size of the text in pixels
- private static final int DEFAULT_MIN_TEXT_SIZE = 8; //sp
- // How precise we want to be when reaching the target textWidth size
- private static final float DEFAULT_PRECISION = 0.5f;
- /**
- * Creates a new instance of {@code AutofitHelper} that wraps a {@link TextView} and enables
- * automatically sizing the text to fit.
- */
- public static AutofitHelper create(TextView view) {
- return create(view, null, 0);
- }
- /**
- * Creates a new instance of {@code AutofitHelper} that wraps a {@link TextView} and enables
- * automatically sizing the text to fit.
- */
- public static AutofitHelper create(TextView view, AttributeSet attrs) {
- return create(view, attrs, 0);
- }
- /**
- * Creates a new instance of {@code AutofitHelper} that wraps a {@link TextView} and enables
- * automatically sizing the text to fit.
- */
- public static AutofitHelper create(TextView view, AttributeSet attrs, int defStyle) {
- AutofitHelper helper = new AutofitHelper(view);
- boolean sizeToFit = true;
- if (attrs != null) {
- Context context = view.getContext();
- int minTextSize = (int) helper.getMinTextSize();
- float precision = helper.getPrecision();
- TypedArray ta = context.obtainStyledAttributes(
- attrs,
- R.styleable.AutofitTextView,
- defStyle,
- 0);
- sizeToFit = ta.getBoolean(R.styleable.AutofitTextView_sizeToFit, sizeToFit);
- minTextSize = ta.getDimensionPixelSize(R.styleable.AutofitTextView_minTextSize,
- minTextSize);
- precision = ta.getFloat(R.styleable.AutofitTextView_precision, precision);
- ta.recycle();
- helper.setMinTextSize(TypedValue.COMPLEX_UNIT_PX, minTextSize)
- .setPrecision(precision);
- }
- if (Build.VERSION.SDK_INT > Build.VERSION_CODES.HONEYCOMB) {
- helper.setEnabled(sizeToFit);
- }
- return helper;
- }
- /**
- * Re-sizes the textSize of the TextView so that the text fits within the bounds of the View.
- */
- private static void autofit(TextView view, TextPaint paint, float minTextSize, float maxTextSize,
- int maxLines, float precision) {
- if (maxLines <= 0 || maxLines == Integer.MAX_VALUE) {
- // Don't auto-size since there's no limit on lines.
- return;
- }
- int targetWidth = view.getWidth() - view.getCompoundPaddingLeft() - view.getCompoundPaddingRight();
- if (targetWidth <= 0) {
- return;
- }
- CharSequence text = view.getText();
- TransformationMethod method = view.getTransformationMethod();
- if (method != null) {
- text = method.getTransformation(text, view);
- }
- Context context = view.getContext();
- Resources r = Resources.getSystem();
- DisplayMetrics displayMetrics;
- float size = maxTextSize;
- float high = size;
- float low = 0;
- if (context != null) {
- r = context.getResources();
- }
- displayMetrics = r.getDisplayMetrics();
- paint.set(view.getPaint());
- paint.setTextSize(size);
- if ((maxLines == 1 && paint.measureText(text, 0, text.length()) > targetWidth)
- || getLineCount(text, paint, size, targetWidth, displayMetrics) > maxLines) {
- size = getAutofitTextSize(text, paint, targetWidth, maxLines, low, high, precision,
- displayMetrics);
- }
- if (size < minTextSize) {
- size = minTextSize;
- }
- view.setTextSize(TypedValue.COMPLEX_UNIT_PX, size);
- }
- /**
- * Recursive binary search to find the best size for the text.
- */
- private static float getAutofitTextSize(CharSequence text, TextPaint paint,
- float targetWidth, int maxLines, float low, float high, float precision,
- DisplayMetrics displayMetrics) {
- float mid = (low + high) / 2.0f;
- int lineCount = 1;
- StaticLayout layout = null;
- paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, mid,
- displayMetrics));
- if (maxLines != 1) {
- layout = new StaticLayout(text, paint, (int)targetWidth, Layout.Alignment.ALIGN_NORMAL,
- 1.0f, 0.0f, true);
- lineCount = layout.getLineCount();
- }
- if (SPEW) Log.d(TAG, "low=" + low + " high=" + high + " mid=" + mid +
- " target=" + targetWidth + " maxLines=" + maxLines + " lineCount=" + lineCount);
- if (lineCount > maxLines) {
- // For the case that `text` has more newline characters than `maxLines`.
- if ((high - low) < precision) {
- return low;
- }
- return getAutofitTextSize(text, paint, targetWidth, maxLines, low, mid, precision,
- displayMetrics);
- }
- else if (lineCount < maxLines) {
- return getAutofitTextSize(text, paint, targetWidth, maxLines, mid, high, precision,
- displayMetrics);
- }
- else {
- float maxLineWidth = 0;
- if (maxLines == 1) {
- maxLineWidth = paint.measureText(text, 0, text.length());
- } else {
- for (int i = 0; i < lineCount; i++) {
- if (layout.getLineWidth(i) > maxLineWidth) {
- maxLineWidth = layout.getLineWidth(i);
- }
- }
- }
- if ((high - low) < precision) {
- return low;
- } else if (maxLineWidth > targetWidth) {
- return getAutofitTextSize(text, paint, targetWidth, maxLines, low, mid, precision,
- displayMetrics);
- } else if (maxLineWidth < targetWidth) {
- return getAutofitTextSize(text, paint, targetWidth, maxLines, mid, high, precision,
- displayMetrics);
- } else {
- return mid;
- }
- }
- }
- private static int getLineCount(CharSequence text, TextPaint paint, float size, float width,
- DisplayMetrics displayMetrics) {
- paint.setTextSize(TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_PX, size,
- displayMetrics));
- StaticLayout layout = new StaticLayout(text, paint, (int)width,
- Layout.Alignment.ALIGN_NORMAL, 1.0f, 0.0f, true);
- return layout.getLineCount();
- }
- private static int getMaxLines(TextView view) {
- int maxLines = -1; // No limit (Integer.MAX_VALUE also means no limit)
- TransformationMethod method = view.getTransformationMethod();
- if (method != null && method instanceof SingleLineTransformationMethod) {
- maxLines = 1;
- }
- else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) {
- // setMaxLines() and getMaxLines() are only available on android-16+
- maxLines = view.getMaxLines();
- }
- return maxLines;
- }
- // Attributes
- private TextView mTextView;
- private TextPaint mPaint;
- /**
- * Original textSize of the TextView.
- */
- private float mTextSize;
- private int mMaxLines;
- private float mMinTextSize;
- private float mMaxTextSize;
- private float mPrecision;
- private boolean mEnabled;
- private boolean mIsAutofitting;
- private ArrayList<OnTextSizeChangeListener> mListeners;
- private TextWatcher mTextWatcher = new AutofitTextWatcher();
- private AutofitHelper(TextView view) {
- final Context context = view.getContext();
- float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
- mTextView = view;
- mTextView.setSingleLine(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB);
- mPaint = new TextPaint();
- setRawTextSize(view.getTextSize());
- mMaxLines = getMaxLines(view);
- mMinTextSize = scaledDensity * DEFAULT_MIN_TEXT_SIZE;
- mMaxTextSize = mTextSize;
- mPrecision = DEFAULT_PRECISION;
- }
- /**
- * Adds an {@link OnTextSizeChangeListener} to the list of those whose methods are called
- * whenever the {@link TextView}'s {@code textSize} changes.
- */
- public AutofitHelper addOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
- if (mListeners == null) {
- mListeners = new ArrayList<OnTextSizeChangeListener>();
- }
- mListeners.add(listener);
- return this;
- }
- /**
- * Removes the specified {@link OnTextSizeChangeListener} from the list of those whose methods
- * are called whenever the {@link TextView}'s {@code textSize} changes.
- */
- public AutofitHelper removeOnTextSizeChangeListener(OnTextSizeChangeListener listener) {
- if (mListeners != null) {
- mListeners.remove(listener);
- }
- return this;
- }
- /**
- * Returns the amount of precision used to calculate the correct text size to fit within its
- * bounds.
- */
- public float getPrecision() {
- return mPrecision;
- }
- /**
- * Set the amount of precision used to calculate the correct text size to fit within its
- * bounds. Lower precision is more precise and takes more time.
- *
- * @param precision The amount of precision.
- */
- public AutofitHelper setPrecision(float precision) {
- if (mPrecision != precision) {
- mPrecision = precision;
- autofit();
- }
- return this;
- }
- /**
- * Returns the minimum size (in pixels) of the text.
- */
- public float getMinTextSize() {
- return mMinTextSize;
- }
- /**
- * Set the minimum text size to the given value, interpreted as "scaled pixel" units. This size
- * is adjusted based on the current density and user font size preference.
- *
- * @param size The scaled pixel size.
- *
- * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
- */
- public AutofitHelper setMinTextSize(float size) {
- return setMinTextSize(TypedValue.COMPLEX_UNIT_SP, size);
- }
- /**
- * Set the minimum text size to a given unit and value. See TypedValue for the possible
- * dimension units.
- *
- * @param unit The desired dimension unit.
- * @param size The desired size in the given units.
- *
- * @attr ref me.grantland.R.styleable#AutofitTextView_minTextSize
- */
- public AutofitHelper setMinTextSize(int unit, float size) {
- Context context = mTextView.getContext();
- Resources r = Resources.getSystem();
- if (context != null) {
- r = context.getResources();
- }
- setRawMinTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
- return this;
- }
- private void setRawMinTextSize(float size) {
- if (size != mMinTextSize) {
- mMinTextSize = size;
- autofit();
- }
- }
- /**
- * Returns the maximum size (in pixels) of the text.
- */
- public float getMaxTextSize() {
- return mMaxTextSize;
- }
- /**
- * Set the maximum text size to the given value, interpreted as "scaled pixel" units. This size
- * is adjusted based on the current density and user font size preference.
- *
- * @param size The scaled pixel size.
- *
- * @attr ref android.R.styleable#TextView_textSize
- */
- public AutofitHelper setMaxTextSize(float size) {
- return setMaxTextSize(TypedValue.COMPLEX_UNIT_SP, size);
- }
- /**
- * Set the maximum text size to a given unit and value. See TypedValue for the possible
- * dimension units.
- *
- * @param unit The desired dimension unit.
- * @param size The desired size in the given units.
- *
- * @attr ref android.R.styleable#TextView_textSize
- */
- public AutofitHelper setMaxTextSize(int unit, float size) {
- Context context = mTextView.getContext();
- Resources r = Resources.getSystem();
- if (context != null) {
- r = context.getResources();
- }
- setRawMaxTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
- return this;
- }
- private void setRawMaxTextSize(float size) {
- if (size != mMaxTextSize) {
- mMaxTextSize = size;
- autofit();
- }
- }
- /**
- * @see TextView#getMaxLines()
- */
- public int getMaxLines() {
- return mMaxLines;
- }
- /**
- * @see TextView#setMaxLines(int)
- */
- public AutofitHelper setMaxLines(int lines) {
- if (mMaxLines != lines) {
- mMaxLines = lines;
- autofit();
- }
- return this;
- }
- /**
- * Returns whether or not automatically resizing text is enabled.
- */
- public boolean isEnabled() {
- return mEnabled;
- }
- /**
- * Set the enabled state of automatically resizing text.
- */
- @TargetApi(11)
- public AutofitHelper setEnabled(boolean enabled) {
- if (mEnabled != enabled) {
- mEnabled = enabled;
- if (enabled) {
- mTextView.addTextChangedListener(mTextWatcher);
- View.OnLayoutChangeListener onLayoutChangeListener = new View.OnLayoutChangeListener() {
- @Override
- public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft,
- int oldTop, int oldRight, int oldBottom) {
- autofit();
- }
- };
- mTextView.addOnLayoutChangeListener(onLayoutChangeListener);
- mTextView.setTag(mTextView.getId(), onLayoutChangeListener);
- autofit();
- } else {
- mTextView.removeTextChangedListener(mTextWatcher);
- Object onLayoutChangeListener = mTextView.getTag(mTextView.getId());
- if (onLayoutChangeListener instanceof View.OnLayoutChangeListener) {
- mTextView.removeOnLayoutChangeListener((View.OnLayoutChangeListener) onLayoutChangeListener);
- }
- mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, mTextSize);
- }
- }
- return this;
- }
- /**
- * Returns the original text size of the View.
- *
- * @see TextView#getTextSize()
- */
- public float getTextSize() {
- return mTextSize;
- }
- /**
- * Set the original text size of the View.
- *
- * @see TextView#setTextSize(float)
- */
- public void setTextSize(float size) {
- setTextSize(TypedValue.COMPLEX_UNIT_SP, size);
- }
- /**
- * Set the original text size of the View.
- *
- * @see TextView#setTextSize(int, float)
- */
- public void setTextSize(int unit, float size) {
- if (mIsAutofitting) {
- // We don't want to update the TextView's actual textSize while we're autofitting
- // since it'd get set to the autofitTextSize
- return;
- }
- Context context = mTextView.getContext();
- Resources r = Resources.getSystem();
- if (context != null) {
- r = context.getResources();
- }
- setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics()));
- }
- private void setRawTextSize(float size) {
- if (mTextSize != size) {
- mTextSize = size;
- }
- }
- private void autofit() {
- float oldTextSize = mTextView.getTextSize();
- float textSize;
- mIsAutofitting = true;
- autofit(mTextView, mPaint, mMinTextSize, mMaxTextSize, mMaxLines, mPrecision);
- mIsAutofitting = false;
- textSize = mTextView.getTextSize();
- if (textSize != oldTextSize) {
- sendTextSizeChange(textSize, oldTextSize);
- }
- }
- private void sendTextSizeChange(float textSize, float oldTextSize) {
- if (mListeners == null) {
- return;
- }
- for (OnTextSizeChangeListener listener : mListeners) {
- listener.onTextSizeChange(textSize, oldTextSize);
- }
- }
- private class AutofitTextWatcher implements TextWatcher {
- @Override
- public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
- // do nothing
- }
- @Override
- public void onTextChanged(CharSequence charSequence, int start, int before, int count) {
- autofit();
- }
- @Override
- public void afterTextChanged(Editable editable) {
- // do nothing
- }
- }
- /**
- * When an object of a type is attached to an {@code AutofitHelper}, its methods will be called
- * when the {@code textSize} is changed.
- */
- public interface OnTextSizeChangeListener {
- /**
- * This method is called to notify you that the size of the text has changed to
- * {@code textSize} from {@code oldTextSize}.
- */
- public void onTextSizeChange(float textSize, float oldTextSize);
- }
- }
- <declare-styleable name="AutofitTextView">
- <!-- Minimum size of the text. -->
- <attr name="minTextSize" format="dimension" />
- <!-- Amount of precision used to calculate the correct text size to fit within its
- bounds. Lower precision is more precise and takes more time. -->
- <attr name="precision" format="float" />
- <!-- Defines whether to automatically resize text to fit to the view's bounds. -->
- <attr name="sizeToFit" format="boolean" />
- </declare-styleable
Add Comment
Please, Sign In to add comment