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