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