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. -->