Java tutorial
/* * Copyright (C) 2017 The Android Open Source Project * * 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 android.support.v7.widget; import static android.support.annotation.RestrictTo.Scope.LIBRARY_GROUP; import android.annotation.TargetApi; import android.content.Context; import android.content.res.Resources; import android.content.res.TypedArray; import android.graphics.RectF; import android.os.Build; import android.support.annotation.NonNull; import android.support.annotation.Nullable; import android.support.annotation.RestrictTo; import android.support.v4.widget.TextViewCompat; import android.support.v7.appcompat.R; import android.text.Layout; import android.text.StaticLayout; import android.text.TextDirectionHeuristic; import android.text.TextDirectionHeuristics; 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; import java.lang.reflect.Method; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.Hashtable; import java.util.List; /** * Utility class which encapsulates the logic for the TextView auto-size text feature added to * the Android Framework in {@link android.os.Build.VERSION_CODES#O}. * * <p>A TextView can be instructed to let the size of the text expand or contract automatically to * fill its layout based on the TextView's characteristics and boundaries. */ class AppCompatTextViewAutoSizeHelper { private static final String TAG = "ACTVAutoSizeHelper"; private static final RectF TEMP_RECTF = new RectF(); // Default minimum size for auto-sizing text in scaled pixels. private static final int DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP = 12; // Default maximum size for auto-sizing text in scaled pixels. private static final int DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP = 112; // Default value for the step size in pixels. private static final int DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX = 1; // Cache of TextView methods used via reflection; the key is the method name and the value is // the method itself or null if it can not be found. private static Hashtable<String, Method> sTextViewMethodByNameCache = new Hashtable<>(); // Use this to specify that any of the auto-size configuration int values have not been set. static final float UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE = -1f; // Ported from TextView#VERY_WIDE. Represents a maximum width in pixels the TextView takes when // horizontal scrolling is activated. private static final int VERY_WIDE = 1024 * 1024; // Auto-size text type. private int mAutoSizeTextType = TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; // Specify if auto-size text is needed. private boolean mNeedsAutoSizeText = false; // Step size for auto-sizing in pixels. private float mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; // Minimum text size for auto-sizing in pixels. private float mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; // Maximum text size for auto-sizing in pixels. private float mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; // Contains a (specified or computed) distinct sorted set of text sizes in pixels to pick from // when auto-sizing text. private int[] mAutoSizeTextSizesInPx = new int[0]; // Specifies whether auto-size should use the provided auto size steps set or if it should // build the steps set using mAutoSizeMinTextSizeInPx, mAutoSizeMaxTextSizeInPx and // mAutoSizeStepGranularityInPx. private boolean mHasPresetAutoSizeValues = false; private TextPaint mTempTextPaint; private final TextView mTextView; private final Context mContext; AppCompatTextViewAutoSizeHelper(TextView textView) { mTextView = textView; mContext = mTextView.getContext(); } void loadFromAttributes(AttributeSet attrs, int defStyleAttr) { float autoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; float autoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; float autoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; TypedArray a = mContext.obtainStyledAttributes(attrs, R.styleable.AppCompatTextView, defStyleAttr, 0); if (a.hasValue(R.styleable.AppCompatTextView_autoSizeTextType)) { mAutoSizeTextType = a.getInt(R.styleable.AppCompatTextView_autoSizeTextType, TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE); } if (a.hasValue(R.styleable.AppCompatTextView_autoSizeStepGranularity)) { autoSizeStepGranularityInPx = a.getDimension(R.styleable.AppCompatTextView_autoSizeStepGranularity, UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); } if (a.hasValue(R.styleable.AppCompatTextView_autoSizeMinTextSize)) { autoSizeMinTextSizeInPx = a.getDimension(R.styleable.AppCompatTextView_autoSizeMinTextSize, UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); } if (a.hasValue(R.styleable.AppCompatTextView_autoSizeMaxTextSize)) { autoSizeMaxTextSizeInPx = a.getDimension(R.styleable.AppCompatTextView_autoSizeMaxTextSize, UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE); } if (a.hasValue(R.styleable.AppCompatTextView_autoSizePresetSizes)) { final int autoSizeStepSizeArrayResId = a .getResourceId(R.styleable.AppCompatTextView_autoSizePresetSizes, 0); if (autoSizeStepSizeArrayResId > 0) { final TypedArray autoSizePreDefTextSizes = a.getResources() .obtainTypedArray(autoSizeStepSizeArrayResId); setupAutoSizeUniformPresetSizes(autoSizePreDefTextSizes); autoSizePreDefTextSizes.recycle(); } } a.recycle(); if (supportsAutoSizeText()) { if (mAutoSizeTextType == TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM) { // If uniform auto-size has been specified but preset values have not been set then // replace the auto-size configuration values that have not been specified with the // defaults. if (!mHasPresetAutoSizeValues) { final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); if (autoSizeMinTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { autoSizeMinTextSizeInPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, displayMetrics); } if (autoSizeMaxTextSizeInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { autoSizeMaxTextSizeInPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, displayMetrics); } if (autoSizeStepGranularityInPx == UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE) { autoSizeStepGranularityInPx = DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX; } validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, autoSizeMaxTextSizeInPx, autoSizeStepGranularityInPx); } setupAutoSizeText(); } } else { mAutoSizeTextType = TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; } } /** * Specify whether this widget should automatically scale the text to try to perfectly fit * within the layout bounds by using the default auto-size configuration. * * @param autoSizeTextType the type of auto-size. Must be one of * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_NONE} or * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM} * * @attr ref R.styleable#AppCompatTextView_autoSizeTextType * * @see #getAutoSizeTextType() * * @hide */ @RestrictTo(LIBRARY_GROUP) void setAutoSizeTextTypeWithDefaults(@TextViewCompat.AutoSizeTextType int autoSizeTextType) { if (supportsAutoSizeText()) { switch (autoSizeTextType) { case TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE: clearAutoSizeConfiguration(); break; case TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM: final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MIN_TEXT_SIZE_IN_SP, displayMetrics); final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, DEFAULT_AUTO_SIZE_MAX_TEXT_SIZE_IN_SP, displayMetrics); validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, autoSizeMaxTextSizeInPx, DEFAULT_AUTO_SIZE_GRANULARITY_IN_PX); if (setupAutoSizeText()) { autoSizeText(); } break; default: throw new IllegalArgumentException("Unknown auto-size text type: " + autoSizeTextType); } } } /** * Specify whether this widget should automatically scale the text to try to perfectly fit * within the layout bounds. If all the configuration params are valid the type of auto-size is * set to {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}. * * @param autoSizeMinTextSize the minimum text size available for auto-size * @param autoSizeMaxTextSize the maximum text size available for auto-size * @param autoSizeStepGranularity the auto-size step granularity. It is used in conjunction with * the minimum and maximum text size in order to build the set of * text sizes the system uses to choose from when auto-sizing * @param unit the desired dimension unit for all sizes above. See {@link TypedValue} for the * possible dimension units * * @throws IllegalArgumentException if any of the configuration params are invalid. * * @attr ref R.styleable#AppCompatTextView_autoSizeTextType * @attr ref R.styleable#AppCompatTextView_autoSizeMinTextSize * @attr ref R.styleable#AppCompatTextView_autoSizeMaxTextSize * @attr ref R.styleable#AppCompatTextView_autoSizeStepGranularity * * @see #setAutoSizeTextTypeWithDefaults(int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) * @see #getAutoSizeMinTextSize() * @see #getAutoSizeMaxTextSize() * @see #getAutoSizeStepGranularity() * @see #getAutoSizeTextAvailableSizes() * * @hide */ @RestrictTo(LIBRARY_GROUP) void setAutoSizeTextTypeUniformWithConfiguration(int autoSizeMinTextSize, int autoSizeMaxTextSize, int autoSizeStepGranularity, int unit) throws IllegalArgumentException { if (supportsAutoSizeText()) { final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); final float autoSizeMinTextSizeInPx = TypedValue.applyDimension(unit, autoSizeMinTextSize, displayMetrics); final float autoSizeMaxTextSizeInPx = TypedValue.applyDimension(unit, autoSizeMaxTextSize, displayMetrics); final float autoSizeStepGranularityInPx = TypedValue.applyDimension(unit, autoSizeStepGranularity, displayMetrics); validateAndSetAutoSizeTextTypeUniformConfiguration(autoSizeMinTextSizeInPx, autoSizeMaxTextSizeInPx, autoSizeStepGranularityInPx); if (setupAutoSizeText()) { autoSizeText(); } } } /** * Specify whether this widget should automatically scale the text to try to perfectly fit * within the layout bounds. If at least one value from the <code>presetSizes</code> is valid * then the type of auto-size is set to {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM}. * * @param presetSizes an {@code int} array of sizes in pixels * @param unit the desired dimension unit for the preset sizes above. See {@link TypedValue} for * the possible dimension units * * @throws IllegalArgumentException if all of the <code>presetSizes</code> are invalid. *_ * @attr ref R.styleable#AppCompatTextView_autoSizeTextType * @attr ref R.styleable#AppCompatTextView_autoSizePresetSizes * * @see #setAutoSizeTextTypeWithDefaults(int) * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #getAutoSizeMinTextSize() * @see #getAutoSizeMaxTextSize() * @see #getAutoSizeTextAvailableSizes() * * @hide */ @RestrictTo(LIBRARY_GROUP) void setAutoSizeTextTypeUniformWithPresetSizes(@NonNull int[] presetSizes, int unit) throws IllegalArgumentException { if (supportsAutoSizeText()) { final int presetSizesLength = presetSizes.length; if (presetSizesLength > 0) { int[] presetSizesInPx = new int[presetSizesLength]; if (unit == TypedValue.COMPLEX_UNIT_PX) { presetSizesInPx = Arrays.copyOf(presetSizes, presetSizesLength); } else { final DisplayMetrics displayMetrics = mContext.getResources().getDisplayMetrics(); // Convert all to sizes to pixels. for (int i = 0; i < presetSizesLength; i++) { presetSizesInPx[i] = Math .round(TypedValue.applyDimension(unit, presetSizes[i], displayMetrics)); } } mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(presetSizesInPx); if (!setupAutoSizeUniformPresetSizesConfiguration()) { throw new IllegalArgumentException( "None of the preset sizes is valid: " + Arrays.toString(presetSizes)); } } else { mHasPresetAutoSizeValues = false; } if (setupAutoSizeText()) { autoSizeText(); } } } /** * Returns the type of auto-size set for this widget. * * @return an {@code int} corresponding to one of the auto-size types: * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_NONE} or * {@link TextViewCompat#AUTO_SIZE_TEXT_TYPE_UNIFORM} * * @attr ref R.styleable#AppCompatTextView_autoSizeTextType * * @see #setAutoSizeTextTypeWithDefaults(int) * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) * * @hide */ @RestrictTo(LIBRARY_GROUP) @TextViewCompat.AutoSizeTextType int getAutoSizeTextType() { return mAutoSizeTextType; } /** * @return the current auto-size step granularity in pixels. * * @attr ref R.styleable#AppCompatTextView_autoSizeStepGranularity * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * * @hide */ @RestrictTo(LIBRARY_GROUP) int getAutoSizeStepGranularity() { return Math.round(mAutoSizeStepGranularityInPx); } /** * @return the current auto-size minimum text size in pixels (the default is 12sp). Note that * if auto-size has not been configured this function returns {@code -1}. * * @attr ref R.styleable#AppCompatTextView_autoSizeMinTextSize * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) * * @hide */ @RestrictTo(LIBRARY_GROUP) int getAutoSizeMinTextSize() { return Math.round(mAutoSizeMinTextSizeInPx); } /** * @return the current auto-size maximum text size in pixels (the default is 112sp). Note that * if auto-size has not been configured this function returns {@code -1}. * * @attr ref R.styleable#AppCompatTextView_autoSizeMaxTextSize * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) * * @hide */ @RestrictTo(LIBRARY_GROUP) int getAutoSizeMaxTextSize() { return Math.round(mAutoSizeMaxTextSizeInPx); } /** * @return the current auto-size {@code int} sizes array (in pixels). * * @see #setAutoSizeTextTypeUniformWithConfiguration(int, int, int, int) * @see #setAutoSizeTextTypeUniformWithPresetSizes(int[], int) * * @hide */ @RestrictTo(LIBRARY_GROUP) int[] getAutoSizeTextAvailableSizes() { return mAutoSizeTextSizesInPx; } private void setupAutoSizeUniformPresetSizes(TypedArray textSizes) { final int textSizesLength = textSizes.length(); final int[] parsedSizes = new int[textSizesLength]; if (textSizesLength > 0) { for (int i = 0; i < textSizesLength; i++) { parsedSizes[i] = textSizes.getDimensionPixelSize(i, -1); } mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(parsedSizes); setupAutoSizeUniformPresetSizesConfiguration(); } } private boolean setupAutoSizeUniformPresetSizesConfiguration() { final int sizesLength = mAutoSizeTextSizesInPx.length; mHasPresetAutoSizeValues = sizesLength > 0; if (mHasPresetAutoSizeValues) { mAutoSizeTextType = TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM; mAutoSizeMinTextSizeInPx = mAutoSizeTextSizesInPx[0]; mAutoSizeMaxTextSizeInPx = mAutoSizeTextSizesInPx[sizesLength - 1]; mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; } return mHasPresetAutoSizeValues; } // Returns distinct sorted positive values. private int[] cleanupAutoSizePresetSizes(int[] presetValues) { final int presetValuesLength = presetValues.length; if (presetValuesLength == 0) { return presetValues; } Arrays.sort(presetValues); final List<Integer> uniqueValidSizes = new ArrayList<>(); for (int i = 0; i < presetValuesLength; i++) { final int currentPresetValue = presetValues[i]; if (currentPresetValue > 0 && Collections.binarySearch(uniqueValidSizes, currentPresetValue) < 0) { uniqueValidSizes.add(currentPresetValue); } } if (presetValuesLength == uniqueValidSizes.size()) { return presetValues; } else { final int uniqueValidSizesLength = uniqueValidSizes.size(); final int[] cleanedUpSizes = new int[uniqueValidSizesLength]; for (int i = 0; i < uniqueValidSizesLength; i++) { cleanedUpSizes[i] = uniqueValidSizes.get(i); } return cleanedUpSizes; } } /** * If all params are valid then save the auto-size configuration. * * @throws IllegalArgumentException if any of the params are invalid */ private void validateAndSetAutoSizeTextTypeUniformConfiguration(float autoSizeMinTextSizeInPx, float autoSizeMaxTextSizeInPx, float autoSizeStepGranularityInPx) throws IllegalArgumentException { // First validate. if (autoSizeMinTextSizeInPx <= 0) { throw new IllegalArgumentException( "Minimum auto-size text size (" + autoSizeMinTextSizeInPx + "px) is less or equal to (0px)"); } if (autoSizeMaxTextSizeInPx <= autoSizeMinTextSizeInPx) { throw new IllegalArgumentException("Maximum auto-size text size (" + autoSizeMaxTextSizeInPx + "px) is less or equal to minimum auto-size " + "text size (" + autoSizeMinTextSizeInPx + "px)"); } if (autoSizeStepGranularityInPx <= 0) { throw new IllegalArgumentException("The auto-size step granularity (" + autoSizeStepGranularityInPx + "px) is less or equal to (0px)"); } // All good, persist the configuration. mAutoSizeTextType = TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM; mAutoSizeMinTextSizeInPx = autoSizeMinTextSizeInPx; mAutoSizeMaxTextSizeInPx = autoSizeMaxTextSizeInPx; mAutoSizeStepGranularityInPx = autoSizeStepGranularityInPx; mHasPresetAutoSizeValues = false; } private boolean setupAutoSizeText() { if (supportsAutoSizeText() && mAutoSizeTextType == TextViewCompat.AUTO_SIZE_TEXT_TYPE_UNIFORM) { // Calculate the sizes set based on minimum size, maximum size and step size if we do // not have a predefined set of sizes or if the current sizes array is empty. if (!mHasPresetAutoSizeValues || mAutoSizeTextSizesInPx.length == 0) { // Calculate sizes to choose from based on the current auto-size configuration. int autoSizeValuesLength = 1; float currentSize = Math.round(mAutoSizeMinTextSizeInPx); while (Math.round(currentSize + mAutoSizeStepGranularityInPx) <= Math .round(mAutoSizeMaxTextSizeInPx)) { autoSizeValuesLength++; currentSize += mAutoSizeStepGranularityInPx; } int[] autoSizeTextSizesInPx = new int[autoSizeValuesLength]; float sizeToAdd = mAutoSizeMinTextSizeInPx; for (int i = 0; i < autoSizeValuesLength; i++) { autoSizeTextSizesInPx[i] = Math.round(sizeToAdd); sizeToAdd += mAutoSizeStepGranularityInPx; } mAutoSizeTextSizesInPx = cleanupAutoSizePresetSizes(autoSizeTextSizesInPx); } mNeedsAutoSizeText = true; } else { mNeedsAutoSizeText = false; } return mNeedsAutoSizeText; } /** * Automatically computes and sets the text size. * * @hide */ @RestrictTo(LIBRARY_GROUP) void autoSizeText() { if (!isAutoSizeEnabled()) { return; } if (mNeedsAutoSizeText) { if (mTextView.getMeasuredHeight() <= 0 || mTextView.getMeasuredWidth() <= 0) { return; } final boolean horizontallyScrolling = invokeAndReturnWithDefault(mTextView, "getHorizontallyScrolling", false); final int availableWidth = horizontallyScrolling ? VERY_WIDE : mTextView.getMeasuredWidth() - mTextView.getTotalPaddingLeft() - mTextView.getTotalPaddingRight(); final int availableHeight = mTextView.getHeight() - mTextView.getCompoundPaddingBottom() - mTextView.getCompoundPaddingTop(); if (availableWidth <= 0 || availableHeight <= 0) { return; } synchronized (TEMP_RECTF) { TEMP_RECTF.setEmpty(); TEMP_RECTF.right = availableWidth; TEMP_RECTF.bottom = availableHeight; final float optimalTextSize = findLargestTextSizeWhichFits(TEMP_RECTF); if (optimalTextSize != mTextView.getTextSize()) { setTextSizeInternal(TypedValue.COMPLEX_UNIT_PX, optimalTextSize); } } } // Always try to auto-size if enabled. Functions that do not want to trigger auto-sizing // after the next layout pass should set this to false. mNeedsAutoSizeText = true; } private void clearAutoSizeConfiguration() { mAutoSizeTextType = TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; mAutoSizeMinTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; mAutoSizeMaxTextSizeInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; mAutoSizeStepGranularityInPx = UNSET_AUTO_SIZE_UNIFORM_CONFIGURATION_VALUE; mAutoSizeTextSizesInPx = new int[0]; mNeedsAutoSizeText = false; } /** @hide */ @RestrictTo(LIBRARY_GROUP) void setTextSizeInternal(int unit, float size) { Resources res = mContext == null ? Resources.getSystem() : mContext.getResources(); setRawTextSize(TypedValue.applyDimension(unit, size, res.getDisplayMetrics())); } private void setRawTextSize(float size) { if (size != mTextView.getPaint().getTextSize()) { mTextView.getPaint().setTextSize(size); boolean isInLayout = false; if (Build.VERSION.SDK_INT >= 18) { isInLayout = mTextView.isInLayout(); } if (mTextView.getLayout() != null) { // Do not auto-size right after setting the text size. mNeedsAutoSizeText = false; final String methodName = "nullLayouts"; try { Method method = getTextViewMethod(methodName); if (method != null) { method.invoke(mTextView); } } catch (Exception ex) { Log.w(TAG, "Failed to invoke TextView#" + methodName + "() method", ex); } if (!isInLayout) { mTextView.requestLayout(); } else { mTextView.forceLayout(); } mTextView.invalidate(); } } } /** * 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) { CharSequence text = mTextView.getText(); TransformationMethod transformationMethod = mTextView.getTransformationMethod(); if (transformationMethod != null) { CharSequence transformedText = transformationMethod.getTransformation(text, mTextView); if (transformedText != null) { text = transformedText; } } final int maxLines = Build.VERSION.SDK_INT >= 16 ? mTextView.getMaxLines() : -1; if (mTempTextPaint == null) { mTempTextPaint = new TextPaint(); } else { mTempTextPaint.reset(); } mTempTextPaint.set(mTextView.getPaint()); mTempTextPaint.setTextSize(suggestedSizeInPx); // Needs reflection call due to being private. Layout.Alignment alignment = invokeAndReturnWithDefault(mTextView, "getLayoutAlignment", Layout.Alignment.ALIGN_NORMAL); final StaticLayout layout = Build.VERSION.SDK_INT >= 23 ? createStaticLayoutForMeasuring(text, alignment, Math.round(availableSpace.right), maxLines) : createStaticLayoutForMeasuringPre23(text, alignment, Math.round(availableSpace.right)); // Lines overflow. if (maxLines != -1 && (layout.getLineCount() > maxLines || (layout.getLineEnd(layout.getLineCount() - 1)) != text.length())) { return false; } // Height overflow. if (layout.getHeight() > availableSpace.bottom) { return false; } return true; } @TargetApi(23) private StaticLayout createStaticLayoutForMeasuring(CharSequence text, Layout.Alignment alignment, int availableWidth, int maxLines) { // Can use the StaticLayout.Builder (along with TextView params added in or after // API 23) to construct the layout. final TextDirectionHeuristic textDirectionHeuristic = invokeAndReturnWithDefault(mTextView, "getTextDirectionHeuristic", TextDirectionHeuristics.FIRSTSTRONG_LTR); final StaticLayout.Builder layoutBuilder = StaticLayout.Builder.obtain(text, 0, text.length(), mTempTextPaint, availableWidth); return layoutBuilder.setAlignment(alignment) .setLineSpacing(mTextView.getLineSpacingExtra(), mTextView.getLineSpacingMultiplier()) .setIncludePad(mTextView.getIncludeFontPadding()).setBreakStrategy(mTextView.getBreakStrategy()) .setHyphenationFrequency(mTextView.getHyphenationFrequency()) .setMaxLines(maxLines == -1 ? Integer.MAX_VALUE : maxLines).setTextDirection(textDirectionHeuristic) .build(); } @TargetApi(14) private StaticLayout createStaticLayoutForMeasuringPre23(CharSequence text, Layout.Alignment alignment, int availableWidth) { // Setup defaults. float lineSpacingMultiplier = 1.0f; float lineSpacingAdd = 0.0f; boolean includePad = true; if (Build.VERSION.SDK_INT >= 16) { // Call public methods. lineSpacingMultiplier = mTextView.getLineSpacingMultiplier(); lineSpacingAdd = mTextView.getLineSpacingExtra(); includePad = mTextView.getIncludeFontPadding(); } else { // Call private methods and make sure to provide fallback defaults in case something // goes wrong. The default values have been inlined with the StaticLayout defaults. lineSpacingMultiplier = invokeAndReturnWithDefault(mTextView, "getLineSpacingMultiplier", lineSpacingMultiplier); lineSpacingAdd = invokeAndReturnWithDefault(mTextView, "getLineSpacingExtra", lineSpacingAdd); includePad = invokeAndReturnWithDefault(mTextView, "getIncludeFontPadding", includePad); } // The layout could not be constructed using the builder so fall back to the // most broad constructor. return new StaticLayout(text, mTempTextPaint, availableWidth, alignment, lineSpacingMultiplier, lineSpacingAdd, includePad); } private <T> T invokeAndReturnWithDefault(@NonNull Object object, @NonNull final String methodName, @NonNull final T defaultValue) { T result = null; boolean exceptionThrown = false; try { // Cache lookup. Method method = getTextViewMethod(methodName); result = (T) method.invoke(object); } catch (Exception ex) { exceptionThrown = true; Log.w(TAG, "Failed to invoke TextView#" + methodName + "() method", ex); } finally { if (result == null && exceptionThrown) { result = defaultValue; } } return result; } @Nullable private Method getTextViewMethod(@NonNull final String methodName) { try { Method method = sTextViewMethodByNameCache.get(methodName); if (method == null) { method = TextView.class.getDeclaredMethod(methodName); if (method != null) { method.setAccessible(true); // Cache update. sTextViewMethodByNameCache.put(methodName, method); } } return method; } catch (Exception ex) { Log.w(TAG, "Failed to retrieve TextView#" + methodName + "() method", ex); return null; } } /** * @return {@code true} if this widget supports auto-sizing text and has been configured to * auto-size. * * @hide */ @RestrictTo(LIBRARY_GROUP) boolean isAutoSizeEnabled() { return supportsAutoSizeText() && mAutoSizeTextType != TextViewCompat.AUTO_SIZE_TEXT_TYPE_NONE; } /** * @return {@code true} if this TextView supports auto-sizing text to fit within its container. */ private boolean supportsAutoSizeText() { // Auto-size only supports TextView and all siblings but EditText. return !(mTextView instanceof AppCompatEditText); } }