summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt297
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/AnimatableClockView.kt27
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt3
-rw-r--r--packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/SimpleDigitalClockTextView.kt186
-rw-r--r--packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt55
-rw-r--r--packages/SystemUI/plugin/src/com/android/systemui/plugins/clocks/ClockLogger.kt15
-rw-r--r--packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt28
7 files changed, 289 insertions, 322 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 b9f9bc7e2daa..5b073e49192a 100644
--- a/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
+++ b/packages/SystemUI/animation/src/com/android/systemui/animation/TextAnimator.kt
@@ -27,9 +27,8 @@ import android.graphics.fonts.FontVariationAxis
import android.text.Layout
import android.util.Log
import android.util.LruCache
-
-private const val DEFAULT_ANIMATION_DURATION: Long = 300
-private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
+import androidx.annotation.VisibleForTesting
+import com.android.app.animation.Interpolators
typealias GlyphCallback = (TextAnimator.PositionedGlyph, Float) -> Unit
@@ -76,6 +75,10 @@ class TypefaceVariantCacheImpl(var baseTypeface: Typeface, override val animatio
cache.put(fvar, it)
}
}
+
+ companion object {
+ private const val TYPEFACE_CACHE_MAX_ENTRIES = 5
+ }
}
/**
@@ -108,25 +111,12 @@ class TextAnimator(
private val typefaceCache: TypefaceVariantCache,
private val invalidateCallback: () -> Unit = {},
) {
- // Following two members are for mutable for testing purposes.
- public var textInterpolator = TextInterpolator(layout, typefaceCache)
- public var animator =
- ValueAnimator.ofFloat(1f).apply {
- duration = DEFAULT_ANIMATION_DURATION
- addUpdateListener {
- textInterpolator.progress = it.animatedValue as Float
- textInterpolator.linearProgress =
- it.currentPlayTime.toFloat() / it.duration.toFloat()
- invalidateCallback()
- }
- addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) = textInterpolator.rebase()
+ @VisibleForTesting var textInterpolator = TextInterpolator(layout, typefaceCache)
+ @VisibleForTesting var createAnimator: () -> ValueAnimator = { ValueAnimator.ofFloat(1f) }
- override fun onAnimationCancel(animation: Animator) = textInterpolator.rebase()
- }
- )
- }
+ var animator: ValueAnimator? = null
+
+ val fontVariationUtils = FontVariationUtils()
sealed class PositionedGlyph {
/** Mutable X coordinate of the glyph position relative from drawing offset. */
@@ -165,8 +155,6 @@ class TextAnimator(
protected set
}
- private val fontVariationUtils = FontVariationUtils()
-
fun updateLayout(layout: Layout, textSize: Float = -1f) {
textInterpolator.layout = layout
@@ -178,9 +166,8 @@ class TextAnimator(
}
}
- fun isRunning(): Boolean {
- return animator.isRunning
- }
+ val isRunning: Boolean
+ get() = animator?.isRunning ?: false
/**
* GlyphFilter applied just before drawing to canvas for tweaking positions and text size.
@@ -237,110 +224,110 @@ class TextAnimator(
fun draw(c: Canvas) = textInterpolator.draw(c)
- /**
- * Set text style with animation.
- *
- * ```
- * By passing -1 to weight, the view preserve the current weight.
- * By passing -1 to textSize, the view preserve the current text size.
- * By passing -1 to duration, the default text animation, 1000ms, is used.
- * By passing false to animate, the text will be updated without animation.
- * ```
- *
- * @param fvar an optional text fontVariationSettings.
- * @param textSize an optional font size.
- * @param colors an optional colors array that must be the same size as numLines passed to the
- * TextInterpolator
- * @param strokeWidth an optional paint stroke width
- * @param animate an optional boolean indicating true for showing style transition as animation,
- * false for immediate style transition. True by default.
- * @param duration an optional animation duration in milliseconds. This is ignored if animate is
- * false.
- * @param interpolator an optional time interpolator. If null is passed, last set interpolator
- * will be used. This is ignored if animate is false.
- */
- fun setTextStyle(
- fvar: String? = "",
- textSize: Float = -1f,
- color: Int? = null,
- strokeWidth: Float = -1f,
- animate: Boolean = true,
- duration: Long = -1L,
- interpolator: TimeInterpolator? = null,
- delay: Long = 0,
- onAnimationEnd: Runnable? = null,
+ /** Style spec to use when rendering the font */
+ data class Style(
+ val fVar: String? = null,
+ val textSize: Float? = null,
+ val color: Int? = null,
+ val strokeWidth: Float? = null,
) {
- setTextStyleInternal(
- fvar,
- textSize,
- color,
- strokeWidth,
- animate,
- duration,
- interpolator,
- delay,
- onAnimationEnd,
- updateLayoutOnFailure = true,
- )
+ fun withUpdatedFVar(
+ fontVariationUtils: FontVariationUtils,
+ weight: Int = -1,
+ width: Int = -1,
+ opticalSize: Int = -1,
+ roundness: Int = -1,
+ ): Style {
+ return this.copy(
+ fVar =
+ fontVariationUtils.updateFontVariation(
+ weight = weight,
+ width = width,
+ opticalSize = opticalSize,
+ roundness = roundness,
+ )
+ )
+ }
}
- private fun setTextStyleInternal(
- fvar: String?,
- textSize: Float,
- color: Int?,
- strokeWidth: Float,
- animate: Boolean,
- duration: Long,
- interpolator: TimeInterpolator?,
- delay: Long,
- onAnimationEnd: Runnable?,
- updateLayoutOnFailure: Boolean,
+ /** Animation Spec for use when style changes should be animated */
+ data class Animation(
+ val animate: Boolean = true,
+ val startDelay: Long = 0,
+ val duration: Long = DEFAULT_ANIMATION_DURATION,
+ val interpolator: TimeInterpolator = Interpolators.LINEAR,
+ val onAnimationEnd: Runnable? = null,
) {
- try {
- if (animate) {
- animator.cancel()
- textInterpolator.rebase()
+ fun configureAnimator(animator: Animator) {
+ animator.startDelay = startDelay
+ animator.duration = duration
+ animator.interpolator = interpolator
+ if (onAnimationEnd != null) {
+ animator.addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animation: Animator) {
+ onAnimationEnd.run()
+ }
+ }
+ )
}
+ }
- if (textSize >= 0) {
- textInterpolator.targetPaint.textSize = textSize
- }
- if (!fvar.isNullOrBlank()) {
- textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(fvar)
- }
- if (color != null) {
- textInterpolator.targetPaint.color = color
- }
- if (strokeWidth >= 0F) {
- textInterpolator.targetPaint.strokeWidth = strokeWidth
+ companion object {
+ val DISABLED = Animation(animate = false)
+ }
+ }
+
+ /** Sets the text style, optionally with animation */
+ fun setTextStyle(style: Style, animation: Animation = Animation.DISABLED) {
+ animator?.cancel()
+ setTextStyleInternal(style, rebase = animation.animate)
+
+ if (animation.animate) {
+ animator = buildAnimator(animation).apply { start() }
+ } else {
+ textInterpolator.progress = 1f
+ textInterpolator.rebase()
+ invalidateCallback()
+ }
+ }
+
+ /** Builds a ValueAnimator from the specified animation parameters */
+ private fun buildAnimator(animation: Animation): ValueAnimator {
+ return createAnimator().apply {
+ duration = DEFAULT_ANIMATION_DURATION
+ animation.configureAnimator(this)
+
+ addUpdateListener {
+ textInterpolator.progress = it.animatedValue as Float
+ textInterpolator.linearProgress = it.currentPlayTime / it.duration.toFloat()
+ invalidateCallback()
}
- textInterpolator.onTargetPaintModified()
- if (animate) {
- animator.startDelay = delay
- animator.duration = if (duration == -1L) DEFAULT_ANIMATION_DURATION else duration
- interpolator?.let { animator.interpolator = it }
- if (onAnimationEnd != null) {
- animator.addListener(
- object : AnimatorListenerAdapter() {
- override fun onAnimationEnd(animation: Animator) {
- onAnimationEnd.run()
- animator.removeListener(this)
- }
-
- override fun onAnimationCancel(animation: Animator) {
- animator.removeListener(this)
- }
- }
- )
+ addListener(
+ object : AnimatorListenerAdapter() {
+ override fun onAnimationEnd(animator: Animator) = textInterpolator.rebase()
+
+ override fun onAnimationCancel(animator: Animator) = textInterpolator.rebase()
}
- animator.start()
- } else {
- // No animation is requested, thus set base and target state to the same state.
- textInterpolator.progress = 1f
- textInterpolator.rebase()
- invalidateCallback()
+ )
+ }
+ }
+
+ private fun setTextStyleInternal(
+ style: Style,
+ rebase: Boolean,
+ updateLayoutOnFailure: Boolean = true,
+ ) {
+ try {
+ if (rebase) textInterpolator.rebase()
+ style.color?.let { textInterpolator.targetPaint.color = it }
+ style.textSize?.let { textInterpolator.targetPaint.textSize = it }
+ style.strokeWidth?.let { textInterpolator.targetPaint.strokeWidth = it }
+ style.fVar?.let {
+ textInterpolator.targetPaint.typeface = typefaceCache.getTypefaceForVariant(it)
}
+ textInterpolator.onTargetPaintModified()
} catch (ex: IllegalArgumentException) {
if (updateLayoutOnFailure) {
Log.e(
@@ -351,81 +338,15 @@ class TextAnimator(
)
updateLayout(textInterpolator.layout)
- setTextStyleInternal(
- fvar,
- textSize,
- color,
- strokeWidth,
- animate,
- duration,
- interpolator,
- delay,
- onAnimationEnd,
- updateLayoutOnFailure = false,
- )
+ setTextStyleInternal(style, rebase, updateLayoutOnFailure = false)
} else {
throw ex
}
}
}
- /**
- * Set text style with animation. Similar as
- *
- * ```
- * fun setTextStyle(
- * fvar: String? = "",
- * textSize: Float = -1f,
- * color: Int? = null,
- * strokeWidth: Float = -1f,
- * animate: Boolean = true,
- * duration: Long = -1L,
- * interpolator: TimeInterpolator? = null,
- * delay: Long = 0,
- * onAnimationEnd: Runnable? = null
- * )
- * ```
- *
- * @param weight an optional style value for `wght` in fontVariationSettings.
- * @param width an optional style value for `wdth` in fontVariationSettings.
- * @param opticalSize an optional style value for `opsz` in fontVariationSettings.
- * @param roundness an optional style value for `ROND` in fontVariationSettings.
- */
- fun setTextStyle(
- weight: Int = -1,
- width: Int = -1,
- opticalSize: Int = -1,
- roundness: Int = -1,
- textSize: Float = -1f,
- color: Int? = null,
- strokeWidth: Float = -1f,
- animate: Boolean = true,
- duration: Long = -1L,
- interpolator: TimeInterpolator? = null,
- delay: Long = 0,
- onAnimationEnd: Runnable? = null,
- ) {
- setTextStyleInternal(
- fvar =
- fontVariationUtils.updateFontVariation(
- weight = weight,
- width = width,
- opticalSize = opticalSize,
- roundness = roundness,
- ),
- textSize = textSize,
- color = color,
- strokeWidth = strokeWidth,
- animate = animate,
- duration = duration,
- interpolator = interpolator,
- delay = delay,
- onAnimationEnd = onAnimationEnd,
- updateLayoutOnFailure = true,
- )
- }
-
companion object {
private val TAG = TextAnimator::class.simpleName!!
+ const val DEFAULT_ANIMATION_DURATION = 300L
}
}
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 b76656d78cc4..4bf0ceb51784 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
@@ -366,7 +366,7 @@ constructor(
fun animateCharge(isDozing: () -> Boolean) {
// Skip charge animation if dozing animation is already playing.
- if (textAnimator == null || textAnimator!!.isRunning()) {
+ if (textAnimator == null || textAnimator!!.isRunning) {
return
}
@@ -444,29 +444,28 @@ constructor(
delay: Long,
onAnimationEnd: Runnable?,
) {
- textAnimator?.let {
- it.setTextStyle(
- weight = weight,
- color = color,
+ val style = TextAnimator.Style(color = color)
+ val animation =
+ TextAnimator.Animation(
animate = animate && isAnimationEnabled,
duration = duration,
- interpolator = interpolator,
- delay = delay,
+ interpolator = interpolator ?: Interpolators.LINEAR,
+ startDelay = delay,
onAnimationEnd = onAnimationEnd,
)
+ textAnimator?.let {
+ it.setTextStyle(
+ style.withUpdatedFVar(it.fontVariationUtils, weight = weight),
+ animation,
+ )
it.glyphFilter = glyphFilter
}
?: run {
// when the text animator is set, update its start values
onTextAnimatorInitialized = { textAnimator ->
textAnimator.setTextStyle(
- weight = weight,
- color = color,
- animate = false,
- duration = duration,
- interpolator = interpolator,
- delay = delay,
- onAnimationEnd = onAnimationEnd,
+ style.withUpdatedFVar(textAnimator.fontVariationUtils, weight = weight),
+ animation.copy(animate = false),
)
textAnimator.glyphFilter = glyphFilter
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
index a5adfa2a1ac6..0b7ea1a335ef 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/SimpleDigitalHandLayerController.kt
@@ -21,6 +21,7 @@ import android.view.ViewGroup
import android.view.animation.Interpolator
import android.widget.RelativeLayout
import androidx.annotation.VisibleForTesting
+import com.android.systemui.animation.TextAnimator
import com.android.systemui.customization.R
import com.android.systemui.log.core.Logger
import com.android.systemui.plugins.clocks.AlarmData
@@ -65,7 +66,7 @@ data class DigitalAlignment(
data class FontTextStyle(
val lineHeight: Float? = null,
val fontSizeScale: Float? = null,
- val transitionDuration: Long = -1L,
+ val transitionDuration: Long = TextAnimator.DEFAULT_ANIMATION_DURATION,
val transitionInterpolator: Interpolator? = null,
)
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 92fa6b5be1ed..8317aa39ef2b 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
@@ -33,7 +33,9 @@ import android.util.MathUtils
import android.util.TypedValue
import android.view.View.MeasureSpec.EXACTLY
import android.view.animation.Interpolator
+import android.view.animation.PathInterpolator
import android.widget.TextView
+import com.android.app.animation.Interpolators
import com.android.internal.annotations.VisibleForTesting
import com.android.systemui.animation.GSFAxes
import com.android.systemui.animation.TextAnimator
@@ -84,17 +86,20 @@ open class SimpleDigitalClockTextView(
else -> listOf(FLEX_AOD_SMALL_WEIGHT_AXIS, FLEX_AOD_WIDTH_AXIS)
}
- private var lsFontVariation =
- if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS).toFVar()
- else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS).toFVar()
+ private var lsFontVariation: String
+ private var aodFontVariation: String
+ private var fidgetFontVariation: String
- private var aodFontVariation = run {
+ init {
val roundAxis = if (!isLegacyFlex) ROUND_AXIS else FLEX_ROUND_AXIS
- (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar()
- }
+ val lsFontAxes =
+ if (!isLegacyFlex) listOf(LS_WEIGHT_AXIS, WIDTH_AXIS, ROUND_AXIS, SLANT_AXIS)
+ else listOf(FLEX_LS_WEIGHT_AXIS, FLEX_LS_WIDTH_AXIS, FLEX_ROUND_AXIS, SLANT_AXIS)
- // TODO(b/374306512): Fidget endpoint to spec
- private var fidgetFontVariation = aodFontVariation
+ lsFontVariation = lsFontAxes.toFVar()
+ aodFontVariation = (fixedAodAxes + listOf(roundAxis, SLANT_AXIS)).toFVar()
+ fidgetFontVariation = buildFidgetVariation(lsFontAxes).toFVar()
+ }
private val parser = DimensionParser(clockCtx.context)
var maxSingleDigitHeight = -1
@@ -121,7 +126,7 @@ open class SimpleDigitalClockTextView(
protected val logger = ClockLogger(this, clockCtx.messageBuffer, this::class.simpleName!!)
get() = field ?: ClockLogger.INIT_LOGGER
- private var aodDozingInterpolator: Interpolator? = null
+ private var aodDozingInterpolator: Interpolator = Interpolators.LINEAR
@VisibleForTesting lateinit var textAnimator: TextAnimator
@@ -149,7 +154,7 @@ open class SimpleDigitalClockTextView(
lockscreenColor = color
lockScreenPaint.color = lockscreenColor
if (dozeFraction < 1f) {
- textAnimator.setTextStyle(color = lockscreenColor, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style(color = lockscreenColor))
}
invalidate()
}
@@ -157,10 +162,8 @@ open class SimpleDigitalClockTextView(
fun updateAxes(lsAxes: List<ClockFontAxisSetting>) {
lsFontVariation = lsAxes.toFVar()
aodFontVariation = lsAxes.replace(fixedAodAxes).toFVar()
- logger.i({ "updateAxes(LS = $str1, AOD = $str2)" }) {
- str1 = lsFontVariation
- str2 = aodFontVariation
- }
+ fidgetFontVariation = buildFidgetVariation(lsAxes).toFVar()
+ logger.updateAxes(lsFontVariation, aodFontVariation)
lockScreenPaint.typeface = typefaceCache.getTypefaceForVariant(lsFontVariation)
typeface = lockScreenPaint.typeface
@@ -168,13 +171,28 @@ open class SimpleDigitalClockTextView(
lockScreenPaint.getTextBounds(text, 0, text.length, textBounds)
targetTextBounds.set(textBounds)
- textAnimator.setTextStyle(fvar = lsFontVariation, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style(fVar = lsFontVariation))
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
recomputeMaxSingleDigitSizes()
requestLayout()
invalidate()
}
+ fun buildFidgetVariation(axes: List<ClockFontAxisSetting>): List<ClockFontAxisSetting> {
+ val result = mutableListOf<ClockFontAxisSetting>()
+ for (axis in axes) {
+ result.add(
+ FIDGET_DISTS.get(axis.key)?.let { (dist, midpoint) ->
+ ClockFontAxisSetting(
+ axis.key,
+ axis.value + dist * if (axis.value > midpoint) -1 else 1,
+ )
+ } ?: axis
+ )
+ }
+ return result
+ }
+
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
logger.onMeasure()
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
@@ -245,40 +263,54 @@ open class SimpleDigitalClockTextView(
fun animateDoze(isDozing: Boolean, isAnimated: Boolean) {
if (!this::textAnimator.isInitialized) return
+ logger.animateDoze()
textAnimator.setTextStyle(
- animate = isAnimated && isAnimationEnabled,
- color = if (isDozing) AOD_COLOR else lockscreenColor,
- textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
- fvar = if (isDozing) aodFontVariation else lsFontVariation,
- duration = aodStyle.transitionDuration,
- interpolator = aodDozingInterpolator,
+ TextAnimator.Style(
+ fVar = if (isDozing) aodFontVariation else lsFontVariation,
+ color = if (isDozing) AOD_COLOR else lockscreenColor,
+ textSize = if (isDozing) aodFontSizePx else lockScreenPaint.textSize,
+ ),
+ TextAnimator.Animation(
+ animate = isAnimated && isAnimationEnabled,
+ duration = aodStyle.transitionDuration,
+ interpolator = aodDozingInterpolator,
+ ),
)
updateTextBoundsForTextAnimator()
}
fun animateCharge() {
- if (!this::textAnimator.isInitialized || textAnimator.isRunning()) {
+ if (!this::textAnimator.isInitialized || textAnimator.isRunning) {
// Skip charge animation if dozing animation is already playing.
return
}
- logger.d("animateCharge()")
+ logger.animateCharge()
+
+ val lsStyle = TextAnimator.Style(fVar = lsFontVariation)
+ val aodStyle = TextAnimator.Style(fVar = aodFontVariation)
+
textAnimator.setTextStyle(
- fvar = if (dozeFraction == 0F) aodFontVariation else lsFontVariation,
- animate = isAnimationEnabled,
- onAnimationEnd =
- Runnable {
+ if (dozeFraction == 0f) aodStyle else lsStyle,
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = CHARGE_ANIMATION_DURATION,
+ onAnimationEnd = {
textAnimator.setTextStyle(
- fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation,
- animate = isAnimationEnabled,
+ if (dozeFraction == 0f) lsStyle else aodStyle,
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = CHARGE_ANIMATION_DURATION,
+ ),
)
updateTextBoundsForTextAnimator()
},
+ ),
)
updateTextBoundsForTextAnimator()
}
fun animateFidget(x: Float, y: Float) {
- if (!this::textAnimator.isInitialized || textAnimator.isRunning()) {
+ if (!this::textAnimator.isInitialized || textAnimator.isRunning) {
// Skip fidget animation if other animation is already playing.
return
}
@@ -286,19 +318,25 @@ open class SimpleDigitalClockTextView(
logger.animateFidget(x, y)
clockCtx.vibrator?.vibrate(FIDGET_HAPTICS)
- // TODO(b/374306512): Duplicated charge animation as placeholder. Implement final version
- // when we have a complete spec. May require additional code to animate individual digits.
+ // TODO(b/374306512): Delay each glyph's animation based on x/y position
textAnimator.setTextStyle(
- fvar = fidgetFontVariation,
- animate = isAnimationEnabled,
- onAnimationEnd =
- Runnable {
+ TextAnimator.Style(fVar = fidgetFontVariation),
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = FIDGET_ANIMATION_DURATION,
+ interpolator = FIDGET_INTERPOLATOR,
+ onAnimationEnd = {
textAnimator.setTextStyle(
- fvar = if (dozeFraction == 0F) lsFontVariation else aodFontVariation,
- animate = isAnimationEnabled,
+ TextAnimator.Style(fVar = lsFontVariation),
+ TextAnimator.Animation(
+ animate = isAnimationEnabled,
+ duration = FIDGET_ANIMATION_DURATION,
+ interpolator = FIDGET_INTERPOLATOR,
+ ),
)
updateTextBoundsForTextAnimator()
},
+ ),
)
updateTextBoundsForTextAnimator()
}
@@ -329,42 +367,20 @@ open class SimpleDigitalClockTextView(
}
private fun getInterpolatedTextBounds(): Rect {
- val interpolatedTextBounds = Rect()
- if (textAnimator.animator.animatedFraction != 1.0f && textAnimator.animator.isRunning) {
- interpolatedTextBounds.left =
- MathUtils.lerp(
- prevTextBounds.left,
- targetTextBounds.left,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
-
- interpolatedTextBounds.right =
- MathUtils.lerp(
- prevTextBounds.right,
- targetTextBounds.right,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
-
- interpolatedTextBounds.top =
- MathUtils.lerp(
- prevTextBounds.top,
- targetTextBounds.top,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
-
- interpolatedTextBounds.bottom =
- MathUtils.lerp(
- prevTextBounds.bottom,
- targetTextBounds.bottom,
- textAnimator.animator.animatedValue as Float,
- )
- .toInt()
- } else {
- interpolatedTextBounds.set(targetTextBounds)
+ val progress = textAnimator.animator?.let { it.animatedValue as Float } ?: 1f
+ if (!textAnimator.isRunning || progress >= 1f) {
+ return Rect(targetTextBounds)
}
+
+ val interpolatedTextBounds = Rect()
+ interpolatedTextBounds.left =
+ MathUtils.lerp(prevTextBounds.left, targetTextBounds.left, progress).toInt()
+ interpolatedTextBounds.right =
+ MathUtils.lerp(prevTextBounds.right, targetTextBounds.right, progress).toInt()
+ interpolatedTextBounds.top =
+ MathUtils.lerp(prevTextBounds.top, targetTextBounds.top, progress).toInt()
+ interpolatedTextBounds.bottom =
+ MathUtils.lerp(prevTextBounds.bottom, targetTextBounds.bottom, progress).toInt()
return interpolatedTextBounds
}
@@ -471,7 +487,7 @@ open class SimpleDigitalClockTextView(
textStyle.lineHeight?.let { lineHeight = it.toInt() }
this.aodStyle = aodStyle ?: textStyle.copy()
- this.aodStyle.transitionInterpolator?.let { aodDozingInterpolator = it }
+ aodDozingInterpolator = this.aodStyle.transitionInterpolator ?: Interpolators.LINEAR
lockScreenPaint.strokeWidth = textBorderWidth
measure(MeasureSpec.UNSPECIFIED, MeasureSpec.UNSPECIFIED)
setInterpolatorPaint()
@@ -500,7 +516,7 @@ open class SimpleDigitalClockTextView(
recomputeMaxSingleDigitSizes()
if (this::textAnimator.isInitialized) {
- textAnimator.setTextStyle(textSize = lockScreenPaint.textSize, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style(textSize = lockScreenPaint.textSize))
}
}
@@ -525,10 +541,11 @@ open class SimpleDigitalClockTextView(
textAnimator.textInterpolator.targetPaint.set(lockScreenPaint)
textAnimator.textInterpolator.onTargetPaintModified()
textAnimator.setTextStyle(
- fvar = lsFontVariation,
- textSize = lockScreenPaint.textSize,
- color = lockscreenColor,
- animate = false,
+ TextAnimator.Style(
+ fVar = lsFontVariation,
+ textSize = lockScreenPaint.textSize,
+ color = lockscreenColor,
+ )
)
}
}
@@ -572,6 +589,17 @@ open class SimpleDigitalClockTextView(
.addPrimitive(VibrationEffect.Composition.PRIMITIVE_QUICK_RISE, 1.0f, 43)
.compose()
+ val CHARGE_ANIMATION_DURATION = 500L
+ val FIDGET_ANIMATION_DURATION = 250L
+ val FIDGET_INTERPOLATOR = PathInterpolator(0.26873f, 0f, 0.45042f, 1f)
+ val FIDGET_DISTS =
+ mapOf(
+ GSFAxes.WEIGHT to Pair(200f, 500f),
+ GSFAxes.WIDTH to Pair(30f, 75f),
+ GSFAxes.ROUND to Pair(0f, 50f),
+ GSFAxes.SLANT to Pair(0f, -5f),
+ )
+
val AOD_COLOR = Color.WHITE
val LS_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 400f)
val AOD_WEIGHT_AXIS = ClockFontAxisSetting(GSFAxes.WEIGHT, 200f)
diff --git a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
index e076418f2630..79e78c9532c6 100644
--- a/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
+++ b/packages/SystemUI/multivalentTests/src/com/android/systemui/shared/clocks/AnimatableClockViewTest.kt
@@ -20,9 +20,10 @@ import android.view.LayoutInflater
import androidx.test.ext.junit.runners.AndroidJUnit4
import androidx.test.filters.SmallTest
import com.android.app.animation.Interpolators
-import com.android.systemui.customization.R
import com.android.systemui.SysuiTestCase
+import com.android.systemui.animation.FontVariationUtils
import com.android.systemui.animation.TextAnimator
+import com.android.systemui.customization.R
import com.android.systemui.util.mockito.any
import org.junit.Before
import org.junit.Rule
@@ -32,7 +33,9 @@ import org.mockito.Mock
import org.mockito.Mockito.times
import org.mockito.Mockito.verify
import org.mockito.Mockito.verifyNoMoreInteractions
+import org.mockito.Mockito.`when` as whenever
import org.mockito.junit.MockitoJUnit
+import org.mockito.kotlin.eq
@RunWith(AndroidJUnit4::class)
@SmallTest
@@ -46,6 +49,7 @@ class AnimatableClockViewTest : SysuiTestCase() {
@Before
fun setUp() {
val layoutInflater = LayoutInflater.from(context)
+ whenever(mockTextAnimator.fontVariationUtils).thenReturn(FontVariationUtils())
clockView =
layoutInflater.inflate(R.layout.clock_default_small, null) as AnimatableClockView
clockView.textAnimatorFactory = { _, _ -> mockTextAnimator }
@@ -57,18 +61,19 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.animateAppearOnLockscreen()
clockView.measure(50, 50)
+ verify(mockTextAnimator).fontVariationUtils
verify(mockTextAnimator).glyphFilter = any()
verify(mockTextAnimator)
.setTextStyle(
- weight = 300,
- textSize = -1.0f,
- color = 200,
- strokeWidth = -1F,
- animate = false,
- duration = 833L,
- interpolator = Interpolators.EMPHASIZED_DECELERATE,
- delay = 0L,
- onAnimationEnd = null
+ eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
+ eq(
+ TextAnimator.Animation(
+ animate = false,
+ duration = 833L,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
+ onAnimationEnd = null,
+ )
+ ),
)
verifyNoMoreInteractions(mockTextAnimator)
}
@@ -79,30 +84,24 @@ class AnimatableClockViewTest : SysuiTestCase() {
clockView.measure(50, 50)
clockView.animateAppearOnLockscreen()
+ verify(mockTextAnimator, times(2)).fontVariationUtils
verify(mockTextAnimator, times(2)).glyphFilter = any()
verify(mockTextAnimator)
.setTextStyle(
- weight = 100,
- textSize = -1.0f,
- color = 200,
- strokeWidth = -1F,
- animate = false,
- duration = 0L,
- interpolator = null,
- delay = 0L,
- onAnimationEnd = null
+ eq(TextAnimator.Style(fVar = "'wght' 100", color = 200)),
+ eq(TextAnimator.Animation(animate = false, duration = 0)),
)
+
verify(mockTextAnimator)
.setTextStyle(
- weight = 300,
- textSize = -1.0f,
- color = 200,
- strokeWidth = -1F,
- animate = true,
- duration = 833L,
- interpolator = Interpolators.EMPHASIZED_DECELERATE,
- delay = 0L,
- onAnimationEnd = null
+ eq(TextAnimator.Style(fVar = "'wght' 300", color = 200)),
+ eq(
+ TextAnimator.Animation(
+ animate = true,
+ duration = 833L,
+ interpolator = Interpolators.EMPHASIZED_DECELERATE,
+ )
+ ),
)
verifyNoMoreInteractions(mockTextAnimator)
}
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 9a837446a802..3ed321e48cd3 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
@@ -83,6 +83,13 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
+ fun updateAxes(lsFVar: String, aodFVar: String) {
+ i({ "updateAxes(LS = $str1, AOD = $str2)" }) {
+ str1 = lsFVar
+ str2 = aodFVar
+ }
+ }
+
fun addView(child: View) {
d({ "addView($str1 @$int1)" }) {
str1 = child::class.simpleName!!
@@ -90,6 +97,14 @@ class ClockLogger(private val view: View?, buffer: MessageBuffer, tag: String) :
}
}
+ fun animateDoze() {
+ d("animateDoze()")
+ }
+
+ fun animateCharge() {
+ d("animateCharge()")
+ }
+
fun animateFidget(x: Float, y: Float) {
d({ "animateFidget($str1, $str2)" }) {
str1 = x.toString()
diff --git a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
index dcf38800bb01..14a81b3f8bfb 100644
--- a/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
+++ b/packages/SystemUI/tests/src/com/android/systemui/animation/TextAnimatorTest.kt
@@ -30,7 +30,6 @@ import kotlin.math.ceil
import org.junit.Test
import org.junit.runner.RunWith
import org.mockito.ArgumentCaptor
-import org.mockito.Mockito.eq
import org.mockito.Mockito.inOrder
import org.mockito.Mockito.mock
import org.mockito.Mockito.never
@@ -60,10 +59,11 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
- textAnimator.setTextStyle(weight = 400, animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), TextAnimator.Animation())
// If animation is requested, the base state should be rebased and the target state should
// be updated.
@@ -90,10 +90,11 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
- textAnimator.setTextStyle(weight = 400, animate = false)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"))
// 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,23 +119,24 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
textAnimator.setTextStyle(
- weight = 400,
- animate = true,
- onAnimationEnd = animationEndCallback,
+ TextAnimator.Style("'wght' 400"),
+ TextAnimator.Animation(animate = true, onAnimationEnd = animationEndCallback),
)
// Verify animationEnd callback has been added.
val captor = ArgumentCaptor.forClass(AnimatorListenerAdapter::class.java)
- verify(valueAnimator).addListener(captor.capture())
- captor.value.onAnimationEnd(valueAnimator)
+ verify(valueAnimator, times(2)).addListener(captor.capture())
+ for (callback in captor.allValues) {
+ callback.onAnimationEnd(valueAnimator)
+ }
// Verify animationEnd callback has been invoked and removed.
verify(animationEndCallback).run()
- verify(valueAnimator).removeListener(eq(captor.value))
}
@Test
@@ -148,18 +150,20 @@ class TextAnimatorTest : SysuiTestCase() {
val textAnimator =
TextAnimator(layout, TypefaceVariantCacheImpl(typeface, 20)).apply {
this.textInterpolator = textInterpolator
+ this.createAnimator = { valueAnimator }
this.animator = valueAnimator
}
- textAnimator.setTextStyle(weight = 400, animate = true)
+ val animation = TextAnimator.Animation(animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), animation)
val prevTypeface = paint.typeface
- textAnimator.setTextStyle(weight = 700, animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 700"), animation)
assertThat(paint.typeface).isNotSameInstanceAs(prevTypeface)
- textAnimator.setTextStyle(weight = 400, animate = true)
+ textAnimator.setTextStyle(TextAnimator.Style("'wght' 400"), animation)
assertThat(paint.typeface).isSameInstanceAs(prevTypeface)
}