Merge "Animate clock fonts with fewer discrete steps" into udc-dev
This commit is contained in:
committed by
Android (Google) Code Review
commit
3c4b404c50
@@ -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.
|
||||
|
||||
@@ -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. */
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user