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