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