blob: 6ca966f05b686a903dca2e032269e0e875c88444 [file] [log] [blame]
/*
* 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
}
}
}