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