diff options
author | 2025-03-07 17:30:05 +0000 | |
---|---|---|
committer | 2025-03-07 22:45:34 +0000 | |
commit | 0a737d0746cb8780f84ec899767d6135ba7511d4 (patch) | |
tree | 250c9de3417bc0359e659a9d7fd5f1c9caa71fb0 | |
parent | 31d719591af4a33bdc2bed2a185922b3b8ce0ddc (diff) |
Normalize TextInterpolator rebase operations
Bug: 401521058
Test: Manually verified edge case
Flag: com.android.systemui.shared.clock_reactive_variants
Change-Id: I2af270f72ce402798e56446102be2ac6a1d5ba27
7 files changed, 92 insertions, 28 deletions
diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt index 4a39cff388a9..4f01d7fcdb51 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt @@ -82,6 +82,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio } } +interface TextAnimatorListener : TextInterpolatorListener { + fun onInvalidate() {} +} + /** * This class provides text animation between two styles. * @@ -110,13 +114,19 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio class TextAnimator( layout: Layout, private val typefaceCache: TypefaceVariantCache, - private val invalidateCallback: () -> Unit = {}, + private val listener: TextAnimatorListener? = null, ) { - @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache) + var textInterpolator = TextInterpolator(layout, typefaceCache, listener) @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) } var animator: ValueAnimator? = null + val progress: Float + get() = textInterpolator.progress + + val linearProgress: Float + get() = textInterpolator.linearProgress + val fontVariationUtils = FontVariationUtils() sealed class PositionedGlyph { @@ -288,8 +298,9 @@ class TextAnimator( animator = buildAnimator(animation).apply { start() } } else { textInterpolator.progress = 1f + textInterpolator.linearProgress = 1f textInterpolator.rebase() - invalidateCallback() + listener?.onInvalidate() } } @@ -302,7 +313,7 @@ class TextAnimator( addUpdateListener { textInterpolator.progress = it.animatedValue as Float textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat() - invalidateCallback() + listener?.onInvalidate() } addListener( diff --git a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt index 457f45388062..22c5258edb58 100644 --- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt +++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextInterpolator.kt @@ -27,8 +27,19 @@ import android.util.MathUtils import com.android.internal.graphics.ColorUtils import java.lang.Math.max +interface TextInterpolatorListener { + fun onPaintModified() {} + + fun onRebased() {} +} + /** Provide text style linear interpolation for plain text. */ -class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) { +class TextInterpolator( + layout: Layout, + var typefaceCache: TypefaceVariantCache, + private val listener: TextInterpolatorListener? = null, +) { + /** * Returns base paint used for interpolation. * @@ -136,6 +147,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) */ fun onTargetPaintModified() { updatePositionsAndFonts(shapeText(layout, targetPaint), updateBase = false) + listener?.onPaintModified() } /** @@ -146,6 +158,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) */ fun onBasePaintModified() { updatePositionsAndFonts(shapeText(layout, basePaint), updateBase = true) + listener?.onPaintModified() } /** @@ -204,6 +217,7 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) */ fun rebase() { if (progress == 0f) { + listener?.onRebased() return } else if (progress == 1f) { basePaint.set(targetPaint) @@ -233,6 +247,8 @@ class TextInterpolator(layout: Layout, var typefaceCache: TypefaceVariantCache) } progress = 0f + linearProgress = 0f + listener?.onRebased() } /** diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt index 6e29e6932629..2f819d183bcc 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt @@ -34,6 +34,7 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GlyphCallback import com.android.systemui.animation.TextAnimator +import com.android.systemui.animation.TextAnimatorListener import com.android.systemui.animation.TypefaceVariantCacheImpl import com.android.systemui.customization.R import com.android.systemui.log.core.LogLevel @@ -100,7 +101,13 @@ constructor( @VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> val cache = TypefaceVariantCacheImpl(layout.paint.typeface, NUM_CLOCK_FONT_ANIMATION_STEPS) - TextAnimator(layout, cache, invalidateCb) + TextAnimator( + layout, + cache, + object : TextAnimatorListener { + override fun onInvalidate() = invalidateCb() + }, + ) } // Used by screenshot tests to provide stability diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt index 2af25fe339a2..da9e26ae3812 100644 --- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt +++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt @@ -24,7 +24,6 @@ import android.graphics.PorterDuff import android.graphics.PorterDuffXfermode import android.graphics.Rect import android.os.VibrationEffect -import android.text.Layout import android.text.TextPaint import android.util.AttributeSet import android.util.Log @@ -39,6 +38,7 @@ import com.android.app.animation.Interpolators import com.android.internal.annotations.VisibleForTesting import com.android.systemui.animation.GSFAxes import com.android.systemui.animation.TextAnimator +import com.android.systemui.animation.TextAnimatorListener import com.android.systemui.customization.R import com.android.systemui.plugins.clocks.ClockFontAxisSetting import com.android.systemui.plugins.clocks.ClockFontAxisSetting.Companion.replace @@ -175,11 +175,6 @@ open class SimpleDigitalClockTextView( private val typefaceCache = clockCtx.typefaceCache.getVariantCache("") - @VisibleForTesting - var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator = { layout, invalidateCb -> - TextAnimator(layout, typefaceCache, invalidateCb) - } - var verticalAlignment: VerticalAlignment = VerticalAlignment.BASELINE var horizontalAlignment: HorizontalAlignment = HorizontalAlignment.CENTER @@ -247,7 +242,18 @@ open class SimpleDigitalClockTextView( val layout = this.layout if (layout != null) { if (!this::textAnimator.isInitialized) { - textAnimator = textAnimatorFactory(layout, ::invalidate) + textAnimator = + TextAnimator( + layout, + typefaceCache, + object : TextAnimatorListener { + override fun onInvalidate() = invalidate() + + override fun onRebased() = updateTextBounds() + + override fun onPaintModified() = updateTextBounds() + }, + ) setInterpolatorPaint() } else { textAnimator.updateLayout(layout) @@ -272,7 +278,7 @@ open class SimpleDigitalClockTextView( override fun onDraw(canvas: Canvas) { logger.onDraw(textAnimator.textInterpolator.shapedText) - val interpProgress = getInterpolatedProgress() + val interpProgress = textAnimator.progress val interpBounds = getInterpolatedTextBounds(interpProgress) if (interpProgress != drawnProgress) { drawnProgress = interpProgress @@ -336,7 +342,6 @@ open class SimpleDigitalClockTextView( interpolator = aodDozingInterpolator, ), ) - updateTextBoundsForTextAnimator() if (!isAnimated) { requestLayout() @@ -367,11 +372,9 @@ open class SimpleDigitalClockTextView( duration = CHARGE_ANIMATION_DURATION, ), ) - updateTextBoundsForTextAnimator() }, ), ) - updateTextBoundsForTextAnimator() } fun animateFidget(x: Float, y: Float) = animateFidget(0L) @@ -401,11 +404,9 @@ open class SimpleDigitalClockTextView( interpolator = FIDGET_INTERPOLATOR, ), ) - updateTextBoundsForTextAnimator() }, ), ) - updateTextBoundsForTextAnimator() } fun refreshText() { @@ -428,12 +429,8 @@ open class SimpleDigitalClockTextView( id == R.id.MINUTE_SECOND_DIGIT } - private fun getInterpolatedProgress(): Float { - return textAnimator.animator?.let { it.animatedValue as Float } ?: 1f - } - /** Returns the interpolated text bounding rect based on interpolation progress */ - private fun getInterpolatedTextBounds(progress: Float = getInterpolatedProgress()): VRectF { + private fun getInterpolatedTextBounds(progress: Float = textAnimator.progress): VRectF { if (progress <= 0f) { return prevTextBounds } else if (!textAnimator.isRunning || progress >= 1f) { @@ -487,6 +484,15 @@ open class SimpleDigitalClockTextView( MeasureSpec.makeMeasureSpec(measureBounds.x.roundToInt(), mode.x), MeasureSpec.makeMeasureSpec(measureBounds.y.roundToInt(), mode.y), ) + + logger.d({ + val size = VPointF.fromLong(long1) + val mode = VPoint.fromLong(long2) + "setInterpolatedSize(size=$size, mode=$mode)" + }) { + long1 = measureBounds.toLong() + long2 = mode.toLong() + } } /** Set the location of the view to match the interpolated text bounds */ @@ -514,6 +520,9 @@ open class SimpleDigitalClockTextView( targetRect.bottom.roundToInt(), ) onViewBoundsChanged?.let { it(targetRect) } + logger.d({ "setInterpolatedLocation(${VRectF.fromLong(long1)})" }) { + long1 = targetRect.toLong() + } return targetRect } @@ -616,7 +625,8 @@ open class SimpleDigitalClockTextView( * rebase if previous animator is canceled so basePaint will store the state we transition from * and targetPaint will store the state we transition to */ - private fun updateTextBoundsForTextAnimator() { + private fun updateTextBounds() { + drawnProgress = null prevTextBounds = textAnimator.textInterpolator.basePaint.getTextBounds(text) targetTextBounds = textAnimator.textInterpolator.targetPaint.getTextBounds(text) } diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt index f9ff75d5fdc8..5b67edda73cc 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt @@ -55,9 +55,9 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) { - d({ "onLayout($bool1, ${VRect(long1.toULong())})" }) { + d({ "onLayout($bool1, ${VRect.fromLong(long1)})" }) { bool1 = changed - long1 = VRect(left, top, right, bottom).data.toLong() + long1 = VRect(left, top, right, bottom).toLong() } } @@ -116,7 +116,7 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) : } fun animateFidget(x: Float, y: Float) { - d({ "animateFidget(${VPointF(long1.toULong())})" }) { long1 = VPointF(x, y).data.toLong() } + d({ "animateFidget(${VPointF.fromLong(long1)})" }) { long1 = VPointF(x, y).toLong() } } companion object { diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt index 1fb37ec28835..de62f9c8be74 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VPoint.kt @@ -56,6 +56,8 @@ value class VPointF(val data: ULong) { fun toPointF() = PointF(x, y) + fun toLong(): Long = data.toLong() + fun lengthSq(): Float = x * x + y * y fun length(): Float = sqrt(lengthSq()) @@ -110,6 +112,8 @@ value class VPointF(val data: ULong) { companion object { val ZERO = VPointF(0, 0) + fun fromLong(data: Long) = VPointF(data.toULong()) + fun max(lhs: VPointF, rhs: VPointF) = VPointF(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPointF, rhs: VPointF) = VPointF(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) @@ -148,6 +152,8 @@ value class VPoint(val data: ULong) { fun toPoint() = Point(x, y) + fun toLong(): Long = data.toLong() + fun abs() = VPoint(abs(x), abs(y)) operator fun component1(): Int = x @@ -191,6 +197,8 @@ value class VPoint(val data: ULong) { companion object { val ZERO = VPoint(0, 0) + fun fromLong(data: Long) = VPoint(data.toULong()) + fun max(lhs: VPoint, rhs: VPoint) = VPoint(max(lhs.x, rhs.x), max(lhs.y, rhs.y)) fun min(lhs: VPoint, rhs: VPoint) = VPoint(min(lhs.x, rhs.x), min(lhs.y, rhs.y)) diff --git a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt index 3c1adf22a405..1bd29aa6a073 100644 --- a/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt +++ b/packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/VRect.kt @@ -84,6 +84,10 @@ value class VRectF(val data: ULong) { val size: VPointF get() = VPointF(width, height) + fun toRectF(): RectF = RectF(left, top, right, bottom) + + fun toLong(): Long = data.toLong() + override fun toString() = "($left, $top) -> ($right, $bottom)" companion object { @@ -91,6 +95,8 @@ value class VRectF(val data: ULong) { private fun fromBits(value: Short): Float = Half.toFloat(Half.intBitsToHalf(value.toInt())) + fun fromLong(data: Long) = VRectF(data.toULong()) + fun fromCenter(center: VPointF, size: VPointF): VRectF { return VRectF( center.x - size.x / 2, @@ -162,11 +168,17 @@ value class VRect(val data: ULong) { val size: VPoint get() = VPoint(width, height) + fun toRect(): Rect = Rect(left, top, right, bottom) + + fun toLong(): Long = data.toLong() + override fun toString() = "($left, $top) -> ($right, $bottom)" companion object { val ZERO = VRect(0, 0, 0, 0) + fun fromLong(data: Long) = VRect(data.toULong()) + fun fromCenter(center: VPoint, size: VPoint): VRect { return VRect( (center.x - size.x / 2).toShort(), |