diff --git a/api/current.txt b/api/current.txt index 10d37dba20b25..33564a28ddf33 100644 --- a/api/current.txt +++ b/api/current.txt @@ -289,6 +289,10 @@ package android { field public static final int autoLink = 16842928; // 0x10100b0 field public static final int autoMirrored = 16843754; // 0x10103ea field public static final int autoRemoveFromRecents = 16843847; // 0x1010447 + field public static final int autoSizeMinTextSize = 16844088; // 0x1010538 + field public static final int autoSizeStepGranularity = 16844086; // 0x1010536 + field public static final int autoSizeStepSizeSet = 16844087; // 0x1010537 + field public static final int autoSizeText = 16844085; // 0x1010535 field public static final int autoStart = 16843445; // 0x10102b5 field public static final deprecated int autoText = 16843114; // 0x101016a field public static final int autoUrlDetect = 16843404; // 0x101028c @@ -48746,6 +48750,8 @@ package android.widget { method public void setTypeface(android.graphics.Typeface, int); method public void setTypeface(android.graphics.Typeface); method public void setWidth(int); + field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0 + field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1 } public static final class TextView.BufferType extends java.lang.Enum { diff --git a/api/system-current.txt b/api/system-current.txt index 0370287a1e54f..150d925158452 100644 --- a/api/system-current.txt +++ b/api/system-current.txt @@ -396,6 +396,10 @@ package android { field public static final int autoLink = 16842928; // 0x10100b0 field public static final int autoMirrored = 16843754; // 0x10103ea field public static final int autoRemoveFromRecents = 16843847; // 0x1010447 + field public static final int autoSizeMinTextSize = 16844088; // 0x1010538 + field public static final int autoSizeStepGranularity = 16844086; // 0x1010536 + field public static final int autoSizeStepSizeSet = 16844087; // 0x1010537 + field public static final int autoSizeText = 16844085; // 0x1010535 field public static final int autoStart = 16843445; // 0x10102b5 field public static final deprecated int autoText = 16843114; // 0x101016a field public static final int autoUrlDetect = 16843404; // 0x101028c @@ -52267,6 +52271,8 @@ package android.widget { method public void setTypeface(android.graphics.Typeface, int); method public void setTypeface(android.graphics.Typeface); method public void setWidth(int); + field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0 + field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1 } public static final class TextView.BufferType extends java.lang.Enum { diff --git a/api/test-current.txt b/api/test-current.txt index 6b1d698f5eaaf..0730cc4a52167 100644 --- a/api/test-current.txt +++ b/api/test-current.txt @@ -289,6 +289,10 @@ package android { field public static final int autoLink = 16842928; // 0x10100b0 field public static final int autoMirrored = 16843754; // 0x10103ea field public static final int autoRemoveFromRecents = 16843847; // 0x1010447 + field public static final int autoSizeMinTextSize = 16844088; // 0x1010538 + field public static final int autoSizeStepGranularity = 16844086; // 0x1010536 + field public static final int autoSizeStepSizeSet = 16844087; // 0x1010537 + field public static final int autoSizeText = 16844085; // 0x1010535 field public static final int autoStart = 16843445; // 0x10102b5 field public static final deprecated int autoText = 16843114; // 0x101016a field public static final int autoUrlDetect = 16843404; // 0x101028c @@ -49004,6 +49008,8 @@ package android.widget { method public void setTypeface(android.graphics.Typeface, int); method public void setTypeface(android.graphics.Typeface); method public void setWidth(int); + field public static final int AUTO_SIZE_TYPE_NONE = 0; // 0x0 + field public static final int AUTO_SIZE_TYPE_XY = 1; // 0x1 } public static final class TextView.BufferType extends java.lang.Enum { diff --git a/core/java/android/widget/TextView.java b/core/java/android/widget/TextView.java index dba86556fd1d5..b85175d655f8a 100644 --- a/core/java/android/widget/TextView.java +++ b/core/java/android/widget/TextView.java @@ -159,6 +159,7 @@ import org.xmlpull.v1.XmlPullParserException; import java.io.IOException; import java.lang.ref.WeakReference; import java.util.ArrayList; +import java.util.Arrays; import java.util.Locale; /** @@ -617,6 +618,7 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener private Rect mTempRect; private long mLastScroll; private Scroller mScroller; + private TextPaint mTempTextPaint; private BoringLayout.Metrics mBoring, mHintBoring; private BoringLayout mSavedLayout, mSavedHintLayout; @@ -667,6 +669,20 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener */ private int mDeviceProvisionedState = DEVICE_PROVISIONED_UNKNOWN; + // The TextView does not auto-size text. + public static final int AUTO_SIZE_TYPE_NONE = 0; + // The TextView performs uniform horizontal and vertical text size scaling to fit within the + // container. + public static final int AUTO_SIZE_TYPE_XY = 1; + // Auto-size type. + private int mAutoSizeType = AUTO_SIZE_TYPE_NONE; + // Default value for the step size in pixels. + private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; + // Contains the sorted set of desired text sizes in pixels to pick from when auto-sizing text. + private int[] mAutoSizeTextSizesInPx; + // Specifies if the current TextView needs to be auto-sized. + private boolean mNeedsTextAutoResize = false; + /** * Kick-start the font cache for the zygote process (to pay the cost of * initializing freetype for our default font only once). @@ -869,6 +885,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener CharSequence hint = null; boolean password = false; int inputType = EditorInfo.TYPE_NULL; + int autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; + int autoSizeMinTextSize = (int) TypedValue.applyDimension( + TypedValue.COMPLEX_UNIT_SP, 12, getResources().getDisplayMetrics()); a = theme.obtainStyledAttributes( attrs, com.android.internal.R.styleable.TextView, defStyleAttr, defStyleRes); @@ -1223,6 +1242,19 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener case com.android.internal.R.styleable.TextView_hyphenationFrequency: mHyphenationFrequency = a.getInt(attr, Layout.HYPHENATION_FREQUENCY_NONE); break; + + case com.android.internal.R.styleable.TextView_autoSizeText: + mAutoSizeType = a.getInt(attr, AUTO_SIZE_TYPE_NONE); + break; + + case com.android.internal.R.styleable.TextView_autoSizeStepGranularity: + autoSizeStepGranularityInPx = a.getDimensionPixelSize( + attr, DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); + break; + + case com.android.internal.R.styleable.TextView_autoSizeMinTextSize: + autoSizeMinTextSize = a.getDimensionPixelSize(attr, autoSizeMinTextSize); + break; } } a.recycle(); @@ -1500,6 +1532,43 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener if (getImportantForAccessibility() == IMPORTANT_FOR_ACCESSIBILITY_AUTO) { setImportantForAccessibility(IMPORTANT_FOR_ACCESSIBILITY_YES); } + + // Setup auto-size. + if (mEditor == null) { + switch (mAutoSizeType) { + case AUTO_SIZE_TYPE_NONE: + // Nothing to do. + break; + case AUTO_SIZE_TYPE_XY: + // getTextSize() represents the maximum text size. + if (getTextSize() <= autoSizeMinTextSize) { + throw new IllegalStateException("Maximum text size is less then minimum " + + "text size"); + } + + if (autoSizeStepGranularityInPx <= 0) { + throw new IllegalStateException("Unexpected zero or negative value for auto" + + " size step granularity in pixels"); + } + + final int autoSizeValuesLength = (int) ((getTextSize() - autoSizeMinTextSize) + / autoSizeStepGranularityInPx); + mAutoSizeTextSizesInPx = new int[autoSizeValuesLength]; + int sizeToAdd = autoSizeMinTextSize; + for (int i = 0; i < autoSizeValuesLength; i++) { + mAutoSizeTextSizesInPx[i] = sizeToAdd; + sizeToAdd += autoSizeStepGranularityInPx; + } + + Arrays.sort(mAutoSizeTextSizesInPx); + mNeedsTextAutoResize = true; + break; + default: + throw new IllegalArgumentException( + "Unknown autoSizeText type: " + mAutoSizeType); + } + + } } private int[] parseDimensionArray(TypedArray dimens) { @@ -2954,6 +3023,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener /** * @return the size (in pixels) of the default text size in this TextView. + * + *

Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than + * this function returns the maximum text size for auto-sizing. */ @ViewDebug.ExportedProperty(category = "text") public float getTextSize() { @@ -2986,6 +3058,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * pixel" units. This size is adjusted based on the current density and * user font size preference. * + *

Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than + * this function sets the maximum text size for auto-sizing. + * * @param size The scaled pixel size. * * @attr ref android.R.styleable#TextView_textSize @@ -2999,6 +3074,9 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener * Set the default text size to a given unit and value. See {@link * TypedValue} for the possible dimension units. * + *

Note: if this TextView has mAutoSizeType set to {@link TextView#AUTO_SIZE_TYPE_XY} than + * this function sets the maximum text size for auto-sizing. + * * @param unit The desired dimension unit. * @param size The desired size in the given units. * @@ -7446,9 +7524,107 @@ public class TextView extends View implements ViewTreeObserver.OnPreDrawListener scrollTo(0, 0); } + if (mNeedsTextAutoResize) { + // Call auto-size after the width and height have been calculated. + autoSizeText(); + } + setMeasuredDimension(width, height); } + /** + * Automatically computes and sets the text size. + */ + private void autoSizeText() { + synchronized (TEMP_RECTF) { + TEMP_RECTF.setEmpty(); + final int maxWidth = getMeasuredWidth() - getPaddingLeft() - getPaddingRight(); + final int maxHeight = getMeasuredHeight() - getPaddingBottom() - getPaddingTop(); + + if (maxWidth <= 0 || maxHeight <= 0) { + return; + } + + TEMP_RECTF.right = maxWidth; + TEMP_RECTF.bottom = maxHeight; + final float textSize = findLargestTextSizeWhichFits(TEMP_RECTF); + setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); + mNeedsTextAutoResize = false; + } + } + + /** + * Performs a binary search to find the largest text size that will still fit within the size + * available to this view. + */ + private int findLargestTextSizeWhichFits(RectF availableSpace) { + final int sizesCount = mAutoSizeTextSizesInPx.length; + if (sizesCount == 0) { + throw new IllegalStateException("No available text sizes to choose from."); + } + + int bestSizeIndex = 0; + int lowIndex = bestSizeIndex + 1; + int highIndex = sizesCount - 1; + int sizeToTryIndex; + while (lowIndex <= highIndex) { + sizeToTryIndex = (lowIndex + highIndex) / 2; + if (suggestedSizeFitsInSpace(mAutoSizeTextSizesInPx[sizeToTryIndex], availableSpace)) { + bestSizeIndex = lowIndex; + lowIndex = sizeToTryIndex + 1; + } else { + highIndex = sizeToTryIndex - 1; + bestSizeIndex = highIndex; + } + } + + return mAutoSizeTextSizesInPx[bestSizeIndex]; + } + + private boolean suggestedSizeFitsInSpace(int suggestedSizeInPx, RectF availableSpace) { + final CharSequence text = getText(); + final int maxLines = getMaxLines(); + if (mTempTextPaint == null) { + mTempTextPaint = new TextPaint(); + } else { + mTempTextPaint.reset(); + } + mTempTextPaint.set(getPaint()); + mTempTextPaint.setTextSize(suggestedSizeInPx); + + if ((mLayout instanceof BoringLayout) && BoringLayout.isBoring( + text, mTempTextPaint, getTextDirectionHeuristic(), mBoring) != null) { + return mTempTextPaint.getFontSpacing() + getPaddingTop() + getPaddingBottom() + <= availableSpace.bottom + && mTempTextPaint.measureText(text, 0, text.length()) + + getPaddingLeft() + getPaddingRight() <= availableSpace.right; + } else { + StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(text, 0, text.length(), + mTempTextPaint, getMeasuredWidth() - getPaddingLeft() - getPaddingRight()); + layoutBuilder.setAlignment(getLayoutAlignment()); + layoutBuilder.setLineSpacing(getLineSpacingExtra(), getLineSpacingMultiplier()); + layoutBuilder.setIncludePad(true); + StaticLayout layout = layoutBuilder.build(); + + // Lines overflow. + if (maxLines != -1 && layout.getLineCount() > maxLines) { + return false; + } + + // Width overflow. + if (layout.getWidth() > availableSpace.right) { + return false; + } + + // Height overflow. + if (layout.getHeight() > availableSpace.bottom) { + return false; + } + } + + return true; + } + private int getDesiredHeight() { return Math.max( getDesiredHeight(mLayout, true), diff --git a/core/res/res/values/attrs.xml b/core/res/res/values/attrs.xml index acafbcfd428ab..95f372c43a59a 100644 --- a/core/res/res/values/attrs.xml +++ b/core/res/res/values/attrs.xml @@ -4624,6 +4624,23 @@ screens with limited space for text. --> + + + + + + + + + + + + + diff --git a/core/res/res/values/public.xml b/core/res/res/values/public.xml index ae82128b88cf8..200961f1c8a88 100644 --- a/core/res/res/values/public.xml +++ b/core/res/res/values/public.xml @@ -2760,6 +2760,10 @@ + + + +