Merge "Animate clock fonts with fewer discrete steps" into udc-dev

This commit is contained in:
Treehugger Robot
2023-06-01 21:27:48 +00:00
committed by Android (Google) Code Review
4 changed files with 67 additions and 51 deletions

View File

@@ -18,6 +18,7 @@ package com.android.systemui.animation
import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.util.Log
import android.util.LruCache
import android.util.MathUtils
import android.util.MathUtils.abs
@@ -114,6 +115,9 @@ class FontInterpolator {
tmpInterpKey.set(start, end, progress)
val cachedFont = interpCache[tmpInterpKey]
if (cachedFont != null) {
if (DEBUG) {
Log.d(LOG_TAG, "[$progress] Interp. cache hit for $tmpInterpKey")
}
return cachedFont
}
@@ -159,6 +163,9 @@ class FontInterpolator {
val axesCachedFont = verFontCache[tmpVarFontKey]
if (axesCachedFont != null) {
interpCache.put(InterpKey(start, end, progress), axesCachedFont)
if (DEBUG) {
Log.d(LOG_TAG, "[$progress] Axis cache hit for $tmpVarFontKey")
}
return axesCachedFont
}
@@ -168,6 +175,9 @@ class FontInterpolator {
val newFont = Font.Builder(start).setFontVariationSettings(newAxes.toTypedArray()).build()
interpCache.put(InterpKey(start, end, progress), newFont)
verFontCache.put(VarFontKey(start, newAxes), newFont)
if (DEBUG) {
Log.d(LOG_TAG, "[$progress] Cache MISS for $tmpInterpKey / $tmpVarFontKey")
}
return newFont
}
@@ -233,6 +243,8 @@ class FontInterpolator {
(v.coerceIn(min, max) / step).toInt() * step
companion object {
private const val LOG_TAG = "FontInterpolator"
private val DEBUG = Log.isLoggable(LOG_TAG, Log.DEBUG)
private val EMPTY_AXES = arrayOf<FontVariationAxis>()
// Returns true if given two font instance can be interpolated.

View File

@@ -26,6 +26,7 @@ import android.graphics.fonts.Font
import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.LruCache
import kotlin.math.roundToInt
private const val DEFAULT_ANIMATION_DURATION: Long = 300
private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
@@ -63,9 +64,9 @@ class TypefaceVariantCacheImpl(
return it
}
return TypefaceVariantCache
.createVariantTypeface(baseTypeface, fvar)
.also { cache.put(fvar, it) }
return TypefaceVariantCache.createVariantTypeface(baseTypeface, fvar).also {
cache.put(fvar, it)
}
}
}
@@ -74,7 +75,6 @@ class TypefaceVariantCacheImpl(
*
* Currently this class can provide text style animation for text weight and text size. For example
* the simple view that draws text with animating text size is like as follows:
*
* <pre> <code>
* ```
* class SimpleTextAnimation : View {
@@ -97,6 +97,7 @@ class TypefaceVariantCacheImpl(
*/
class TextAnimator(
layout: Layout,
numberOfAnimationSteps: Int? = null, // Only do this number of discrete animation steps.
private val invalidateCallback: () -> Unit,
) {
var typefaceCache: TypefaceVariantCache = TypefaceVariantCacheImpl(layout.paint.typeface)
@@ -112,7 +113,8 @@ class TextAnimator(
ValueAnimator.ofFloat(1f).apply {
duration = DEFAULT_ANIMATION_DURATION
addUpdateListener {
textInterpolator.progress = it.animatedValue as Float
textInterpolator.progress =
calculateProgress(it.animatedValue as Float, numberOfAnimationSteps)
invalidateCallback()
}
addListener(
@@ -123,6 +125,17 @@ class TextAnimator(
)
}
private fun calculateProgress(animProgress: Float, numberOfAnimationSteps: Int?): Float {
if (numberOfAnimationSteps != null) {
// This clamps the progress to the nearest value of "numberOfAnimationSteps"
// discrete values between 0 and 1f.
return (animProgress * numberOfAnimationSteps).roundToInt() /
numberOfAnimationSteps.toFloat()
}
return animProgress
}
sealed class PositionedGlyph {
/** Mutable X coordinate of the glyph position relative from drawing offset. */

View File

@@ -74,7 +74,8 @@ class AnimatableClockView @JvmOverloads constructor(
private var onTextAnimatorInitialized: Runnable? = null
@VisibleForTesting var textAnimatorFactory: (Layout, () -> Unit) -> TextAnimator =
{ layout, invalidateCb -> TextAnimator(layout, invalidateCb) }
{ layout, invalidateCb ->
TextAnimator(layout, NUM_CLOCK_FONT_ANIMATION_STEPS, invalidateCb) }
@VisibleForTesting var isAnimationEnabled: Boolean = true
@VisibleForTesting var timeOverrideInMillis: Long? = null
@@ -567,6 +568,7 @@ class AnimatableClockView @JvmOverloads constructor(
private const val CHARGE_ANIM_DURATION_PHASE_0: Long = 500
private const val CHARGE_ANIM_DURATION_PHASE_1: Long = 1000
private const val COLOR_ANIM_DURATION: Long = 400
private const val NUM_CLOCK_FONT_ANIMATION_STEPS = 30
// Constants for the animation
private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED

View File

@@ -26,18 +26,17 @@ import android.text.TextPaint
import androidx.test.filters.SmallTest
import com.android.systemui.SysuiTestCase
import com.google.common.truth.Truth.assertThat
import kotlin.math.ceil
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
import org.mockito.Mockito.`when`
import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import kotlin.math.ceil
import org.mockito.Mockito.`when`
@RunWith(AndroidTestingRunner::class)
@SmallTest
@@ -56,15 +55,13 @@ class TextAnimatorTest : SysuiTestCase() {
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
val textAnimator =
TextAnimator(layout, null, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
textAnimator.setTextStyle(
weight = 400,
animate = true
)
textAnimator.setTextStyle(weight = 400, animate = true)
// If animation is requested, the base state should be rebased and the target state should
// be updated.
@@ -88,15 +85,13 @@ class TextAnimatorTest : SysuiTestCase() {
val paint = mock(TextPaint::class.java)
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
val textAnimator =
TextAnimator(layout, null, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
textAnimator.setTextStyle(
weight = 400,
animate = false
)
textAnimator.setTextStyle(weight = 400, animate = false)
// If animation is not requested, the progress should be 1 which is end of animation and the
// base state is rebased to target state by calling rebase.
@@ -118,15 +113,16 @@ class TextAnimatorTest : SysuiTestCase() {
`when`(textInterpolator.targetPaint).thenReturn(paint)
val animationEndCallback = mock(Runnable::class.java)
val textAnimator = TextAnimator(layout, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
val textAnimator =
TextAnimator(layout, null, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
textAnimator.setTextStyle(
weight = 400,
animate = true,
onAnimationEnd = animationEndCallback
weight = 400,
animate = true,
onAnimationEnd = animationEndCallback
)
// Verify animationEnd callback has been added.
@@ -144,34 +140,27 @@ class TextAnimatorTest : SysuiTestCase() {
val layout = makeLayout("Hello, World", PAINT)
val valueAnimator = mock(ValueAnimator::class.java)
val textInterpolator = mock(TextInterpolator::class.java)
val paint = TextPaint().apply {
typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
}
val paint =
TextPaint().apply {
typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf")
}
`when`(textInterpolator.targetPaint).thenReturn(paint)
val textAnimator = TextAnimator(layout, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
val textAnimator =
TextAnimator(layout, null, {}).apply {
this.textInterpolator = textInterpolator
this.animator = valueAnimator
}
textAnimator.setTextStyle(
weight = 400,
animate = true
)
textAnimator.setTextStyle(weight = 400, animate = true)
val prevTypeface = paint.typeface
textAnimator.setTextStyle(
weight = 700,
animate = true
)
textAnimator.setTextStyle(weight = 700, animate = true)
assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface)
textAnimator.setTextStyle(
weight = 400,
animate = true
)
textAnimator.setTextStyle(weight = 400, animate = true)
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}