1 /*
2  * Copyright (C) 2024 The Android Open Source Project
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  *      http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 
17 package com.android.systemui.shared.clocks
18 
19 import android.animation.Animator
20 import android.animation.AnimatorListenerAdapter
21 import android.animation.TimeInterpolator
22 import android.animation.ValueAnimator
23 import android.graphics.Point
24 
25 class DigitTranslateAnimator(val updateCallback: () -> Unit) {
26     val DEFAULT_ANIMATION_DURATION = 500L
27     val updatedTranslate = Point(0, 0)
28 
29     val baseTranslation = Point(0, 0)
30     var targetTranslation: Point? = null
31     val bounceAnimator: ValueAnimator =
<lambda>null32         ValueAnimator.ofFloat(1f).apply {
33             duration = DEFAULT_ANIMATION_DURATION
34             addUpdateListener {
35                 updateTranslation(it.animatedFraction, updatedTranslate)
36                 updateCallback()
37             }
38             addListener(
39                 object : AnimatorListenerAdapter() {
40                     override fun onAnimationEnd(animation: Animator) {
41                         rebase()
42                     }
43 
44                     override fun onAnimationCancel(animation: Animator) {
45                         rebase()
46                     }
47                 }
48             )
49         }
50 
rebasenull51     fun rebase() {
52         baseTranslation.x = updatedTranslate.x
53         baseTranslation.y = updatedTranslate.y
54     }
55 
animatePositionnull56     fun animatePosition(
57         animate: Boolean = true,
58         delay: Long = 0,
59         duration: Long = -1L,
60         interpolator: TimeInterpolator? = null,
61         targetTranslation: Point? = null,
62         onAnimationEnd: Runnable? = null,
63     ) {
64         this.targetTranslation = targetTranslation ?: Point(0, 0)
65         if (animate) {
66             bounceAnimator.cancel()
67             bounceAnimator.startDelay = delay
68             bounceAnimator.duration =
69                 if (duration == -1L) {
70                     DEFAULT_ANIMATION_DURATION
71                 } else {
72                     duration
73                 }
74             interpolator?.let { bounceAnimator.interpolator = it }
75             if (onAnimationEnd != null) {
76                 val listener =
77                     object : AnimatorListenerAdapter() {
78                         override fun onAnimationEnd(animation: Animator) {
79                             onAnimationEnd.run()
80                             bounceAnimator.removeListener(this)
81                         }
82 
83                         override fun onAnimationCancel(animation: Animator) {
84                             bounceAnimator.removeListener(this)
85                         }
86                     }
87                 bounceAnimator.addListener(listener)
88             }
89             bounceAnimator.start()
90         } else {
91             // No animation is requested, thus set base and target state to the same state.
92             updateTranslation(1F, updatedTranslate)
93             rebase()
94             updateCallback()
95         }
96     }
97 
updateTranslationnull98     fun updateTranslation(progress: Float, outPoint: Point) {
99         outPoint.x =
100             (baseTranslation.x + progress * (targetTranslation!!.x - baseTranslation.x)).toInt()
101         outPoint.y =
102             (baseTranslation.y + progress * (targetTranslation!!.y - baseTranslation.y)).toInt()
103     }
104 }
105