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