1/* 2 * Copyright (C) 2016 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17package com.android.deskclock.widget; 18 19import android.text.Layout; 20import android.text.TextPaint; 21import android.util.TypedValue; 22import android.view.View; 23import android.widget.TextView; 24 25import static java.lang.Integer.MAX_VALUE; 26 27/** 28 * A TextView which automatically re-sizes its text to fit within its boundaries. 29 */ 30public final class TextSizeHelper { 31 32 // The text view whose size this class controls. 33 private final TextView mTextView; 34 35 // Text paint used for measuring. 36 private final TextPaint mMeasurePaint = new TextPaint(); 37 38 // The maximum size the text is allowed to be (in pixels). 39 private float mMaxTextSize; 40 41 // The maximum width the text is allowed to be (in pixels). 42 private int mWidthConstraint = MAX_VALUE; 43 44 // The maximum height the text is allowed to be (in pixels). 45 private int mHeightConstraint = MAX_VALUE; 46 47 // When {@code true} calls to {@link #requestLayout()} should be ignored. 48 private boolean mIgnoreRequestLayout; 49 50 public TextSizeHelper(TextView view) { 51 mTextView = view; 52 mMaxTextSize = view.getTextSize(); 53 } 54 55 public void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { 56 int widthConstraint = MAX_VALUE; 57 if (View.MeasureSpec.getMode(widthMeasureSpec) != View.MeasureSpec.UNSPECIFIED) { 58 widthConstraint = View.MeasureSpec.getSize(widthMeasureSpec) 59 - mTextView.getCompoundPaddingLeft() - mTextView.getCompoundPaddingRight(); 60 } 61 62 int heightConstraint = MAX_VALUE; 63 if (View.MeasureSpec.getMode(heightMeasureSpec) != View.MeasureSpec.UNSPECIFIED) { 64 heightConstraint = View.MeasureSpec.getSize(heightMeasureSpec) 65 - mTextView.getCompoundPaddingTop() - mTextView.getCompoundPaddingBottom(); 66 } 67 68 if (mTextView.isLayoutRequested() || mWidthConstraint != widthConstraint 69 || mHeightConstraint != heightConstraint) { 70 mWidthConstraint = widthConstraint; 71 mHeightConstraint = heightConstraint; 72 73 adjustTextSize(); 74 } 75 } 76 77 public void onTextChanged(int lengthBefore, int lengthAfter) { 78 // The length of the text has changed, request layout to recalculate the current text 79 // size. This is necessary to workaround an optimization in TextView#checkForRelayout() 80 // which will avoid re-layout when the view has a fixed layout width. 81 if (lengthBefore != lengthAfter) { 82 mTextView.requestLayout(); 83 } 84 } 85 86 public boolean shouldIgnoreRequestLayout() { 87 return mIgnoreRequestLayout; 88 } 89 90 private void adjustTextSize() { 91 final CharSequence text = mTextView.getText(); 92 float textSize = mMaxTextSize; 93 if (text.length() > 0 && (mWidthConstraint < MAX_VALUE || mHeightConstraint < MAX_VALUE)) { 94 mMeasurePaint.set(mTextView.getPaint()); 95 96 float minTextSize = 1f; 97 float maxTextSize = mMaxTextSize; 98 while (maxTextSize >= minTextSize) { 99 final float midTextSize = Math.round((maxTextSize + minTextSize) / 2f); 100 mMeasurePaint.setTextSize(midTextSize); 101 102 final float width = Layout.getDesiredWidth(text, mMeasurePaint); 103 final float height = mMeasurePaint.getFontMetricsInt(null); 104 if (width > mWidthConstraint || height > mHeightConstraint) { 105 maxTextSize = midTextSize - 1f; 106 } else { 107 textSize = midTextSize; 108 minTextSize = midTextSize + 1f; 109 } 110 } 111 } 112 113 if (mTextView.getTextSize() != textSize) { 114 mIgnoreRequestLayout = true; 115 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize); 116 mIgnoreRequestLayout = false; 117 } 118 } 119}