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