diff options
Diffstat (limited to 'src/com/android/launcher3/AutoFitTextView.java')
-rw-r--r-- | src/com/android/launcher3/AutoFitTextView.java | 381 |
1 files changed, 381 insertions, 0 deletions
diff --git a/src/com/android/launcher3/AutoFitTextView.java b/src/com/android/launcher3/AutoFitTextView.java new file mode 100644 index 000000000..208dd4073 --- /dev/null +++ b/src/com/android/launcher3/AutoFitTextView.java @@ -0,0 +1,381 @@ +/* + * Copyright (C) 2014 Grantland Chew + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.android.launcher3; + +import android.content.Context; +import android.content.res.Resources; +import android.content.res.TypedArray; +import android.text.Layout; +import android.text.StaticLayout; +import android.text.TextPaint; +import android.text.method.TransformationMethod; +import android.util.AttributeSet; +import android.util.DisplayMetrics; +import android.util.Log; +import android.util.TypedValue; +import android.widget.TextView; + +/** + * A TextView that resizes it's text to be no larger than the width of the view. + * + * @author Grantland Chew <grantlandchew@gmail.com> + */ +public class AutoFitTextView extends TextView { + + private static final String TAG = "AutoFitTextView"; + 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 PRECISION = 0.5f; + + // Attributes + private boolean mSizeToFit; + private int mMaxLines; + private float mMinTextSize; + private float mMaxTextSize; + private float mPrecision; + private TextPaint mPaint; + + 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) { + float scaledDensity = context.getResources().getDisplayMetrics().scaledDensity; + boolean sizeToFit = true; + int minTextSize = (int) scaledDensity * DEFAULT_MIN_TEXT_SIZE; + float precision = PRECISION; + + 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(); + } + + mPaint = new TextPaint(); + setSizeToFit(sizeToFit); + setRawTextSize(super.getTextSize()); + setRawMinTextSize(minTextSize); + setPrecision(precision); + } + + // Getters and Setters + + /** + * @return whether or not the text will be automatically resized to fit its constraints. + */ + public boolean isSizeToFit() { + return mSizeToFit; + } + + /** + * Sets the property of this field (singleLine, to automatically resize the text to fit its constraints. + */ + public void setSizeToFit() { + setSizeToFit(true); + } + + /** + * If true, the text will automatically be resized to fit its constraints; if false, it will + * act like a normal TextView. + * + * @param sizeToFit + */ + public void setSizeToFit(boolean sizeToFit) { + mSizeToFit = sizeToFit; + refitText(); + } + + /** + * {@inheritDoc} + */ + @Override + public float getTextSize() { + return mMaxTextSize; + } + + /** + * {@inheritDoc} + */ + @Override + public void setTextSize(int unit, float size) { + Context context = getContext(); + Resources r = Resources.getSystem(); + + if (context != null) { + r = context.getResources(); + } + + setRawTextSize(TypedValue.applyDimension(unit, size, r.getDisplayMetrics())); + } + + private void setRawTextSize(float size) { + if (size != mMaxTextSize) { + mMaxTextSize = size; + refitText(); + } + } + + /** + * @return the minimum size (in pixels) of the text size in this AutofitTextView + */ + public float getMinTextSize() { + return mMinTextSize; + } + + /** + * 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) { + Context context = getContext(); + Resources r = Resources.getSystem(); + + if (context != null) { + r = context.getResources(); + } + + setRawMinTextSize(TypedValue.applyDimension(unit, minSize, r.getDisplayMetrics())); + } + + /** + * 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) { + setMinTextSize(TypedValue.COMPLEX_UNIT_SP, minSize); + } + + private void setRawMinTextSize(float minSize) { + if (minSize != mMinTextSize) { + mMinTextSize = minSize; + refitText(); + } + } + + /** + * @return the amount of precision used to calculate the correct text size to fit within it's + * bounds. + */ + public float getPrecision() { + return mPrecision; + } + + /** + * Set the amount of precision used to calculate the correct text size to fit within it's + * bounds. Lower precision is more precise and takes more time. + * + * @param precision The amount of precision. + */ + public void setPrecision(float precision) { + if (precision != mPrecision) { + mPrecision = precision; + refitText(); + } + } + + /** + * {@inheritDoc} + */ + @Override + public void setLines(int lines) { + super.setLines(lines); + mMaxLines = lines; + refitText(); + } + + /** + * {@inheritDoc} + */ + @Override + public int getMaxLines() { + return mMaxLines; + } + + /** + * {@inheritDoc} + */ + @Override + public void setMaxLines(int maxLines) { + super.setMaxLines(maxLines); + if (maxLines != mMaxLines) { + mMaxLines = maxLines; + refitText(); + } + } + + /** + * Re size the font so the specified text fits in the text box assuming the text box is the + * specified width. + */ + private void refitText() { + if (!mSizeToFit) { + return; + } + + if (mMaxLines <= 0) { + // Don't auto-size since there's no limit on lines. + return; + } + + CharSequence text = getText(); + TransformationMethod method = getTransformationMethod(); + if (method != null) { + text = method.getTransformation(text, this); + } + int targetWidth = getWidth() - getPaddingLeft() - getPaddingRight(); + if (targetWidth > 0) { + Context context = getContext(); + Resources r = Resources.getSystem(); + DisplayMetrics displayMetrics; + + float size = mMaxTextSize; + float high = size; + float low = 0; + + if (context != null) { + r = context.getResources(); + } + displayMetrics = r.getDisplayMetrics(); + + mPaint.set(getPaint()); + mPaint.setTextSize(size); + + if ((mMaxLines == 1 && mPaint.measureText(text, 0, text.length()) > targetWidth) + || getLineCount(text, mPaint, size, targetWidth, displayMetrics) > mMaxLines) { + size = getTextSize(text, mPaint, targetWidth, mMaxLines, low, high, mPrecision, + displayMetrics); + } + + if (size < mMinTextSize) { + size = mMinTextSize; + } + + super.setTextSize(TypedValue.COMPLEX_UNIT_PX, size); + } + } + + /** + * Recursive binary search to find the best size for the text + */ + private static float getTextSize(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) { + return getTextSize(text, paint, targetWidth, maxLines, low, mid, precision, + displayMetrics); + } + else if (lineCount < maxLines) { + return getTextSize(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 getTextSize(text, paint, targetWidth, maxLines, low, mid, precision, + displayMetrics); + } else if (maxLineWidth < targetWidth) { + return getTextSize(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(); + } + + @Override + protected void onTextChanged(final CharSequence text, final int start, + final int lengthBefore, final int lengthAfter) { + super.onTextChanged(text, start, lengthBefore, lengthAfter); + refitText(); + } + + @Override + protected void onSizeChanged(int w, int h, int oldw, int oldh) { + super.onSizeChanged(w, h, oldw, oldh); + if (w != oldw) { + refitText(); + } + } +}
\ No newline at end of file |