Loading packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +12 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 } Loading Loading @@ -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 } Loading @@ -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 } Loading Loading @@ -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. Loading packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +18 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } } } Loading @@ -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 { Loading @@ -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) Loading @@ -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( Loading @@ -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. */ Loading packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +3 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt +34 −45 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -56,15 +55,13 @@ class TextAnimatorTest : SysuiTestCase() { val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { 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. Loading @@ -88,15 +85,13 @@ class TextAnimatorTest : SysuiTestCase() { val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { 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. Loading @@ -118,7 +113,8 @@ class TextAnimatorTest : SysuiTestCase() { `when`(textInterpolator.targetPaint).thenReturn(paint) val animationEndCallback = mock(Runnable::class.java) val textAnimator = TextAnimator(layout, {}).apply { val textAnimator = TextAnimator(layout, null, {}).apply { this.textInterpolator = textInterpolator this.animator = valueAnimator } Loading @@ -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 { val paint = TextPaint().apply { typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf") } `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { 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) } Loading Loading
packages/SystemUI/animation/src/com/android/systemui/animation/FontInterpolator.kt +12 −0 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 } Loading Loading @@ -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 } Loading @@ -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 } Loading Loading @@ -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. Loading
packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt +18 −5 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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) } } } Loading @@ -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 { Loading @@ -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) Loading @@ -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( Loading @@ -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. */ Loading
packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt +3 −1 Original line number Diff line number Diff line Loading @@ -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 Loading Loading @@ -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 Loading
packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt +34 −45 Original line number Diff line number Diff line Loading @@ -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 Loading @@ -56,15 +55,13 @@ class TextAnimatorTest : SysuiTestCase() { val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { 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. Loading @@ -88,15 +85,13 @@ class TextAnimatorTest : SysuiTestCase() { val paint = mock(TextPaint::class.java) `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { 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. Loading @@ -118,7 +113,8 @@ class TextAnimatorTest : SysuiTestCase() { `when`(textInterpolator.targetPaint).thenReturn(paint) val animationEndCallback = mock(Runnable::class.java) val textAnimator = TextAnimator(layout, {}).apply { val textAnimator = TextAnimator(layout, null, {}).apply { this.textInterpolator = textInterpolator this.animator = valueAnimator } Loading @@ -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 { val paint = TextPaint().apply { typeface = Typeface.createFromFile("/system/fonts/Roboto-Regular.ttf") } `when`(textInterpolator.targetPaint).thenReturn(paint) val textAnimator = TextAnimator(layout, {}).apply { 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) } Loading