Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 5cd7efb2 authored by Andrei Stingaceanu's avatar Andrei Stingaceanu
Browse files

AutoSize TextView (part 1) - minimal end-to-end

Introduced the minimal number of attributes needed
to do autosizing and the autosize functions
bundled in TextView (for now). Note that in this
first version the autosizing can only be controlled
via construction.

Next: introduce getters/setters for the new attributes.

Bug: 32221168
Test: added a minimal smoke-ish CTS which exercises the
      new attributes.

Change-Id: Idf2195f6a600bfb7908b703ea046209b5868c521
parent a6fa8e54
Loading
Loading
Loading
Loading
+6 −0
Original line number Diff line number Diff line
@@ -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 {
+6 −0
Original line number Diff line number Diff line
@@ -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 {
+6 −0
Original line number Diff line number Diff line
@@ -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 {
+176 −0
Original line number Diff line number Diff line
@@ -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.
     *
     * <p>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.
     *
     * <p>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.
     *
     * <p>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),
+17 −0
Original line number Diff line number Diff line
@@ -4624,6 +4624,23 @@
            screens with limited space for text. -->
            <enum name="full" value="2" />
        </attr>
        <!-- Specify the type of auto-size. -->
        <attr name="autoSizeText" format="enum">
            <!-- No auto-sizing (default). -->
            <enum name="none" value="0" />
            <!-- Uniform horizontal and vertical scaling. -->
            <enum name="xy" value="1" />
        </attr>
        <!-- Specify the auto-size step size if <code>autoSizeText</code> is set to
        <code>xy</code>. The default is 1px. Overwrites
        <code>autoSizeStepSizeSet</code> if set. -->
        <attr name="autoSizeStepGranularity" format="dimension" />
        <!-- Array of dimensions to be used in conjunction with
        <code>autoSizeText</code> set to <code>xy</code>. Overwrites
        <code>autoSizeStepGranularity</code> if set. -->
        <attr name="autoSizeStepSizeSet"/>
        <!-- The minimum text size constraint to be used when auto-sizing text -->
        <attr name="autoSizeMinTextSize" format="dimension" />
    </declare-styleable>
    <declare-styleable name="TextViewAppearance">
        <!-- Base text color, typeface, size, and style. -->
Loading