1 /* 2 * Copyright (C) 2020 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 17 package com.android.deskclock.widget 18 19 import android.text.Layout 20 import android.text.TextPaint 21 import android.util.TypedValue 22 import android.view.View.MeasureSpec 23 import android.widget.TextView 24 25 /** 26 * A TextView which automatically re-sizes its text to fit within its boundaries. 27 */ 28 class TextSizeHelper(private val mTextView: TextView) { 29 30 // Text paint used for measuring. 31 private val mMeasurePaint = TextPaint() 32 33 // The maximum size the text is allowed to be (in pixels). 34 private val mMaxTextSize: Float = mTextView.textSize 35 36 // The maximum width the text is allowed to be (in pixels). 37 private var mWidthConstraint = Int.MAX_VALUE 38 39 // The maximum height the text is allowed to be (in pixels). 40 private var mHeightConstraint = Int.MAX_VALUE 41 42 // When {@code true} calls to {@link #requestLayout()} should be ignored. 43 private var mIgnoreRequestLayout = false 44 onMeasurenull45 fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { 46 var widthConstraint = Int.MAX_VALUE 47 if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { 48 widthConstraint = (MeasureSpec.getSize(widthMeasureSpec) - 49 mTextView.compoundPaddingLeft - mTextView.compoundPaddingRight) 50 } 51 52 var heightConstraint = Int.MAX_VALUE 53 if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED) { 54 heightConstraint = (MeasureSpec.getSize(heightMeasureSpec) - 55 mTextView.compoundPaddingTop - mTextView.compoundPaddingBottom) 56 } 57 58 if (mTextView.isLayoutRequested || 59 mWidthConstraint != widthConstraint || 60 mHeightConstraint != heightConstraint) { 61 mWidthConstraint = widthConstraint 62 mHeightConstraint = heightConstraint 63 adjustTextSize() 64 } 65 } 66 onTextChangednull67 fun onTextChanged(lengthBefore: Int, lengthAfter: Int) { 68 // The length of the text has changed, request layout to recalculate the current text 69 // size. This is necessary to workaround an optimization in TextView#checkForRelayout() 70 // which will avoid re-layout when the view has a fixed layout width. 71 if (lengthBefore != lengthAfter) { 72 mTextView.requestLayout() 73 } 74 } 75 shouldIgnoreRequestLayoutnull76 fun shouldIgnoreRequestLayout(): Boolean { 77 return mIgnoreRequestLayout 78 } 79 adjustTextSizenull80 private fun adjustTextSize() { 81 val text = mTextView.text 82 var textSize = mMaxTextSize 83 if (text.isNotEmpty() && 84 (mWidthConstraint < Int.MAX_VALUE || mHeightConstraint < Int.MAX_VALUE)) { 85 mMeasurePaint.set(mTextView.paint) 86 87 var minTextSize = 1f 88 var maxTextSize = mMaxTextSize 89 while (maxTextSize >= minTextSize) { 90 val midTextSize = Math.round((maxTextSize + minTextSize) / 2f).toFloat() 91 mMeasurePaint.textSize = midTextSize 92 93 val width = Layout.getDesiredWidth(text, mMeasurePaint) 94 val height = mMeasurePaint.getFontMetricsInt(null).toFloat() 95 if (width > mWidthConstraint || height > mHeightConstraint) { 96 maxTextSize = midTextSize - 1f 97 } else { 98 textSize = midTextSize 99 minTextSize = midTextSize + 1f 100 } 101 } 102 } 103 104 if (mTextView.textSize != textSize) { 105 mIgnoreRequestLayout = true 106 mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) 107 mIgnoreRequestLayout = false 108 } 109 } 110 }