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