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 }