1 /*
<lambda>null2  * Copyright (C) 2019 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.util.animation
18 
19 import android.os.Looper
20 import android.util.ArrayMap
21 import android.util.Log
22 import android.view.View
23 import androidx.dynamicanimation.animation.DynamicAnimation
24 import androidx.dynamicanimation.animation.FlingAnimation
25 import androidx.dynamicanimation.animation.FloatPropertyCompat
26 import androidx.dynamicanimation.animation.SpringAnimation
27 import androidx.dynamicanimation.animation.SpringForce
28 import com.android.systemui.util.animation.PhysicsAnimator.Companion.getInstance
29 import java.lang.ref.WeakReference
30 import java.util.WeakHashMap
31 import kotlin.math.abs
32 import kotlin.math.max
33 import kotlin.math.min
34 
35 /**
36  * Extension function for all objects which will return a PhysicsAnimator instance for that object.
37  */
38 val <T : View> T.physicsAnimator: PhysicsAnimator<T> get() { return getInstance(this) }
39 
40 private const val TAG = "PhysicsAnimator"
41 
42 private val UNSET = -Float.MAX_VALUE
43 
44 /**
45  * [FlingAnimation] multiplies the friction set via [FlingAnimation.setFriction] by 4.2f, which is
46  * where this number comes from. We use it in [PhysicsAnimator.flingThenSpring] to calculate the
47  * minimum velocity for a fling to reach a certain value, given the fling's friction.
48  */
49 private const val FLING_FRICTION_SCALAR_MULTIPLIER = 4.2f
50 
51 typealias EndAction = () -> Unit
52 
53 /** A map of Property -> AnimationUpdate, which is provided to update listeners on each frame. */
54 typealias UpdateMap<T> =
55         ArrayMap<FloatPropertyCompat<in T>, PhysicsAnimator.AnimationUpdate>
56 
57 /**
58  * Map of the animators associated with a given object. This ensures that only one animator
59  * per object exists.
60  */
61 internal val animators = WeakHashMap<Any, PhysicsAnimator<*>>()
62 
63 /**
64  * Default spring configuration to use for animations where stiffness and/or damping ratio
65  * were not provided, and a default spring was not set via [PhysicsAnimator.setDefaultSpringConfig].
66  */
67 private val globalDefaultSpring = PhysicsAnimator.SpringConfig(
68         SpringForce.STIFFNESS_MEDIUM,
69         SpringForce.DAMPING_RATIO_MEDIUM_BOUNCY)
70 
71 /**
72  * Default fling configuration to use for animations where friction was not provided, and a default
73  * fling config was not set via [PhysicsAnimator.setDefaultFlingConfig].
74  */
75 private val globalDefaultFling = PhysicsAnimator.FlingConfig(
76         friction = 1f, min = -Float.MAX_VALUE, max = Float.MAX_VALUE)
77 
78 /** Whether to log helpful debug information about animations. */
79 private var verboseLogging = false
80 
81 /**
82  * Animator that uses physics-based animations to animate properties on views and objects. Physics
83  * animations use real-world physical concepts, such as momentum and mass, to realistically simulate
84  * motion. PhysicsAnimator is heavily inspired by [android.view.ViewPropertyAnimator], and
85  * also uses the builder pattern to configure and start animations.
86  *
87  * The physics animations are backed by [DynamicAnimation].
88  *
89  * @param T The type of the object being animated.
90  */
91 class PhysicsAnimator<T> private constructor (target: T) {
92     /** Weak reference to the animation target. */
93     val weakTarget = WeakReference(target)
94 
95     /** Data class for representing animation frame updates. */
96     data class AnimationUpdate(val value: Float, val velocity: Float)
97 
98     /** [DynamicAnimation] instances for the given properties. */
99     private val springAnimations = ArrayMap<FloatPropertyCompat<in T>, SpringAnimation>()
100     private val flingAnimations = ArrayMap<FloatPropertyCompat<in T>, FlingAnimation>()
101 
102     /**
103      * Spring and fling configurations for the properties to be animated on the target. We'll
104      * configure and start the DynamicAnimations for these properties according to the provided
105      * configurations.
106      */
107     private val springConfigs = ArrayMap<FloatPropertyCompat<in T>, SpringConfig>()
108     private val flingConfigs = ArrayMap<FloatPropertyCompat<in T>, FlingConfig>()
109 
110     /**
111      * Animation listeners for the animation. These will be notified when each property animation
112      * updates or ends.
113      */
114     private val updateListeners = ArrayList<UpdateListener<T>>()
115     private val endListeners = ArrayList<EndListener<T>>()
116 
117     /** End actions to run when all animations have completed.  */
118     private val endActions = ArrayList<EndAction>()
119 
120     /** SpringConfig to use by default for properties whose springs were not provided. */
121     private var defaultSpring: SpringConfig = globalDefaultSpring
122 
123     /** FlingConfig to use by default for properties whose fling configs were not provided. */
124     private var defaultFling: FlingConfig = globalDefaultFling
125 
126     /**
127      * Internal listeners that respond to DynamicAnimations updating and ending, and dispatch to
128      * the listeners provided via [addUpdateListener] and [addEndListener]. This allows us to add
129      * just one permanent update and end listener to the DynamicAnimations.
130      */
131     internal var internalListeners = ArrayList<InternalListener>()
132 
133     /**
134      * Action to run when [start] is called. This can be changed by
135      * [PhysicsAnimatorTestUtils.prepareForTest] to enable animators to run under test and provide
136      * helpful test utilities.
137      */
138     internal var startAction: () -> Unit = ::startInternal
139 
140     /**
141      * Action to run when [cancel] is called. This can be changed by
142      * [PhysicsAnimatorTestUtils.prepareForTest] to cancel animations from the main thread, which
143      * is required.
144      */
145     internal var cancelAction: (Set<FloatPropertyCompat<in T>>) -> Unit = ::cancelInternal
146 
147     /**
148      * Springs a property to the given value, using the provided configuration settings.
149      *
150      * Springs are used when you know the exact value to which you want to animate. They can be
151      * configured with a start velocity (typically used when the spring is initiated by a touch
152      * event), but this velocity will be realistically attenuated as forces are applied to move the
153      * property towards the end value.
154      *
155      * If you find yourself repeating the same stiffness and damping ratios many times, consider
156      * storing a single [SpringConfig] instance and passing that in instead of individual values.
157      *
158      * @param property The property to spring to the given value. The property must be an instance
159      * of FloatPropertyCompat&lt;? super T&gt;. For example, if this is a
160      * PhysicsAnimator&lt;FrameLayout&gt;, you can use a FloatPropertyCompat&lt;FrameLayout&gt;, as
161      * well as a FloatPropertyCompat&lt;ViewGroup&gt;, and so on.
162      * @param toPosition The value to spring the given property to.
163      * @param startVelocity The initial velocity to use for the animation.
164      * @param stiffness The stiffness to use for the spring. Higher stiffness values result in
165      * faster animations, while lower stiffness means a slower animation. Reasonable values for
166      * low, medium, and high stiffness can be found as constants in [SpringForce].
167      * @param dampingRatio The damping ratio (bounciness) to use for the spring. Higher values
168      * result in a less 'springy' animation, while lower values allow the animation to bounce
169      * back and forth for a longer time after reaching the final position. Reasonable values for
170      * low, medium, and high damping can be found in [SpringForce].
171      */
springnull172     fun spring(
173         property: FloatPropertyCompat<in T>,
174         toPosition: Float,
175         startVelocity: Float = 0f,
176         stiffness: Float = defaultSpring.stiffness,
177         dampingRatio: Float = defaultSpring.dampingRatio
178     ): PhysicsAnimator<T> {
179         if (verboseLogging) {
180             Log.d(TAG, "Springing ${getReadablePropertyName(property)} to $toPosition.")
181         }
182 
183         springConfigs[property] =
184                 SpringConfig(stiffness, dampingRatio, startVelocity, toPosition)
185         return this
186     }
187 
188     /**
189      * Springs a property to a given value using the provided start velocity and configuration
190      * options.
191      *
192      * @see spring
193      */
springnull194     fun spring(
195         property: FloatPropertyCompat<in T>,
196         toPosition: Float,
197         startVelocity: Float,
198         config: SpringConfig = defaultSpring
199     ): PhysicsAnimator<T> {
200         return spring(
201                 property, toPosition, startVelocity, config.stiffness, config.dampingRatio)
202     }
203 
204     /**
205      * Springs a property to a given value using the provided configuration options, and a start
206      * velocity of 0f.
207      *
208      * @see spring
209      */
springnull210     fun spring(
211         property: FloatPropertyCompat<in T>,
212         toPosition: Float,
213         config: SpringConfig = defaultSpring
214     ): PhysicsAnimator<T> {
215         return spring(property, toPosition, 0f, config)
216     }
217 
218     /**
219      * Springs a property to a given value using the provided configuration options, and a start
220      * velocity of 0f.
221      *
222      * @see spring
223      */
springnull224     fun spring(
225         property: FloatPropertyCompat<in T>,
226         toPosition: Float
227     ): PhysicsAnimator<T> {
228         return spring(property, toPosition, 0f)
229     }
230 
231     /**
232      * Flings a property using the given start velocity, using a [FlingAnimation] configured using
233      * the provided configuration settings.
234      *
235      * Flings are used when you have a start velocity, and want the property value to realistically
236      * decrease as friction is applied until the velocity reaches zero. Flings do not have a
237      * deterministic end value. If you are attempting to animate to a specific end value, use
238      * [spring].
239      *
240      * If you find yourself repeating the same friction/min/max values, consider storing a single
241      * [FlingConfig] and passing that in instead.
242      *
243      * @param property The property to fling using the given start velocity.
244      * @param startVelocity The start velocity (in pixels per second) with which to start the fling.
245      * @param friction Friction value applied to slow down the animation over time. Higher values
246      * will more quickly slow the animation. Typical friction values range from 1f to 10f.
247      * @param min The minimum value allowed for the animation. If this value is reached, the
248      * animation will end abruptly.
249      * @param max The maximum value allowed for the animation. If this value is reached, the
250      * animation will end abruptly.
251      */
flingnull252     fun fling(
253         property: FloatPropertyCompat<in T>,
254         startVelocity: Float,
255         friction: Float = defaultFling.friction,
256         min: Float = defaultFling.min,
257         max: Float = defaultFling.max
258     ): PhysicsAnimator<T> {
259         if (verboseLogging) {
260             Log.d(TAG, "Flinging ${getReadablePropertyName(property)} " +
261                     "with velocity $startVelocity.")
262         }
263 
264         flingConfigs[property] = FlingConfig(friction, min, max, startVelocity)
265         return this
266     }
267 
268     /**
269      * Flings a property using the given start velocity, using a [FlingAnimation] configured using
270      * the provided configuration settings.
271      *
272      * @see fling
273      */
flingnull274     fun fling(
275         property: FloatPropertyCompat<in T>,
276         startVelocity: Float,
277         config: FlingConfig = defaultFling
278     ): PhysicsAnimator<T> {
279         return fling(property, startVelocity, config.friction, config.min, config.max)
280     }
281 
282     /**
283      * Flings a property using the given start velocity. If the fling animation reaches the min/max
284      * bounds (from the [flingConfig]) with velocity remaining, it'll overshoot it and spring back.
285      *
286      * If the object is already out of the fling bounds, it will immediately spring back within
287      * bounds.
288      *
289      * This is useful for animating objects that are bounded by constraints such as screen edges,
290      * since otherwise the fling animation would end abruptly upon reaching the min/max bounds.
291      *
292      * @param property The property to animate.
293      * @param startVelocity The velocity, in pixels/second, with which to start the fling. If the
294      * object is already outside the fling bounds, this velocity will be used as the start velocity
295      * of the spring that will spring it back within bounds.
296      * @param flingMustReachMinOrMax If true, the fling animation is guaranteed to reach either its
297      * minimum bound (if [startVelocity] is negative) or maximum bound (if it's positive). The
298      * animator will use startVelocity if it's sufficient, or add more velocity if necessary. This
299      * is useful when fling's deceleration-based physics are preferable to the acceleration-based
300      * forces used by springs - typically, when you're allowing the user to move an object somewhere
301      * on the screen, but it needs to be along an edge.
302      * @param flingConfig The configuration to use for the fling portion of the animation.
303      * @param springConfig The configuration to use for the spring portion of the animation.
304      */
305     @JvmOverloads
flingThenSpringnull306     fun flingThenSpring(
307         property: FloatPropertyCompat<in T>,
308         startVelocity: Float,
309         flingConfig: FlingConfig,
310         springConfig: SpringConfig,
311         flingMustReachMinOrMax: Boolean = false
312     ): PhysicsAnimator<T> {
313         val target = weakTarget.get()
314         if (target == null) {
315             Log.w(TAG, "Trying to animate a GC-ed target.")
316             return this
317         }
318         val flingConfigCopy = flingConfig.copy()
319         val springConfigCopy = springConfig.copy()
320         val toAtLeast = if (startVelocity < 0) flingConfig.min else flingConfig.max
321 
322         if (flingMustReachMinOrMax && isValidValue(toAtLeast)) {
323             val currentValue = property.getValue(target)
324             val flingTravelDistance =
325                     startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
326             val projectedFlingEndValue = currentValue + flingTravelDistance
327             val midpoint = (flingConfig.min + flingConfig.max) / 2
328 
329             // If fling velocity is too low to push the target past the midpoint between min and
330             // max, then spring back towards the nearest edge, starting with the current velocity.
331             if ((startVelocity < 0 && projectedFlingEndValue > midpoint) ||
332                     (startVelocity > 0 && projectedFlingEndValue < midpoint)) {
333                 val toPosition =
334                         if (projectedFlingEndValue < midpoint) flingConfig.min else flingConfig.max
335                 if (isValidValue(toPosition)) {
336                     return spring(property, toPosition, startVelocity, springConfig)
337                 }
338             }
339 
340             // Projected fling end value is past the midpoint, so fling forward.
341             val distanceToDestination = toAtLeast - property.getValue(target)
342 
343             // The minimum velocity required for the fling to end up at the given destination,
344             // taking the provided fling friction value.
345             val velocityToReachDestination = distanceToDestination *
346                     (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
347 
348             // If there's distance to cover, and the provided velocity is moving in the correct
349             // direction, ensure that the velocity is high enough to reach the destination.
350             // Otherwise, just use startVelocity - this means that the fling is at or out of bounds.
351             // The fling will immediately end and a spring will bring the object back into bounds
352             // with this startVelocity.
353             flingConfigCopy.startVelocity = when {
354                 distanceToDestination > 0f && startVelocity >= 0f ->
355                     max(velocityToReachDestination, startVelocity)
356                 distanceToDestination < 0f && startVelocity <= 0f ->
357                     min(velocityToReachDestination, startVelocity)
358                 else -> startVelocity
359             }
360 
361             springConfigCopy.finalPosition = toAtLeast
362         } else {
363             flingConfigCopy.startVelocity = startVelocity
364         }
365 
366         flingConfigs[property] = flingConfigCopy
367         springConfigs[property] = springConfigCopy
368         return this
369     }
370 
isValidValuenull371     private fun isValidValue(value: Float) = value < Float.MAX_VALUE && value > -Float.MAX_VALUE
372 
373     /**
374      * Adds a listener that will be called whenever any property on the animated object is updated.
375      * This will be called on every animation frame, with the current value of the animated object
376      * and the new property values.
377      */
378     fun addUpdateListener(listener: UpdateListener<T>): PhysicsAnimator<T> {
379         updateListeners.add(listener)
380         return this
381     }
382 
383     /**
384      * Adds a listener that will be called when a property stops animating. This is useful if
385      * you care about a specific property ending, or want to use the end value/end velocity from a
386      * particular property's animation. If you just want to run an action when all property
387      * animations have ended, use [withEndActions].
388      */
addEndListenernull389     fun addEndListener(listener: EndListener<T>): PhysicsAnimator<T> {
390         endListeners.add(listener)
391         return this
392     }
393 
394     /**
395      * Adds end actions that will be run sequentially when animations for every property involved in
396      * this specific animation have ended (unless they were explicitly canceled). For example, if
397      * you call:
398      *
399      * animator
400      *   .spring(TRANSLATION_X, ...)
401      *   .spring(TRANSLATION_Y, ...)
402      *   .withEndAction(action)
403      *   .start()
404      *
405      * 'action' will be run when both TRANSLATION_X and TRANSLATION_Y end.
406      *
407      * Other properties may still be animating, if those animations were not started in the same
408      * call. For example:
409      *
410      * animator
411      *   .spring(ALPHA, ...)
412      *   .start()
413      *
414      * animator
415      *   .spring(TRANSLATION_X, ...)
416      *   .spring(TRANSLATION_Y, ...)
417      *   .withEndAction(action)
418      *   .start()
419      *
420      * 'action' will still be run as soon as TRANSLATION_X and TRANSLATION_Y end, even if ALPHA is
421      * still animating.
422      *
423      * If you want to run actions as soon as a subset of property animations have ended, you want
424      * access to the animation's end value/velocity, or you want to run these actions even if the
425      * animation is explicitly canceled, use [addEndListener]. End listeners have an allEnded param,
426      * which indicates that all relevant animations have ended.
427      */
withEndActionsnull428     fun withEndActions(vararg endActions: EndAction?): PhysicsAnimator<T> {
429         this.endActions.addAll(endActions.filterNotNull())
430         return this
431     }
432 
433     /**
434      * Helper overload so that callers from Java can use Runnables or method references as end
435      * actions without having to explicitly return Unit.
436      */
withEndActionsnull437     fun withEndActions(vararg endActions: Runnable?): PhysicsAnimator<T> {
438         this.endActions.addAll(endActions.filterNotNull().map { it::run })
439         return this
440     }
441 
setDefaultSpringConfignull442     fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
443         this.defaultSpring = defaultSpring
444     }
445 
setDefaultFlingConfignull446     fun setDefaultFlingConfig(defaultFling: FlingConfig) {
447         this.defaultFling = defaultFling
448     }
449 
450     /** Starts the animations! */
startnull451     fun start() {
452         startAction()
453     }
454 
455     /**
456      * Starts the animations for real! This is typically called immediately by [start] unless this
457      * animator is under test.
458      */
startInternalnull459     internal fun startInternal() {
460         if (!Looper.getMainLooper().isCurrentThread) {
461             Log.e(TAG, "Animations can only be started on the main thread. If you are seeing " +
462                     "this message in a test, call PhysicsAnimatorTestUtils#prepareForTest in " +
463                     "your test setup.")
464         }
465         val target = weakTarget.get()
466         if (target == null) {
467             Log.w(TAG, "Trying to animate a GC-ed object.")
468             return
469         }
470 
471         // Functions that will actually start the animations. These are run after we build and add
472         // the InternalListener, since some animations might update/end immediately and we don't
473         // want to miss those updates.
474         val animationStartActions = ArrayList<() -> Unit>()
475 
476         for (animatedProperty in getAnimatedProperties()) {
477             val flingConfig = flingConfigs[animatedProperty]
478             val springConfig = springConfigs[animatedProperty]
479 
480             // The property's current value on the object.
481             val currentValue = animatedProperty.getValue(target)
482 
483             // Start by checking for a fling configuration. If one is present, we're either flinging
484             // or flinging-then-springing. Either way, we'll want to start the fling first.
485             if (flingConfig != null) {
486                 animationStartActions.add {
487                     // When the animation is starting, adjust the min/max bounds to include the
488                     // current value of the property, if necessary. This is required to allow a
489                     // fling to bring an out-of-bounds object back into bounds. For example, if an
490                     // object was dragged halfway off the left side of the screen, but then flung to
491                     // the right, we don't want the animation to end instantly just because the
492                     // object started out of bounds. If the fling is in the direction that would
493                     // take it farther out of bounds, it will end instantly as expected.
494                     flingConfig.apply {
495                         min = min(currentValue, this.min)
496                         max = max(currentValue, this.max)
497                     }
498 
499                     // Flings can't be updated to a new position while maintaining velocity, because
500                     // we're using the explicitly provided start velocity. Cancel any flings (or
501                     // springs) on this property before flinging.
502                     cancel(animatedProperty)
503 
504                     // Apply the configuration and start the animation.
505                     getFlingAnimation(animatedProperty, target)
506                             .also { flingConfig.applyToAnimation(it) }
507                             .start()
508                 }
509             }
510 
511             // Check for a spring configuration. If one is present, we're either springing, or
512             // flinging-then-springing.
513             if (springConfig != null) {
514 
515                 // If there is no corresponding fling config, we're only springing.
516                 if (flingConfig == null) {
517                     // Apply the configuration and start the animation.
518                     val springAnim = getSpringAnimation(animatedProperty, target)
519                     springConfig.applyToAnimation(springAnim)
520                     animationStartActions.add(springAnim::start)
521                 } else {
522                     // If there's a corresponding fling config, we're flinging-then-springing. Save
523                     // the fling's original bounds so we can spring to them when the fling ends.
524                     val flingMin = flingConfig.min
525                     val flingMax = flingConfig.max
526 
527                     // Add an end listener that will start the spring when the fling ends.
528                     endListeners.add(0, object : EndListener<T> {
529                         override fun onAnimationEnd(
530                             target: T,
531                             property: FloatPropertyCompat<in T>,
532                             wasFling: Boolean,
533                             canceled: Boolean,
534                             finalValue: Float,
535                             finalVelocity: Float,
536                             allRelevantPropertyAnimsEnded: Boolean
537                         ) {
538                             // If this isn't the relevant property, it wasn't a fling, or the fling
539                             // was explicitly cancelled, don't spring.
540                             if (property != animatedProperty || !wasFling || canceled) {
541                                 return
542                             }
543 
544                             val endedWithVelocity = abs(finalVelocity) > 0
545 
546                             // If the object was out of bounds when the fling animation started, it
547                             // will immediately end. In that case, we'll spring it back in bounds.
548                             val endedOutOfBounds = finalValue !in flingMin..flingMax
549 
550                             // If the fling ended either out of bounds or with remaining velocity,
551                             // it's time to spring.
552                             if (endedWithVelocity || endedOutOfBounds) {
553                                 springConfig.startVelocity = finalVelocity
554 
555                                 // If the spring's final position isn't set, this is a
556                                 // flingThenSpring where flingMustReachMinOrMax was false. We'll
557                                 // need to set the spring's final position here.
558                                 if (springConfig.finalPosition == UNSET) {
559                                     if (endedWithVelocity) {
560                                         // If the fling ended with negative velocity, that means it
561                                         // hit the min bound, so spring to that bound (and vice
562                                         // versa).
563                                         springConfig.finalPosition =
564                                                 if (finalVelocity < 0) flingMin else flingMax
565                                     } else if (endedOutOfBounds) {
566                                         // If the fling ended out of bounds, spring it to the
567                                         // nearest bound.
568                                         springConfig.finalPosition =
569                                                 if (finalValue < flingMin) flingMin else flingMax
570                                     }
571                                 }
572 
573                                 // Apply the configuration and start the spring animation.
574                                 getSpringAnimation(animatedProperty, target)
575                                         .also { springConfig.applyToAnimation(it) }
576                                         .start()
577                             }
578                         }
579                     })
580                 }
581             }
582         }
583 
584         // Add an internal listener that will dispatch animation events to the provided listeners.
585         internalListeners.add(InternalListener(
586                 target,
587                 getAnimatedProperties(),
588                 ArrayList(updateListeners),
589                 ArrayList(endListeners),
590                 ArrayList(endActions)))
591 
592         // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
593         // constructed and added so that we don't miss the end listener firing for any animations
594         // that immediately end.
595         animationStartActions.forEach { it.invoke() }
596 
597         clearAnimator()
598     }
599 
600     /** Clear the animator's builder variables. */
clearAnimatornull601     private fun clearAnimator() {
602         springConfigs.clear()
603         flingConfigs.clear()
604 
605         updateListeners.clear()
606         endListeners.clear()
607         endActions.clear()
608     }
609 
610     /** Retrieves a spring animation for the given property, building one if needed. */
getSpringAnimationnull611     private fun getSpringAnimation(
612         property: FloatPropertyCompat<in T>,
613         target: T
614     ): SpringAnimation {
615         return springAnimations.getOrPut(
616                 property,
617                 { configureDynamicAnimation(SpringAnimation(target, property), property)
618                         as SpringAnimation })
619     }
620 
621     /** Retrieves a fling animation for the given property, building one if needed. */
getFlingAnimationnull622     private fun getFlingAnimation(property: FloatPropertyCompat<in T>, target: T): FlingAnimation {
623         return flingAnimations.getOrPut(
624                 property,
625                 { configureDynamicAnimation(FlingAnimation(target, property), property)
626                         as FlingAnimation })
627     }
628 
629     /**
630      * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
631      * listeners.
632      */
configureDynamicAnimationnull633     private fun configureDynamicAnimation(
634         anim: DynamicAnimation<*>,
635         property: FloatPropertyCompat<in T>
636     ): DynamicAnimation<*> {
637         anim.addUpdateListener { _, value, velocity ->
638             for (i in 0 until internalListeners.size) {
639                 internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
640             }
641         }
642         anim.addEndListener { _, canceled, value, velocity ->
643             internalListeners.removeAll {
644                 it.onInternalAnimationEnd(
645                         property, canceled, value, velocity, anim is FlingAnimation)
646             }
647             if (springAnimations[property] == anim) {
648                 springAnimations.remove(property)
649             }
650             if (flingAnimations[property] == anim) {
651                 flingAnimations.remove(property)
652             }
653         }
654         return anim
655     }
656 
657     /**
658      * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
659      * them to the appropriate update/end listeners. This class is also aware of which properties
660      * were being animated when the end listeners were passed in, so that we can provide the
661      * appropriate value for allEnded to [EndListener.onAnimationEnd].
662      */
663     internal inner class InternalListener constructor(
664         private val target: T,
665         private var properties: Set<FloatPropertyCompat<in T>>,
666         private var updateListeners: List<UpdateListener<T>>,
667         private var endListeners: List<EndListener<T>>,
668         private var endActions: List<EndAction>
669     ) {
670 
671         /** The number of properties whose animations haven't ended. */
672         private var numPropertiesAnimating = properties.size
673 
674         /**
675          * Update values that haven't yet been dispatched because not all property animations have
676          * updated yet.
677          */
678         private val undispatchedUpdates =
679                 ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
680 
681         /** Called when a DynamicAnimation updates.  */
onInternalAnimationUpdatenull682         internal fun onInternalAnimationUpdate(
683             property: FloatPropertyCompat<in T>,
684             value: Float,
685             velocity: Float
686         ) {
687 
688             // If this property animation isn't relevant to this listener, ignore it.
689             if (!properties.contains(property)) {
690                 return
691             }
692 
693             undispatchedUpdates[property] = AnimationUpdate(value, velocity)
694             maybeDispatchUpdates()
695         }
696 
697         /**
698          * Called when a DynamicAnimation ends.
699          *
700          * @return True if this listener should be removed from the list of internal listeners, so
701          * it no longer receives updates from DynamicAnimations.
702          */
onInternalAnimationEndnull703         internal fun onInternalAnimationEnd(
704             property: FloatPropertyCompat<in T>,
705             canceled: Boolean,
706             finalValue: Float,
707             finalVelocity: Float,
708             isFling: Boolean
709         ): Boolean {
710 
711             // If this property animation isn't relevant to this listener, ignore it.
712             if (!properties.contains(property)) {
713                 return false
714             }
715 
716             // Dispatch updates if we have one for each property.
717             numPropertiesAnimating--
718             maybeDispatchUpdates()
719 
720             // If we didn't have an update for each property, dispatch the update for the ending
721             // property. This guarantees that an update isn't sent for this property *after* we call
722             // onAnimationEnd for that property.
723             if (undispatchedUpdates.contains(property)) {
724                 updateListeners.forEach { updateListener ->
725                     updateListener.onAnimationUpdateForProperty(
726                             target,
727                             UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
728                 }
729 
730                 undispatchedUpdates.remove(property)
731             }
732 
733             val allEnded = !arePropertiesAnimating(properties)
734             endListeners.forEach {
735                 it.onAnimationEnd(
736                         target, property, isFling, canceled, finalValue, finalVelocity,
737                         allEnded)
738 
739                 // Check that the end listener didn't restart this property's animation.
740                 if (isPropertyAnimating(property)) {
741                     return false
742                 }
743             }
744 
745             // If all of the animations that this listener cares about have ended, run the end
746             // actions unless the animation was canceled.
747             if (allEnded && !canceled) {
748                 endActions.forEach { it() }
749             }
750 
751             return allEnded
752         }
753 
754         /**
755          * Dispatch undispatched values if we've received an update from each of the animating
756          * properties.
757          */
maybeDispatchUpdatesnull758         private fun maybeDispatchUpdates() {
759             if (undispatchedUpdates.size >= numPropertiesAnimating &&
760                     undispatchedUpdates.size > 0) {
761                 updateListeners.forEach {
762                     it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
763                 }
764 
765                 undispatchedUpdates.clear()
766             }
767         }
768     }
769 
770     /** Return true if any animations are running on the object.  */
isRunningnull771     fun isRunning(): Boolean {
772         return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
773     }
774 
775     /** Returns whether the given property is animating.  */
isPropertyAnimatingnull776     fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
777         return springAnimations[property]?.isRunning ?: false ||
778                 flingAnimations[property]?.isRunning ?: false
779     }
780 
781     /** Returns whether any of the given properties are animating.  */
arePropertiesAnimatingnull782     fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
783         return properties.any { isPropertyAnimating(it) }
784     }
785 
786     /** Return the set of properties that will begin animating upon calling [start]. */
getAnimatedPropertiesnull787     internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
788         return springConfigs.keys.union(flingConfigs.keys)
789     }
790 
791     /**
792      * Cancels the given properties. This is typically called immediately by [cancel], unless this
793      * animator is under test.
794      */
cancelInternalnull795     internal fun cancelInternal(properties: Set<FloatPropertyCompat<in T>>) {
796         for (property in properties) {
797             flingAnimations[property]?.cancel()
798             springAnimations[property]?.cancel()
799         }
800     }
801 
802     /** Cancels all in progress animations on all properties. */
cancelnull803     fun cancel() {
804         cancelAction(flingAnimations.keys)
805         cancelAction(springAnimations.keys)
806     }
807 
808     /** Cancels in progress animations on the provided properties only. */
cancelnull809     fun cancel(vararg properties: FloatPropertyCompat<in T>) {
810         cancelAction(properties.toSet())
811     }
812 
813     /**
814      * Container object for spring animation configuration settings. This allows you to store
815      * default stiffness and damping ratio values in a single configuration object, which you can
816      * pass to [spring].
817      */
818     data class SpringConfig internal constructor(
819         internal var stiffness: Float,
820         internal var dampingRatio: Float,
821         internal var startVelocity: Float = 0f,
822         internal var finalPosition: Float = UNSET
823     ) {
824 
825         constructor() :
826                 this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
827 
828         constructor(stiffness: Float, dampingRatio: Float) :
829                 this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
830 
831         /** Apply these configuration settings to the given SpringAnimation. */
applyToAnimationnull832         internal fun applyToAnimation(anim: SpringAnimation) {
833             val springForce = anim.spring ?: SpringForce()
834             anim.spring = springForce.apply {
835                 stiffness = this@SpringConfig.stiffness
836                 dampingRatio = this@SpringConfig.dampingRatio
837                 finalPosition = this@SpringConfig.finalPosition
838             }
839 
840             if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
841         }
842     }
843 
844     /**
845      * Container object for fling animation configuration settings. This allows you to store default
846      * friction values (as well as optional min/max values) in a single configuration object, which
847      * you can pass to [fling] and related methods.
848      */
849     data class FlingConfig internal constructor(
850         internal var friction: Float,
851         internal var min: Float,
852         internal var max: Float,
853         internal var startVelocity: Float
854     ) {
855 
856         constructor() : this(globalDefaultFling.friction)
857 
858         constructor(friction: Float) :
859                 this(friction, globalDefaultFling.min, globalDefaultFling.max)
860 
861         constructor(friction: Float, min: Float, max: Float) :
862                 this(friction, min, max, startVelocity = 0f)
863 
864         /** Apply these configuration settings to the given FlingAnimation. */
applyToAnimationnull865         internal fun applyToAnimation(anim: FlingAnimation) {
866             anim.apply {
867                 friction = this@FlingConfig.friction
868                 setMinValue(min)
869                 setMaxValue(max)
870                 setStartVelocity(startVelocity)
871             }
872         }
873     }
874 
875     /**
876      * Listener for receiving values from in progress animations. Used with
877      * [PhysicsAnimator.addUpdateListener].
878      *
879      * @param <T> The type of the object being animated.
880     </T> */
881     interface UpdateListener<T> {
882 
883         /**
884          * Called on each animation frame with the target object, and a map of FloatPropertyCompat
885          * -> AnimationUpdate, containing the latest value and velocity for that property. When
886          * multiple properties are animating together, the map will typically contain one entry for
887          * each property. However, you should never assume that this is the case - when a property
888          * animation ends earlier than the others, you'll receive an UpdateMap containing only that
889          * property's final update. Subsequently, you'll only receive updates for the properties
890          * that are still animating.
891          *
892          * Always check that the map contains an update for the property you're interested in before
893          * accessing it.
894          *
895          * @param target The animated object itself.
896          * @param values Map of property to AnimationUpdate, which contains that property
897          * animation's latest value and velocity. You should never assume that a particular property
898          * is present in this map.
899          */
onAnimationUpdateForPropertynull900         fun onAnimationUpdateForProperty(
901             target: T,
902             values: UpdateMap<T>
903         )
904     }
905 
906     /**
907      * Listener for receiving callbacks when animations end.
908      *
909      * @param <T> The type of the object being animated.
910     </T> */
911     interface EndListener<T> {
912 
913         /**
914          * Called with the final animation values as each property animation ends. This can be used
915          * to respond to specific property animations concluding (such as hiding a view when ALPHA
916          * ends, even if the corresponding TRANSLATION animations have not ended).
917          *
918          * If you just want to run an action when all of the property animations have ended, you can
919          * use [PhysicsAnimator.withEndActions].
920          *
921          * @param target The animated object itself.
922          * @param property The property whose animation has just ended.
923          * @param wasFling Whether this property ended after a fling animation (as opposed to a
924          * spring animation). If this property was animated via [flingThenSpring], this will be true
925          * if the fling animation did not reach the min/max bounds, decelerating to a stop
926          * naturally. It will be false if it hit the bounds and was sprung back.
927          * @param canceled Whether the animation was explicitly canceled before it naturally ended.
928          * @param finalValue The final value of the animated property.
929          * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
930          * This is typically zero, unless this was a fling animation which ended abruptly due to
931          * reaching its configured min/max values.
932          * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
933          * have ended. Relevant properties are those which were animated alongside the
934          * [addEndListener] call where this animator was passed in. For example:
935          *
936          * animator
937          *    .spring(TRANSLATION_X, 100f)
938          *    .spring(TRANSLATION_Y, 200f)
939          *    .withEndListener(firstEndListener)
940          *    .start()
941          *
942          * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
943          * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
944          * allEnded = true.
945          *
946          * If a subsequent call to start() is made with other properties, those properties are not
947          * considered relevant and allEnded will still equal true when only TRANSLATION_X and
948          * TRANSLATION_Y end. For example, if immediately after the prior example, while
949          * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
950          *
951          * animator.
952          *    .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
953          *    .withEndListener(secondEndListener)
954          *    .start()
955          *
956          * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
957          * though SCALE_X is still animating. Similarly, secondEndListener will be called with
958          * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
959          * running.
960          */
961         fun onAnimationEnd(
962             target: T,
963             property: FloatPropertyCompat<in T>,
964             wasFling: Boolean,
965             canceled: Boolean,
966             finalValue: Float,
967             finalVelocity: Float,
968             allRelevantPropertyAnimsEnded: Boolean
969         )
970     }
971 
972     companion object {
973 
974         /**
975          * Constructor to use to for new physics animator instances in [getInstance]. This is
976          * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
977          * all code using the physics animator is given testable instances instead.
978          */
979         internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
980 
981         @JvmStatic
982         @Suppress("UNCHECKED_CAST")
getInstancenull983         fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
984             if (!animators.containsKey(target)) {
985                 animators[target] = instanceConstructor(target)
986             }
987 
988             return animators[target] as PhysicsAnimator<T>
989         }
990 
991         /**
992          * Set whether all physics animators should log a lot of information about animations.
993          * Useful for debugging!
994          */
995         @JvmStatic
setVerboseLoggingnull996         fun setVerboseLogging(debug: Boolean) {
997             verboseLogging = debug
998         }
999 
1000         /**
1001          * Estimates the end value of a fling that starts at the given value using the provided
1002          * start velocity and fling configuration.
1003          *
1004          * This is only an estimate. Fling animations use a timing-based physics simulation that is
1005          * non-deterministic, so this exact value may not be reached.
1006          */
1007         @JvmStatic
estimateFlingEndValuenull1008         fun estimateFlingEndValue(
1009             startValue: Float,
1010             startVelocity: Float,
1011             flingConfig: FlingConfig
1012         ): Float {
1013             val distance = startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
1014             return Math.min(flingConfig.max, Math.max(flingConfig.min, startValue + distance))
1015         }
1016 
1017         @JvmStatic
getReadablePropertyNamenull1018         fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
1019             return when (property) {
1020                 DynamicAnimation.TRANSLATION_X -> "translationX"
1021                 DynamicAnimation.TRANSLATION_Y -> "translationY"
1022                 DynamicAnimation.TRANSLATION_Z -> "translationZ"
1023                 DynamicAnimation.SCALE_X -> "scaleX"
1024                 DynamicAnimation.SCALE_Y -> "scaleY"
1025                 DynamicAnimation.ROTATION -> "rotation"
1026                 DynamicAnimation.ROTATION_X -> "rotationX"
1027                 DynamicAnimation.ROTATION_Y -> "rotationY"
1028                 DynamicAnimation.SCROLL_X -> "scrollX"
1029                 DynamicAnimation.SCROLL_Y -> "scrollY"
1030                 DynamicAnimation.ALPHA -> "alpha"
1031                 else -> "Custom FloatPropertyCompat instance"
1032             }
1033         }
1034     }
1035 }
1036