/* * Copyright (C) 2016 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 com.android.deskclock.widget; import android.text.Layout; import android.text.TextPaint; import android.util.TypedValue; import android.view.View; import android.widget.TextView; import static java.lang.Integer.MAX_VALUE; /** * A TextView which automatically re-sizes its text to fit within its boundaries. */ public final class TextSizeHelper { // The text view whose size this class controls. private final TextView mTextView; // Text paint used for measuring. private final TextPaint mMeasurePaint = new TextPaint(); // The maximum size the text is allowed to be (in pixels). private float mMaxTextSize; // The maximum width the text is allowed to be (in pixels). private int mWidthConstraint = MAX_VALUE; // The maximum height the text is allowed to be (in pixels). private int mHeightConstraint = MAX_VALUE; // When {@code true} calls to {@link #requestLayout()} should be ignored. private boolean mIgnoreRequestLayout; public TextSizeHelper(TextView view) { mTextView = view; mMaxTextSize = view.getTextSize(); } public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { int widthConstraint = MAX_VALUE; if (View.MeasureSpec.getMode(widthMeasureSpec) != View.MeasureSpec.UNSPECIFIED) { widthConstraint = View.MeasureSpec.getSize(widthMeasureSpec) - mTextView.getCompoundPaddingLeft() - mTextView.getCompoundPaddingRight(); } int heightConstraint = MAX_VALUE; if (View.MeasureSpec.getMode(heightMeasureSpec) != View.MeasureSpec.UNSPECIFIED) { heightConstraint = View.MeasureSpec.getSize(heightMeasureSpec) - mTextView.getCompoundPaddingTop() - mTextView.getCompoundPaddingBottom(); } if (mTextView.isLayoutRequested() || mWidthConstraint != widthConstraint || mHeightConstraint != heightConstraint) { mWidthConstraint = widthConstraint; mHeightConstraint = heightConstraint; adjustTextSize(); } } public void onTextChanged(int lengthBefore, int lengthAfter) { // The length of the text has changed, request layout to recalculate the current text // size. This is necessary to workaround an optimization in TextView#checkForRelayout() // which will avoid re-layout when the view has a fixed layout width. if (lengthBefore != lengthAfter) { mTextView.requestLayout(); } } public boolean shouldIgnoreRequestLayout() { return mIgnoreRequestLayout; } private void adjustTextSize() { final CharSequence text = mTextView.getText(); float textSize = mMaxTextSize; if (text.length() > 0 && (mWidthConstraint < MAX_VALUE || mHeightConstraint < MAX_VALUE)) { mMeasurePaint.set(mTextView.getPaint()); float minTextSize = 1f; float maxTextSize = mMaxTextSize; while (maxTextSize >= minTextSize) { final float midTextSize = Math.round((maxTextSize + minTextSize) / 2f); mMeasurePaint.setTextSize(midTextSize); final float width = Layout.getDesiredWidth(text, mMeasurePaint); final float height = mMeasurePaint.getFontMetricsInt(null); if (width > mWidthConstraint || height > mHeightConstraint) { maxTextSize = midTextSize - 1f; } else { textSize = midTextSize; minTextSize = midTextSize + 1f; } } } if (mTextView.getTextSize() != textSize) { mIgnoreRequestLayout = true; mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); mIgnoreRequestLayout = false; } } }