| /* |
| * Copyright (C) 2020 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.MeasureSpec |
| import android.widget.TextView |
| |
| /** |
| * A TextView which automatically re-sizes its text to fit within its boundaries. |
| */ |
| class TextSizeHelper(private val mTextView: TextView) { |
| |
| // Text paint used for measuring. |
| private val mMeasurePaint = TextPaint() |
| |
| // The maximum size the text is allowed to be (in pixels). |
| private val mMaxTextSize: Float = mTextView.textSize |
| |
| // The maximum width the text is allowed to be (in pixels). |
| private var mWidthConstraint = Int.MAX_VALUE |
| |
| // The maximum height the text is allowed to be (in pixels). |
| private var mHeightConstraint = Int.MAX_VALUE |
| |
| // When {@code true} calls to {@link #requestLayout()} should be ignored. |
| private var mIgnoreRequestLayout = false |
| |
| fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) { |
| var widthConstraint = Int.MAX_VALUE |
| if (MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.UNSPECIFIED) { |
| widthConstraint = (MeasureSpec.getSize(widthMeasureSpec) - |
| mTextView.compoundPaddingLeft - mTextView.compoundPaddingRight) |
| } |
| |
| var heightConstraint = Int.MAX_VALUE |
| if (MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.UNSPECIFIED) { |
| heightConstraint = (MeasureSpec.getSize(heightMeasureSpec) - |
| mTextView.compoundPaddingTop - mTextView.compoundPaddingBottom) |
| } |
| |
| if (mTextView.isLayoutRequested || |
| mWidthConstraint != widthConstraint || |
| mHeightConstraint != heightConstraint) { |
| mWidthConstraint = widthConstraint |
| mHeightConstraint = heightConstraint |
| adjustTextSize() |
| } |
| } |
| |
| fun onTextChanged(lengthBefore: Int, lengthAfter: Int) { |
| // 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() |
| } |
| } |
| |
| fun shouldIgnoreRequestLayout(): Boolean { |
| return mIgnoreRequestLayout |
| } |
| |
| private fun adjustTextSize() { |
| val text = mTextView.text |
| var textSize = mMaxTextSize |
| if (text.isNotEmpty() && |
| (mWidthConstraint < Int.MAX_VALUE || mHeightConstraint < Int.MAX_VALUE)) { |
| mMeasurePaint.set(mTextView.paint) |
| |
| var minTextSize = 1f |
| var maxTextSize = mMaxTextSize |
| while (maxTextSize >= minTextSize) { |
| val midTextSize = Math.round((maxTextSize + minTextSize) / 2f).toFloat() |
| mMeasurePaint.textSize = midTextSize |
| |
| val width = Layout.getDesiredWidth(text, mMeasurePaint) |
| val height = mMeasurePaint.getFontMetricsInt(null).toFloat() |
| if (width > mWidthConstraint || height > mHeightConstraint) { |
| maxTextSize = midTextSize - 1f |
| } else { |
| textSize = midTextSize |
| minTextSize = midTextSize + 1f |
| } |
| } |
| } |
| |
| if (mTextView.textSize != textSize) { |
| mIgnoreRequestLayout = true |
| mTextView.setTextSize(TypedValue.COMPLEX_UNIT_PX, textSize) |
| mIgnoreRequestLayout = false |
| } |
| } |
| } |