1 /*
2  * Copyright (C) 2020 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.wm.shell.animation
18 
19 import android.util.ArrayMap
20 import android.util.Log
21 import android.view.View
22 import androidx.dynamicanimation.animation.DynamicAnimation
23 import androidx.dynamicanimation.animation.FlingAnimation
24 import androidx.dynamicanimation.animation.FloatPropertyCompat
25 import androidx.dynamicanimation.animation.SpringAnimation
26 import androidx.dynamicanimation.animation.SpringForce
27 
28 import com.android.wm.shell.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      */
172     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      */
194     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      */
210     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      */
224     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      */
252     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      */
274     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
306     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 
371     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      */
389     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      */
428     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      */
437     fun withEndActions(vararg endActions: Runnable?): PhysicsAnimator<T> {
438         this.endActions.addAll(endActions.filterNotNull().map { it::run })
439         return this
440     }
441 
442     fun setDefaultSpringConfig(defaultSpring: SpringConfig) {
443         this.defaultSpring = defaultSpring
444     }
445 
446     fun setDefaultFlingConfig(defaultFling: FlingConfig) {
447         this.defaultFling = defaultFling
448     }
449 
450     /** Starts the animations! */
451     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      */
459     internal fun startInternal() {
460         val target = weakTarget.get()
461         if (target == null) {
462             Log.w(TAG, "Trying to animate a GC-ed object.")
463             return
464         }
465 
466         // Functions that will actually start the animations. These are run after we build and add
467         // the InternalListener, since some animations might update/end immediately and we don't
468         // want to miss those updates.
469         val animationStartActions = ArrayList<() -> Unit>()
470 
471         for (animatedProperty in getAnimatedProperties()) {
472             val flingConfig = flingConfigs[animatedProperty]
473             val springConfig = springConfigs[animatedProperty]
474 
475             // The property's current value on the object.
476             val currentValue = animatedProperty.getValue(target)
477 
478             // Start by checking for a fling configuration. If one is present, we're either flinging
479             // or flinging-then-springing. Either way, we'll want to start the fling first.
480             if (flingConfig != null) {
481                 animationStartActions.add {
482                     // When the animation is starting, adjust the min/max bounds to include the
483                     // current value of the property, if necessary. This is required to allow a
484                     // fling to bring an out-of-bounds object back into bounds. For example, if an
485                     // object was dragged halfway off the left side of the screen, but then flung to
486                     // the right, we don't want the animation to end instantly just because the
487                     // object started out of bounds. If the fling is in the direction that would
488                     // take it farther out of bounds, it will end instantly as expected.
489                     flingConfig.apply {
490                         min = min(currentValue, this.min)
491                         max = max(currentValue, this.max)
492                     }
493 
494                     // Flings can't be updated to a new position while maintaining velocity, because
495                     // we're using the explicitly provided start velocity. Cancel any flings (or
496                     // springs) on this property before flinging.
497                     cancel(animatedProperty)
498 
499                     // Apply the configuration and start the animation.
500                     getFlingAnimation(animatedProperty, target)
501                         .also { flingConfig.applyToAnimation(it) }.start()
502                 }
503             }
504 
505             // Check for a spring configuration. If one is present, we're either springing, or
506             // flinging-then-springing.
507             if (springConfig != null) {
508 
509                 // If there is no corresponding fling config, we're only springing.
510                 if (flingConfig == null) {
511                     // Apply the configuration and start the animation.
512                     val springAnim = getSpringAnimation(animatedProperty, target)
513 
514                     // Apply the configuration and start the animation.
515                     springConfig.applyToAnimation(springAnim)
516                     animationStartActions.add(springAnim::start)
517                 } else {
518                     // If there's a corresponding fling config, we're flinging-then-springing. Save
519                     // the fling's original bounds so we can spring to them when the fling ends.
520                     val flingMin = flingConfig.min
521                     val flingMax = flingConfig.max
522 
523                     // Add an end listener that will start the spring when the fling ends.
524                     endListeners.add(0, object : EndListener<T> {
525                         override fun onAnimationEnd(
526                             target: T,
527                             property: FloatPropertyCompat<in T>,
528                             wasFling: Boolean,
529                             canceled: Boolean,
530                             finalValue: Float,
531                             finalVelocity: Float,
532                             allRelevantPropertyAnimsEnded: Boolean
533                         ) {
534                             // If this isn't the relevant property, it wasn't a fling, or the fling
535                             // was explicitly cancelled, don't spring.
536                             if (property != animatedProperty || !wasFling || canceled) {
537                                 return
538                             }
539 
540                             val endedWithVelocity = abs(finalVelocity) > 0
541 
542                             // If the object was out of bounds when the fling animation started, it
543                             // will immediately end. In that case, we'll spring it back in bounds.
544                             val endedOutOfBounds = finalValue !in flingMin..flingMax
545 
546                             // If the fling ended either out of bounds or with remaining velocity,
547                             // it's time to spring.
548                             if (endedWithVelocity || endedOutOfBounds) {
549                                 springConfig.startVelocity = finalVelocity
550 
551                                 // If the spring's final position isn't set, this is a
552                                 // flingThenSpring where flingMustReachMinOrMax was false. We'll
553                                 // need to set the spring's final position here.
554                                 if (springConfig.finalPosition == UNSET) {
555                                     if (endedWithVelocity) {
556                                         // If the fling ended with negative velocity, that means it
557                                         // hit the min bound, so spring to that bound (and vice
558                                         // versa).
559                                         springConfig.finalPosition =
560                                                 if (finalVelocity < 0) flingMin else flingMax
561                                     } else if (endedOutOfBounds) {
562                                         // If the fling ended out of bounds, spring it to the
563                                         // nearest bound.
564                                         springConfig.finalPosition =
565                                                 if (finalValue < flingMin) flingMin else flingMax
566                                     }
567                                 }
568 
569                                 // Apply the configuration and start the spring animation.
570                                 getSpringAnimation(animatedProperty, target)
571                                     .also { springConfig.applyToAnimation(it) }.start()
572                             }
573                         }
574                     })
575                 }
576             }
577         }
578 
579         // Add an internal listener that will dispatch animation events to the provided listeners.
580         internalListeners.add(InternalListener(
581                 target,
582                 getAnimatedProperties(),
583                 ArrayList(updateListeners),
584                 ArrayList(endListeners),
585                 ArrayList(endActions)))
586 
587         // Actually start the DynamicAnimations. This is delayed until after the InternalListener is
588         // constructed and added so that we don't miss the end listener firing for any animations
589         // that immediately end.
590         animationStartActions.forEach { it.invoke() }
591 
592         clearAnimator()
593     }
594 
595     /** Clear the animator's builder variables. */
596     private fun clearAnimator() {
597         springConfigs.clear()
598         flingConfigs.clear()
599 
600         updateListeners.clear()
601         endListeners.clear()
602         endActions.clear()
603     }
604 
605     /** Retrieves a spring animation for the given property, building one if needed. */
606     private fun getSpringAnimation(
607         property: FloatPropertyCompat<in T>,
608         target: T
609     ): SpringAnimation {
610         return springAnimations.getOrPut(
611                 property,
612                 { configureDynamicAnimation(SpringAnimation(target, property), property)
613                         as SpringAnimation })
614     }
615 
616     /** Retrieves a fling animation for the given property, building one if needed. */
617     private fun getFlingAnimation(property: FloatPropertyCompat<in T>, target: T): FlingAnimation {
618         return flingAnimations.getOrPut(
619                 property,
620                 { configureDynamicAnimation(FlingAnimation(target, property), property)
621                         as FlingAnimation })
622     }
623 
624     /**
625      * Adds update and end listeners to the DynamicAnimation which will dispatch to the internal
626      * listeners.
627      */
628     private fun configureDynamicAnimation(
629         anim: DynamicAnimation<*>,
630         property: FloatPropertyCompat<in T>
631     ): DynamicAnimation<*> {
632         anim.addUpdateListener { _, value, velocity ->
633             for (i in 0 until internalListeners.size) {
634                 internalListeners[i].onInternalAnimationUpdate(property, value, velocity)
635             }
636         }
637         anim.addEndListener { _, canceled, value, velocity ->
638             internalListeners.removeAll {
639                 it.onInternalAnimationEnd(
640                         property, canceled, value, velocity, anim is FlingAnimation)
641             }
642             if (springAnimations[property] == anim) {
643                 springAnimations.remove(property)
644             }
645             if (flingAnimations[property] == anim) {
646                 flingAnimations.remove(property)
647             }
648         }
649         return anim
650     }
651 
652     /**
653      * Internal listener class that receives updates from DynamicAnimation listeners, and dispatches
654      * them to the appropriate update/end listeners. This class is also aware of which properties
655      * were being animated when the end listeners were passed in, so that we can provide the
656      * appropriate value for allEnded to [EndListener.onAnimationEnd].
657      */
658     internal inner class InternalListener constructor(
659         private val target: T,
660         private var properties: Set<FloatPropertyCompat<in T>>,
661         private var updateListeners: List<UpdateListener<T>>,
662         private var endListeners: List<EndListener<T>>,
663         private var endActions: List<EndAction>
664     ) {
665 
666         /** The number of properties whose animations haven't ended. */
667         private var numPropertiesAnimating = properties.size
668 
669         /**
670          * Update values that haven't yet been dispatched because not all property animations have
671          * updated yet.
672          */
673         private val undispatchedUpdates =
674                 ArrayMap<FloatPropertyCompat<in T>, AnimationUpdate>()
675 
676         /** Called when a DynamicAnimation updates.  */
677         internal fun onInternalAnimationUpdate(
678             property: FloatPropertyCompat<in T>,
679             value: Float,
680             velocity: Float
681         ) {
682 
683             // If this property animation isn't relevant to this listener, ignore it.
684             if (!properties.contains(property)) {
685                 return
686             }
687 
688             undispatchedUpdates[property] = AnimationUpdate(value, velocity)
689             maybeDispatchUpdates()
690         }
691 
692         /**
693          * Called when a DynamicAnimation ends.
694          *
695          * @return True if this listener should be removed from the list of internal listeners, so
696          * it no longer receives updates from DynamicAnimations.
697          */
698         internal fun onInternalAnimationEnd(
699             property: FloatPropertyCompat<in T>,
700             canceled: Boolean,
701             finalValue: Float,
702             finalVelocity: Float,
703             isFling: Boolean
704         ): Boolean {
705 
706             // If this property animation isn't relevant to this listener, ignore it.
707             if (!properties.contains(property)) {
708                 return false
709             }
710 
711             // Dispatch updates if we have one for each property.
712             numPropertiesAnimating--
713             maybeDispatchUpdates()
714 
715             // If we didn't have an update for each property, dispatch the update for the ending
716             // property. This guarantees that an update isn't sent for this property *after* we call
717             // onAnimationEnd for that property.
718             if (undispatchedUpdates.contains(property)) {
719                 updateListeners.forEach { updateListener ->
720                     updateListener.onAnimationUpdateForProperty(
721                             target,
722                             UpdateMap<T>().also { it[property] = undispatchedUpdates[property] })
723                 }
724 
725                 undispatchedUpdates.remove(property)
726             }
727 
728             val allEnded = !arePropertiesAnimating(properties)
729             endListeners.forEach {
730                 it.onAnimationEnd(
731                         target, property, isFling, canceled, finalValue, finalVelocity,
732                         allEnded)
733 
734                 // Check that the end listener didn't restart this property's animation.
735                 if (isPropertyAnimating(property)) {
736                     return false
737                 }
738             }
739 
740             // If all of the animations that this listener cares about have ended, run the end
741             // actions unless the animation was canceled.
742             if (allEnded && !canceled) {
743                 endActions.forEach { it() }
744             }
745 
746             return allEnded
747         }
748 
749         /**
750          * Dispatch undispatched values if we've received an update from each of the animating
751          * properties.
752          */
753         private fun maybeDispatchUpdates() {
754             if (undispatchedUpdates.size >= numPropertiesAnimating &&
755                     undispatchedUpdates.size > 0) {
756                 updateListeners.forEach {
757                     it.onAnimationUpdateForProperty(target, ArrayMap(undispatchedUpdates))
758                 }
759 
760                 undispatchedUpdates.clear()
761             }
762         }
763     }
764 
765     /** Return true if any animations are running on the object.  */
766     fun isRunning(): Boolean {
767         return arePropertiesAnimating(springAnimations.keys.union(flingAnimations.keys))
768     }
769 
770     /** Returns whether the given property is animating.  */
771     fun isPropertyAnimating(property: FloatPropertyCompat<in T>): Boolean {
772         return springAnimations[property]?.isRunning ?: false ||
773                 flingAnimations[property]?.isRunning ?: false
774     }
775 
776     /** Returns whether any of the given properties are animating.  */
777     fun arePropertiesAnimating(properties: Set<FloatPropertyCompat<in T>>): Boolean {
778         return properties.any { isPropertyAnimating(it) }
779     }
780 
781     /** Return the set of properties that will begin animating upon calling [start]. */
782     internal fun getAnimatedProperties(): Set<FloatPropertyCompat<in T>> {
783         return springConfigs.keys.union(flingConfigs.keys)
784     }
785 
786     /**
787      * Cancels the given properties. This is typically called immediately by [cancel], unless this
788      * animator is under test.
789      */
790     internal fun cancelInternal(properties: Set<FloatPropertyCompat<in T>>) {
791         for (property in properties) {
792             flingAnimations[property]?.cancel()
793             springAnimations[property]?.cancel()
794         }
795     }
796 
797     /** Cancels all in progress animations on all properties. */
798     fun cancel() {
799         if (flingAnimations.size > 0) {
800             cancelAction(flingAnimations.keys)
801         }
802         if (springAnimations.size > 0) {
803             cancelAction(springAnimations.keys)
804         }
805     }
806 
807     /** Cancels in progress animations on the provided properties only. */
808     fun cancel(vararg properties: FloatPropertyCompat<in T>) {
809         cancelAction(properties.toSet())
810     }
811 
812     /**
813      * Container object for spring animation configuration settings. This allows you to store
814      * default stiffness and damping ratio values in a single configuration object, which you can
815      * pass to [spring].
816      */
817     data class SpringConfig internal constructor(
818         var stiffness: Float,
819         internal var dampingRatio: Float,
820         internal var startVelocity: Float = 0f,
821         internal var finalPosition: Float = UNSET
822     ) {
823 
824         constructor() :
825                 this(globalDefaultSpring.stiffness, globalDefaultSpring.dampingRatio)
826 
827         constructor(stiffness: Float, dampingRatio: Float) :
828                 this(stiffness = stiffness, dampingRatio = dampingRatio, startVelocity = 0f)
829 
830         /** Apply these configuration settings to the given SpringAnimation. */
831         internal fun applyToAnimation(anim: SpringAnimation) {
832             val springForce = anim.spring ?: SpringForce()
833             anim.spring = springForce.apply {
834                 stiffness = this@SpringConfig.stiffness
835                 dampingRatio = this@SpringConfig.dampingRatio
836                 finalPosition = this@SpringConfig.finalPosition
837             }
838 
839             if (startVelocity != 0f) anim.setStartVelocity(startVelocity)
840         }
841     }
842 
843     /**
844      * Container object for fling animation configuration settings. This allows you to store default
845      * friction values (as well as optional min/max values) in a single configuration object, which
846      * you can pass to [fling] and related methods.
847      */
848     data class FlingConfig internal constructor(
849         internal var friction: Float,
850         var min: Float,
851         var max: Float,
852         internal var startVelocity: Float
853     ) {
854 
855         constructor() : this(globalDefaultFling.friction)
856 
857         constructor(friction: Float) :
858                 this(friction, globalDefaultFling.min, globalDefaultFling.max)
859 
860         constructor(friction: Float, min: Float, max: Float) :
861                 this(friction, min, max, startVelocity = 0f)
862 
863         /** Apply these configuration settings to the given FlingAnimation. */
864         internal fun applyToAnimation(anim: FlingAnimation) {
865             anim.apply {
866                 friction = this@FlingConfig.friction
867                 setMinValue(min)
868                 setMaxValue(max)
869                 setStartVelocity(startVelocity)
870             }
871         }
872     }
873 
874     /**
875      * Listener for receiving values from in progress animations. Used with
876      * [PhysicsAnimator.addUpdateListener].
877      *
878      * @param <T> The type of the object being animated.
879     </T> */
880     interface UpdateListener<T> {
881 
882         /**
883          * Called on each animation frame with the target object, and a map of FloatPropertyCompat
884          * -> AnimationUpdate, containing the latest value and velocity for that property. When
885          * multiple properties are animating together, the map will typically contain one entry for
886          * each property. However, you should never assume that this is the case - when a property
887          * animation ends earlier than the others, you'll receive an UpdateMap containing only that
888          * property's final update. Subsequently, you'll only receive updates for the properties
889          * that are still animating.
890          *
891          * Always check that the map contains an update for the property you're interested in before
892          * accessing it.
893          *
894          * @param target The animated object itself.
895          * @param values Map of property to AnimationUpdate, which contains that property
896          * animation's latest value and velocity. You should never assume that a particular property
897          * is present in this map.
898          */
899         fun onAnimationUpdateForProperty(
900             target: T,
901             values: UpdateMap<T>
902         )
903     }
904 
905     /**
906      * Listener for receiving callbacks when animations end.
907      *
908      * @param <T> The type of the object being animated.
909     </T> */
910     interface EndListener<T> {
911 
912         /**
913          * Called with the final animation values as each property animation ends. This can be used
914          * to respond to specific property animations concluding (such as hiding a view when ALPHA
915          * ends, even if the corresponding TRANSLATION animations have not ended).
916          *
917          * If you just want to run an action when all of the property animations have ended, you can
918          * use [PhysicsAnimator.withEndActions].
919          *
920          * @param target The animated object itself.
921          * @param property The property whose animation has just ended.
922          * @param wasFling Whether this property ended after a fling animation (as opposed to a
923          * spring animation). If this property was animated via [flingThenSpring], this will be true
924          * if the fling animation did not reach the min/max bounds, decelerating to a stop
925          * naturally. It will be false if it hit the bounds and was sprung back.
926          * @param canceled Whether the animation was explicitly canceled before it naturally ended.
927          * @param finalValue The final value of the animated property.
928          * @param finalVelocity The final velocity (in pixels per second) of the ended animation.
929          * This is typically zero, unless this was a fling animation which ended abruptly due to
930          * reaching its configured min/max values.
931          * @param allRelevantPropertyAnimsEnded Whether all properties relevant to this end listener
932          * have ended. Relevant properties are those which were animated alongside the
933          * [addEndListener] call where this animator was passed in. For example:
934          *
935          * animator
936          *    .spring(TRANSLATION_X, 100f)
937          *    .spring(TRANSLATION_Y, 200f)
938          *    .withEndListener(firstEndListener)
939          *    .start()
940          *
941          * firstEndListener will be called first for TRANSLATION_X, with allEnded = false,
942          * because TRANSLATION_Y is still running. When TRANSLATION_Y ends, it'll be called with
943          * allEnded = true.
944          *
945          * If a subsequent call to start() is made with other properties, those properties are not
946          * considered relevant and allEnded will still equal true when only TRANSLATION_X and
947          * TRANSLATION_Y end. For example, if immediately after the prior example, while
948          * TRANSLATION_X and TRANSLATION_Y are still animating, we called:
949          *
950          * animator.
951          *    .spring(SCALE_X, 2f, stiffness = 10f) // That will take awhile...
952          *    .withEndListener(secondEndListener)
953          *    .start()
954          *
955          * firstEndListener will still be called with allEnded = true when TRANSLATION_X/Y end, even
956          * though SCALE_X is still animating. Similarly, secondEndListener will be called with
957          * allEnded = true as soon as SCALE_X ends, even if the translation animations are still
958          * running.
959          */
960         fun onAnimationEnd(
961             target: T,
962             property: FloatPropertyCompat<in T>,
963             wasFling: Boolean,
964             canceled: Boolean,
965             finalValue: Float,
966             finalVelocity: Float,
967             allRelevantPropertyAnimsEnded: Boolean
968         )
969     }
970 
971     companion object {
972 
973         /**
974          * Constructor to use to for new physics animator instances in [getInstance]. This is
975          * typically the default constructor, but [PhysicsAnimatorTestUtils] can change it so that
976          * all code using the physics animator is given testable instances instead.
977          */
978         internal var instanceConstructor: (Any) -> PhysicsAnimator<*> = ::PhysicsAnimator
979 
980         @JvmStatic
981         @Suppress("UNCHECKED_CAST")
982         fun <T : Any> getInstance(target: T): PhysicsAnimator<T> {
983             if (!animators.containsKey(target)) {
984                 animators[target] = instanceConstructor(target)
985             }
986 
987             return animators[target] as PhysicsAnimator<T>
988         }
989 
990         /**
991          * Set whether all physics animators should log a lot of information about animations.
992          * Useful for debugging!
993          */
994         @JvmStatic
995         fun setVerboseLogging(debug: Boolean) {
996             verboseLogging = debug
997         }
998 
999         /**
1000          * Estimates the end value of a fling that starts at the given value using the provided
1001          * start velocity and fling configuration.
1002          *
1003          * This is only an estimate. Fling animations use a timing-based physics simulation that is
1004          * non-deterministic, so this exact value may not be reached.
1005          */
1006         @JvmStatic
1007         fun estimateFlingEndValue(
1008             startValue: Float,
1009             startVelocity: Float,
1010             flingConfig: FlingConfig
1011         ): Float {
1012             val distance = startVelocity / (flingConfig.friction * FLING_FRICTION_SCALAR_MULTIPLIER)
1013             return Math.min(flingConfig.max, Math.max(flingConfig.min, startValue + distance))
1014         }
1015 
1016         @JvmStatic
1017         fun getReadablePropertyName(property: FloatPropertyCompat<*>): String {
1018             return when (property) {
1019                 DynamicAnimation.TRANSLATION_X -> "translationX"
1020                 DynamicAnimation.TRANSLATION_Y -> "translationY"
1021                 DynamicAnimation.TRANSLATION_Z -> "translationZ"
1022                 DynamicAnimation.SCALE_X -> "scaleX"
1023                 DynamicAnimation.SCALE_Y -> "scaleY"
1024                 DynamicAnimation.ROTATION -> "rotation"
1025                 DynamicAnimation.ROTATION_X -> "rotationX"
1026                 DynamicAnimation.ROTATION_Y -> "rotationY"
1027                 DynamicAnimation.SCROLL_X -> "scrollX"
1028                 DynamicAnimation.SCROLL_Y -> "scrollY"
1029                 DynamicAnimation.ALPHA -> "alpha"
1030                 else -> "Custom FloatPropertyCompat instance"
1031             }
1032         }
1033     }
1034 }
1035