Merge "Port stepping animation to FlexClockView" into main
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
index a4782ac..ee21ea6 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/FlexClockFaceController.kt
@@ -55,10 +55,7 @@
override val view: View
get() = layerController.view
- override val config =
- ClockFaceConfig(
- hasCustomPositionUpdatedAnimation = false // TODO(b/364673982)
- )
+ override val config = ClockFaceConfig(hasCustomPositionUpdatedAnimation = true)
override var theme = ThemeConfig(true, assets.seedColor)
@@ -96,6 +93,19 @@
layerController.view.layoutParams = lp
}
+ /** See documentation at [FlexClockView.offsetGlyphsForStepClockAnimation]. */
+ private fun offsetGlyphsForStepClockAnimation(
+ clockStartLeft: Int,
+ direction: Int,
+ fraction: Float
+ ) {
+ (view as? FlexClockView)?.offsetGlyphsForStepClockAnimation(
+ clockStartLeft,
+ direction,
+ fraction,
+ )
+ }
+
override val layout: ClockFaceLayout =
DefaultClockFaceLayout(view).apply {
views[0].id =
@@ -248,10 +258,12 @@
override fun onPositionUpdated(fromLeft: Int, direction: Int, fraction: Float) {
layerController.animations.onPositionUpdated(fromLeft, direction, fraction)
+ if (isLargeClock) offsetGlyphsForStepClockAnimation(fromLeft, direction, fraction)
}
override fun onPositionUpdated(distance: Float, fraction: Float) {
layerController.animations.onPositionUpdated(distance, fraction)
+ // TODO(b/378128811) port stepping animation
}
}
}
diff --git a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
index d86c0d6..593eba9 100644
--- a/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
+++ b/packages/SystemUI/customization/src/com/android/systemui/shared/clocks/view/FlexClockView.kt
@@ -19,6 +19,7 @@
import android.content.Context
import android.graphics.Canvas
import android.graphics.Point
+import android.util.MathUtils.constrainedMap
import android.view.View
import android.view.ViewGroup
import android.widget.RelativeLayout
@@ -50,6 +51,8 @@
)
}
+ private val digitOffsets = mutableMapOf<Int, Float>()
+
override fun addView(child: View?) {
super.addView(child)
(child as SimpleDigitalClockTextView).digitTranslateAnimator =
@@ -76,7 +79,7 @@
digitLeftTopMap[R.id.HOUR_SECOND_DIGIT] = Point(maxSingleDigitSize.x, 0)
digitLeftTopMap[R.id.MINUTE_FIRST_DIGIT] = Point(0, maxSingleDigitSize.y)
digitLeftTopMap[R.id.MINUTE_SECOND_DIGIT] = Point(maxSingleDigitSize)
- digitLeftTopMap.forEach { _, point ->
+ digitLeftTopMap.forEach { (_, point) ->
point.x += abs(aodTranslate.x)
point.y += abs(aodTranslate.y)
}
@@ -89,11 +92,17 @@
override fun onDraw(canvas: Canvas) {
super.onDraw(canvas)
- digitalClockTextViewMap.forEach { (id, _) ->
- val textView = digitalClockTextViewMap[id]!!
- canvas.translate(digitLeftTopMap[id]!!.x.toFloat(), digitLeftTopMap[id]!!.y.toFloat())
+ digitalClockTextViewMap.forEach { (id, textView) ->
+ // save canvas location in anticipation of restoration later
+ canvas.save()
+ val xTranslateAmount =
+ digitOffsets.getOrDefault(id, 0f) + digitLeftTopMap[id]!!.x.toFloat()
+ // move canvas to location that the textView would like
+ canvas.translate(xTranslateAmount, digitLeftTopMap[id]!!.y.toFloat())
+ // draw the textView at the location of the canvas above
textView.draw(canvas)
- canvas.translate(-digitLeftTopMap[id]!!.x.toFloat(), -digitLeftTopMap[id]!!.y.toFloat())
+ // reset the canvas location back to 0 without drawing
+ canvas.restore()
}
}
@@ -157,10 +166,108 @@
}
}
+ /**
+ * Offsets the textViews of the clock for the step clock animation.
+ *
+ * The animation makes the textViews of the clock move at different speeds, when the clock is
+ * moving horizontally.
+ *
+ * @param clockStartLeft the [getLeft] position of the clock, before it started moving.
+ * @param clockMoveDirection the direction in which it is moving. A positive number means right,
+ * and negative means left.
+ * @param moveFraction fraction of the clock movement. 0 means it is at the beginning, and 1
+ * means it finished moving.
+ */
+ fun offsetGlyphsForStepClockAnimation(
+ clockStartLeft: Int,
+ clockMoveDirection: Int,
+ moveFraction: Float,
+ ) {
+ val isMovingToCenter = if (isLayoutRtl) clockMoveDirection < 0 else clockMoveDirection > 0
+ // The sign of moveAmountDeltaForDigit is already set here
+ // we can interpret (left - clockStartLeft) as (destinationPosition - originPosition)
+ // so we no longer need to multiply direct sign to moveAmountDeltaForDigit
+ val currentMoveAmount = left - clockStartLeft
+ for (i in 0 until NUM_DIGITS) {
+ val mapIndexToId =
+ when (i) {
+ 0 -> R.id.HOUR_FIRST_DIGIT
+ 1 -> R.id.HOUR_SECOND_DIGIT
+ 2 -> R.id.MINUTE_FIRST_DIGIT
+ 3 -> R.id.MINUTE_SECOND_DIGIT
+ else -> -1
+ }
+ val digitFraction =
+ getDigitFraction(
+ digit = i,
+ isMovingToCenter = isMovingToCenter,
+ fraction = moveFraction,
+ )
+ // left here is the final left position after the animation is done
+ val moveAmountForDigit = currentMoveAmount * digitFraction
+ var moveAmountDeltaForDigit = moveAmountForDigit - currentMoveAmount
+ if (isMovingToCenter && moveAmountForDigit < 0) moveAmountDeltaForDigit *= -1
+ digitOffsets[mapIndexToId] = moveAmountDeltaForDigit
+ invalidate()
+ }
+ }
+
+ private val moveToCenterDelays: List<Int>
+ get() = if (isLayoutRtl) MOVE_LEFT_DELAYS else MOVE_RIGHT_DELAYS
+
+ private val moveToSideDelays: List<Int>
+ get() = if (isLayoutRtl) MOVE_RIGHT_DELAYS else MOVE_LEFT_DELAYS
+
+ private fun getDigitFraction(digit: Int, isMovingToCenter: Boolean, fraction: Float): Float {
+ // The delay for the digit, in terms of fraction.
+ // (i.e. the digit should not move during 0.0 - 0.1).
+ val delays = if (isMovingToCenter) moveToCenterDelays else moveToSideDelays
+ val digitInitialDelay = delays[digit] * MOVE_DIGIT_STEP
+ return MOVE_INTERPOLATOR.getInterpolation(
+ constrainedMap(
+ /* rangeMin= */ 0.0f,
+ /* rangeMax= */ 1.0f,
+ /* valueMin= */ digitInitialDelay,
+ /* valueMax= */ digitInitialDelay + AVAILABLE_ANIMATION_TIME,
+ /* value= */ fraction,
+ )
+ )
+ }
+
companion object {
val AOD_TRANSITION_DURATION = 750L
val CHARGING_TRANSITION_DURATION = 300L
+ // Calculate the positions of all of the digits...
+ // Offset each digit by, say, 0.1
+ // This means that each digit needs to move over a slice of "fractions", i.e. digit 0 should
+ // move from 0.0 - 0.7, digit 1 from 0.1 - 0.8, digit 2 from 0.2 - 0.9, and digit 3
+ // from 0.3 - 1.0.
+ private const val NUM_DIGITS = 4
+
+ // Delays. Each digit's animation should have a slight delay, so we get a nice
+ // "stepping" effect. When moving right, the second digit of the hour should move first.
+ // When moving left, the first digit of the hour should move first. The lists encode
+ // the delay for each digit (hour[0], hour[1], minute[0], minute[1]), to be multiplied
+ // by delayMultiplier.
+ private val MOVE_LEFT_DELAYS = listOf(0, 1, 2, 3)
+ private val MOVE_RIGHT_DELAYS = listOf(1, 0, 3, 2)
+
+ // How much delay to apply to each subsequent digit. This is measured in terms of "fraction"
+ // (i.e. a value of 0.1 would cause a digit to wait until fraction had hit 0.1, or 0.2 etc
+ // before moving).
+ //
+ // The current specs dictate that each digit should have a 33ms gap between them. The
+ // overall time is 1s right now.
+ private const val MOVE_DIGIT_STEP = 0.033f
+
+ // Constants for the animation
+ private val MOVE_INTERPOLATOR = Interpolators.EMPHASIZED
+
+ // Total available transition time for each digit, taking into account the step. If step is
+ // 0.1, then digit 0 would animate over 0.0 - 0.7, making availableTime 0.7.
+ private const val AVAILABLE_ANIMATION_TIME = 1.0f - MOVE_DIGIT_STEP * (NUM_DIGITS - 1)
+
// Use the sign of targetTranslation to control the direction of digit translation
fun updateDirectionalTargetTranslate(id: Int, targetTranslation: Point): Point {
val outPoint = Point(targetTranslation)
@@ -169,17 +276,14 @@
outPoint.x *= -1
outPoint.y *= -1
}
-
R.id.HOUR_SECOND_DIGIT -> {
outPoint.x *= 1
outPoint.y *= -1
}
-
R.id.MINUTE_FIRST_DIGIT -> {
outPoint.x *= -1
outPoint.y *= 1
}
-
R.id.MINUTE_SECOND_DIGIT -> {
outPoint.x *= 1
outPoint.y *= 1