1 /* 2 * 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.bubbles.animation; 18 19 import android.animation.Animator; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.ObjectAnimator; 22 import android.animation.TimeInterpolator; 23 import android.content.Context; 24 import android.graphics.Path; 25 import android.graphics.PointF; 26 import android.util.FloatProperty; 27 import android.util.Log; 28 import android.view.View; 29 import android.view.ViewGroup; 30 import android.widget.FrameLayout; 31 32 import androidx.annotation.Nullable; 33 import androidx.dynamicanimation.animation.DynamicAnimation; 34 import androidx.dynamicanimation.animation.SpringAnimation; 35 import androidx.dynamicanimation.animation.SpringForce; 36 37 import com.android.systemui.R; 38 39 import java.util.ArrayList; 40 import java.util.HashMap; 41 import java.util.HashSet; 42 import java.util.List; 43 import java.util.Map; 44 import java.util.Set; 45 46 /** 47 * Layout that constructs physics-based animations for each of its children, which behave according 48 * to settings provided by a {@link PhysicsAnimationController} instance. 49 * 50 * See physics-animation-layout.md. 51 */ 52 public class PhysicsAnimationLayout extends FrameLayout { 53 private static final String TAG = "Bubbs.PAL"; 54 55 /** 56 * Controls the construction, configuration, and use of the physics animations supplied by this 57 * layout. 58 */ 59 abstract static class PhysicsAnimationController { 60 61 /** Configures a given {@link PhysicsPropertyAnimator} for a view at the given index. */ 62 interface ChildAnimationConfigurator { 63 64 /** 65 * Called to configure the animator for the view at the given index. 66 * 67 * This method should make use of methods such as 68 * {@link PhysicsPropertyAnimator#translationX} and 69 * {@link PhysicsPropertyAnimator#withStartDelay} to configure the animation. 70 * 71 * Implementations should not call {@link PhysicsPropertyAnimator#start}, this will 72 * happen elsewhere after configuration is complete. 73 */ configureAnimationForChildAtIndex(int index, PhysicsPropertyAnimator animation)74 void configureAnimationForChildAtIndex(int index, PhysicsPropertyAnimator animation); 75 } 76 77 /** 78 * Returned by {@link #animationsForChildrenFromIndex} to allow starting multiple animations 79 * on multiple child views at the same time. 80 */ 81 interface MultiAnimationStarter { 82 83 /** 84 * Start all animations and call the given end actions once all animations have 85 * completed. 86 */ startAll(Runnable... endActions)87 void startAll(Runnable... endActions); 88 } 89 90 /** 91 * Constant to return from {@link #getNextAnimationInChain} if the animation should not be 92 * chained at all. 93 */ 94 protected static final int NONE = -1; 95 96 /** Set of properties for which the layout should construct physics animations. */ getAnimatedProperties()97 abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties(); 98 99 /** 100 * Returns the index of the next animation after the given index in the animation chain, or 101 * {@link #NONE} if it should not be chained, or if the chain should end at the given index. 102 * 103 * If a next index is returned, an update listener will be added to the animation at the 104 * given index that dispatches value updates to the animation at the next index. This 105 * creates a 'following' effect. 106 * 107 * Typical implementations of this method will return either index + 1, or index - 1, to 108 * create forward or backward chains between adjacent child views, but this is not required. 109 */ getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index)110 abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index); 111 112 /** 113 * Offsets to be added to the value that chained animations of the given property dispatch 114 * to subsequent child animations. 115 * 116 * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles 117 * stack off to the left or right side slightly. 118 */ getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property)119 abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property); 120 121 /** 122 * Returns the SpringForce to be used for the given child view's property animation. Despite 123 * these usually being similar or identical across properties and views, {@link SpringForce} 124 * also contains the SpringAnimation's final position, so we have to construct a new one for 125 * each animation rather than using a constant. 126 */ getSpringForce(DynamicAnimation.ViewProperty property, View view)127 abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view); 128 129 /** 130 * Called when a new child is added at the specified index. Controllers can use this 131 * opportunity to animate in the new view. 132 */ onChildAdded(View child, int index)133 abstract void onChildAdded(View child, int index); 134 135 /** 136 * Called with a child view that has been removed from the layout, from the given index. The 137 * passed view has been removed from the layout and added back as a transient view, which 138 * renders normally, but is not part of the normal view hierarchy and will not be considered 139 * by getChildAt() and getChildCount(). 140 * 141 * The controller can perform animations on the child (either manually, or by using 142 * {@link #animationForChild(View)}), and then call finishRemoval when complete. 143 * 144 * finishRemoval must be called by implementations of this method, or transient views will 145 * never be removed. 146 */ onChildRemoved(View child, int index, Runnable finishRemoval)147 abstract void onChildRemoved(View child, int index, Runnable finishRemoval); 148 149 /** Called when a child view has been reordered in the view hierachy. */ onChildReordered(View child, int oldIndex, int newIndex)150 abstract void onChildReordered(View child, int oldIndex, int newIndex); 151 152 /** 153 * Called when the controller is set as the active animation controller for the given 154 * layout. Once active, the controller can start animations using the animator instances 155 * returned by {@link #animationForChild}. 156 * 157 * While all animations started by the previous controller will be cancelled, the new 158 * controller should not make any assumptions about the state of the layout or its children. 159 * Their translation, alpha, scale, etc. values may have been changed by the previous 160 * controller and should be reset here if relevant. 161 */ onActiveControllerForLayout(PhysicsAnimationLayout layout)162 abstract void onActiveControllerForLayout(PhysicsAnimationLayout layout); 163 164 protected PhysicsAnimationLayout mLayout; 165 PhysicsAnimationController()166 PhysicsAnimationController() { } 167 168 /** Whether this controller is the currently active controller for its associated layout. */ isActiveController()169 protected boolean isActiveController() { 170 return mLayout != null && this == mLayout.mController; 171 } 172 setLayout(PhysicsAnimationLayout layout)173 protected void setLayout(PhysicsAnimationLayout layout) { 174 this.mLayout = layout; 175 onActiveControllerForLayout(layout); 176 } 177 getLayout()178 protected PhysicsAnimationLayout getLayout() { 179 return mLayout; 180 } 181 182 /** 183 * Returns a {@link PhysicsPropertyAnimator} instance for the given child view. 184 */ animationForChild(View child)185 protected PhysicsPropertyAnimator animationForChild(View child) { 186 PhysicsPropertyAnimator animator = 187 (PhysicsPropertyAnimator) child.getTag(R.id.physics_animator_tag); 188 189 if (animator == null) { 190 animator = mLayout.new PhysicsPropertyAnimator(child); 191 child.setTag(R.id.physics_animator_tag, animator); 192 } 193 194 animator.clearAnimator(); 195 animator.setAssociatedController(this); 196 197 return animator; 198 } 199 200 /** Returns a {@link PhysicsPropertyAnimator} instance for child at the given index. */ animationForChildAtIndex(int index)201 protected PhysicsPropertyAnimator animationForChildAtIndex(int index) { 202 return animationForChild(mLayout.getChildAt(index)); 203 } 204 205 /** 206 * Returns a {@link MultiAnimationStarter} whose startAll method will start the physics 207 * animations for all children from startIndex onward. The provided configurator will be 208 * called with each child's {@link PhysicsPropertyAnimator}, where it can set up each 209 * animation appropriately. 210 */ animationsForChildrenFromIndex( int startIndex, ChildAnimationConfigurator configurator)211 protected MultiAnimationStarter animationsForChildrenFromIndex( 212 int startIndex, ChildAnimationConfigurator configurator) { 213 final Set<DynamicAnimation.ViewProperty> allAnimatedProperties = new HashSet<>(); 214 final List<PhysicsPropertyAnimator> allChildAnims = new ArrayList<>(); 215 216 // Retrieve the animator for each child, ask the configurator to configure it, then save 217 // it and the properties it chose to animate. 218 for (int i = startIndex; i < mLayout.getChildCount(); i++) { 219 final PhysicsPropertyAnimator anim = animationForChildAtIndex(i); 220 configurator.configureAnimationForChildAtIndex(i, anim); 221 allAnimatedProperties.addAll(anim.getAnimatedProperties()); 222 allChildAnims.add(anim); 223 } 224 225 // Return a MultiAnimationStarter that will start all of the child animations, and also 226 // add a multiple property end listener to the layout that will call the end action 227 // provided to startAll() once all animations on the animated properties complete. 228 return (endActions) -> { 229 final Runnable runAllEndActions = () -> { 230 for (Runnable action : endActions) { 231 action.run(); 232 } 233 }; 234 235 // If there aren't any children to animate, just run the end actions. 236 if (mLayout.getChildCount() == 0) { 237 runAllEndActions.run(); 238 return; 239 } 240 241 if (endActions != null) { 242 setEndActionForMultipleProperties( 243 runAllEndActions, 244 allAnimatedProperties.toArray( 245 new DynamicAnimation.ViewProperty[0])); 246 } 247 248 for (PhysicsPropertyAnimator childAnim : allChildAnims) { 249 childAnim.start(); 250 } 251 }; 252 } 253 254 /** 255 * Sets an end action that will be run when all child animations for a given property have 256 * stopped running. 257 */ 258 protected void setEndActionForProperty( 259 Runnable action, DynamicAnimation.ViewProperty property) { 260 mLayout.mEndActionForProperty.put(property, action); 261 } 262 263 /** 264 * Sets an end action that will be run when all child animations for all of the given 265 * properties have stopped running. 266 */ 267 protected void setEndActionForMultipleProperties( 268 Runnable action, DynamicAnimation.ViewProperty... properties) { 269 final Runnable checkIfAllFinished = () -> { 270 if (!mLayout.arePropertiesAnimating(properties)) { 271 action.run(); 272 273 for (DynamicAnimation.ViewProperty property : properties) { 274 removeEndActionForProperty(property); 275 } 276 } 277 }; 278 279 for (DynamicAnimation.ViewProperty property : properties) { 280 setEndActionForProperty(checkIfAllFinished, property); 281 } 282 } 283 284 /** 285 * Removes the end listener that would have been called when all child animations for a 286 * given property stopped running. 287 */ 288 protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) { 289 mLayout.mEndActionForProperty.remove(property); 290 } 291 } 292 293 /** 294 * End actions that are called when every child's animation of the given property has finished. 295 */ 296 protected final HashMap<DynamicAnimation.ViewProperty, Runnable> mEndActionForProperty = 297 new HashMap<>(); 298 299 /** The currently active animation controller. */ 300 @Nullable protected PhysicsAnimationController mController; 301 302 public PhysicsAnimationLayout(Context context) { 303 super(context); 304 } 305 306 /** 307 * Sets the animation controller and constructs or reconfigures the layout's physics animations 308 * to meet the controller's specifications. 309 */ 310 public void setActiveController(PhysicsAnimationController controller) { 311 cancelAllAnimations(); 312 mEndActionForProperty.clear(); 313 314 this.mController = controller; 315 mController.setLayout(this); 316 317 // Set up animations for this controller's animated properties. 318 for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { 319 setUpAnimationsForProperty(property); 320 } 321 } 322 323 @Override 324 public void addView(View child, int index, ViewGroup.LayoutParams params) { 325 addViewInternal(child, index, params, false /* isReorder */); 326 } 327 328 @Override 329 public void removeView(View view) { 330 if (mController != null) { 331 final int index = indexOfChild(view); 332 333 // Remove the view and add it back as a transient view so we can animate it out. 334 super.removeView(view); 335 addTransientView(view, index); 336 337 // Tell the controller to animate this view out, and call the callback when it's 338 // finished. 339 mController.onChildRemoved(view, index, () -> { 340 // The controller says it's done with the transient view, cancel animations in case 341 // any are still running and then remove it. 342 cancelAnimationsOnView(view); 343 removeTransientView(view); 344 }); 345 } else { 346 // Without a controller, nobody will animate this view out, so it gets an unceremonious 347 // departure. 348 super.removeView(view); 349 } 350 } 351 352 @Override 353 public void removeViewAt(int index) { 354 removeView(getChildAt(index)); 355 } 356 357 /** Immediately re-orders the view to the given index. */ 358 public void reorderView(View view, int index) { 359 final int oldIndex = indexOfChild(view); 360 361 super.removeView(view); 362 addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */); 363 364 if (mController != null) { 365 mController.onChildReordered(view, oldIndex, index); 366 } 367 } 368 369 /** Checks whether any animations of the given properties are still running. */ 370 public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) { 371 for (int i = 0; i < getChildCount(); i++) { 372 if (arePropertiesAnimatingOnView(getChildAt(i), properties)) { 373 return true; 374 } 375 } 376 377 return false; 378 } 379 380 /** Checks whether any animations of the given properties are running on the given view. */ 381 public boolean arePropertiesAnimatingOnView( 382 View view, DynamicAnimation.ViewProperty... properties) { 383 final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view); 384 for (DynamicAnimation.ViewProperty property : properties) { 385 final SpringAnimation animation = getAnimationFromView(property, view); 386 if (animation != null && animation.isRunning()) { 387 return true; 388 } 389 390 // If the target animator is running, its update listener will trigger the translation 391 // physics animations at some point. We should consider the translation properties to be 392 // be animating in this case, even if the physics animations haven't been started yet. 393 final boolean isTranslation = 394 property.equals(DynamicAnimation.TRANSLATION_X) 395 || property.equals(DynamicAnimation.TRANSLATION_Y); 396 if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) { 397 return true; 398 } 399 } 400 401 return false; 402 } 403 404 /** Cancels all animations that are running on all child views, for all properties. */ 405 public void cancelAllAnimations() { 406 if (mController == null) { 407 return; 408 } 409 410 cancelAllAnimationsOfProperties( 411 mController.getAnimatedProperties().toArray(new DynamicAnimation.ViewProperty[]{})); 412 } 413 414 /** Cancels all animations that are running on all child views, for the given properties. */ 415 public void cancelAllAnimationsOfProperties(DynamicAnimation.ViewProperty... properties) { 416 if (mController == null) { 417 return; 418 } 419 420 for (int i = 0; i < getChildCount(); i++) { 421 for (DynamicAnimation.ViewProperty property : properties) { 422 final DynamicAnimation anim = getAnimationAtIndex(property, i); 423 if (anim != null) { 424 anim.cancel(); 425 } 426 } 427 } 428 } 429 430 /** Cancels all of the physics animations running on the given view. */ 431 public void cancelAnimationsOnView(View view) { 432 // If present, cancel the target animator so it doesn't restart the translation physics 433 // animations. 434 final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view); 435 if (targetAnimator != null) { 436 targetAnimator.cancel(); 437 } 438 439 // Cancel physics animations on the view. 440 for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { 441 getAnimationFromView(property, view).cancel(); 442 } 443 } 444 445 protected boolean isActiveController(PhysicsAnimationController controller) { 446 return mController == controller; 447 } 448 449 /** Whether the first child would be left of center if translated to the given x value. */ 450 protected boolean isFirstChildXLeftOfCenter(float x) { 451 if (getChildCount() > 0) { 452 return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2; 453 } else { 454 return false; // If there's no first child, really anything is correct, right? 455 } 456 } 457 458 /** ViewProperty's toString is useless, this returns a readable name for debug logging. */ 459 protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) { 460 if (property.equals(DynamicAnimation.TRANSLATION_X)) { 461 return "TRANSLATION_X"; 462 } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { 463 return "TRANSLATION_Y"; 464 } else if (property.equals(DynamicAnimation.SCALE_X)) { 465 return "SCALE_X"; 466 } else if (property.equals(DynamicAnimation.SCALE_Y)) { 467 return "SCALE_Y"; 468 } else if (property.equals(DynamicAnimation.ALPHA)) { 469 return "ALPHA"; 470 } else { 471 return "Unknown animation property."; 472 } 473 } 474 475 /** 476 * Adds a view to the layout. If this addition is not the result of a call to 477 * {@link #reorderView}, this will also notify the controller via 478 * {@link PhysicsAnimationController#onChildAdded} and set up animations for the view. 479 */ 480 private void addViewInternal( 481 View child, int index, ViewGroup.LayoutParams params, boolean isReorder) { 482 super.addView(child, index, params); 483 484 // Set up animations for the new view, if the controller is set. If it isn't set, we'll be 485 // setting up animations for all children when setActiveController is called. 486 if (mController != null && !isReorder) { 487 for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) { 488 setUpAnimationForChild(property, child, index); 489 } 490 491 mController.onChildAdded(child, index); 492 } 493 } 494 495 /** 496 * Retrieves the animation of the given property from the view at the given index via the view 497 * tag system. 498 */ 499 private SpringAnimation getAnimationAtIndex( 500 DynamicAnimation.ViewProperty property, int index) { 501 return getAnimationFromView(property, getChildAt(index)); 502 } 503 504 /** Retrieves the animation of the given property from the view via the view tag system. */ 505 private SpringAnimation getAnimationFromView( 506 DynamicAnimation.ViewProperty property, View view) { 507 return (SpringAnimation) view.getTag(getTagIdForProperty(property)); 508 } 509 510 /** Retrieves the target animator from the view via the view tag system. */ 511 @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) { 512 return (ObjectAnimator) view.getTag(R.id.target_animator_tag); 513 } 514 515 /** Sets up SpringAnimations of the given property for each child view in the layout. */ 516 private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) { 517 for (int i = 0; i < getChildCount(); i++) { 518 setUpAnimationForChild(property, getChildAt(i), i); 519 } 520 } 521 522 /** Constructs a SpringAnimation of the given property for a child view. */ 523 private void setUpAnimationForChild( 524 DynamicAnimation.ViewProperty property, View child, int index) { 525 SpringAnimation newAnim = new SpringAnimation(child, property); 526 newAnim.addUpdateListener((animation, value, velocity) -> { 527 final int indexOfChild = indexOfChild(child); 528 final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild); 529 530 if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) { 531 return; 532 } 533 534 final float offset = mController.getOffsetForChainedPropertyAnimation(property); 535 if (nextAnimInChain < getChildCount()) { 536 getAnimationAtIndex(property, nextAnimInChain) 537 .animateToFinalPosition(value + offset); 538 } 539 }); 540 541 newAnim.setSpring(mController.getSpringForce(property, child)); 542 newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property)); 543 child.setTag(getTagIdForProperty(property), newAnim); 544 } 545 546 /** Return a stable ID to use as a tag key for the given property's animations. */ 547 private int getTagIdForProperty(DynamicAnimation.ViewProperty property) { 548 if (property.equals(DynamicAnimation.TRANSLATION_X)) { 549 return R.id.translation_x_dynamicanimation_tag; 550 } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) { 551 return R.id.translation_y_dynamicanimation_tag; 552 } else if (property.equals(DynamicAnimation.SCALE_X)) { 553 return R.id.scale_x_dynamicanimation_tag; 554 } else if (property.equals(DynamicAnimation.SCALE_Y)) { 555 return R.id.scale_y_dynamicanimation_tag; 556 } else if (property.equals(DynamicAnimation.ALPHA)) { 557 return R.id.alpha_dynamicanimation_tag; 558 } 559 560 return -1; 561 } 562 563 /** 564 * End listener that is added to each individual DynamicAnimation, which dispatches to a single 565 * listener when every other animation of the given property is no longer running. 566 * 567 * This is required since chained DynamicAnimations can stop and start again due to changes in 568 * upstream animations. This means that adding an end listener to just the last animation is not 569 * sufficient. By firing only when every other animation on the property has stopped running, we 570 * ensure that no animation will be restarted after the single end listener is called. 571 */ 572 protected class AllAnimationsForPropertyFinishedEndListener 573 implements DynamicAnimation.OnAnimationEndListener { 574 private DynamicAnimation.ViewProperty mProperty; 575 576 AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) { 577 this.mProperty = property; 578 } 579 580 @Override 581 public void onAnimationEnd( 582 DynamicAnimation anim, boolean canceled, float value, float velocity) { 583 if (!arePropertiesAnimating(mProperty)) { 584 if (mEndActionForProperty.containsKey(mProperty)) { 585 final Runnable callback = mEndActionForProperty.get(mProperty); 586 587 if (callback != null) { 588 callback.run(); 589 } 590 } 591 } 592 } 593 } 594 595 /** 596 * Animator class returned by {@link PhysicsAnimationController#animationForChild}, to allow 597 * controllers to animate child views using physics animations. 598 * 599 * See docs/physics-animation-layout.md for documentation and examples. 600 */ 601 protected class PhysicsPropertyAnimator { 602 /** The view whose properties this animator animates. */ 603 private View mView; 604 605 /** Start velocity to use for all property animations. */ 606 private float mDefaultStartVelocity = -Float.MAX_VALUE; 607 608 /** Start delay to use when start is called. */ 609 private long mStartDelay = 0; 610 611 /** Damping ratio to use for the animations. */ 612 private float mDampingRatio = -1; 613 614 /** Stiffness to use for the animations. */ 615 private float mStiffness = -1; 616 617 /** End actions to call when animations for the given property complete. */ 618 private Map<DynamicAnimation.ViewProperty, Runnable[]> mEndActionsForProperty = 619 new HashMap<>(); 620 621 /** 622 * Start velocities to use for TRANSLATION_X and TRANSLATION_Y, since these are often 623 * provided by VelocityTrackers and differ from each other. 624 */ 625 private Map<DynamicAnimation.ViewProperty, Float> mPositionStartVelocities = 626 new HashMap<>(); 627 628 /** 629 * End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed, 630 * if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously. 631 */ 632 @Nullable private Runnable[] mPositionEndActions; 633 634 /** 635 * All of the properties that have been set and will animate when {@link #start} is called. 636 */ 637 private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>(); 638 639 /** 640 * All of the initial property values that have been set. These values will be instantly set 641 * when {@link #start} is called, just before the animation begins. 642 */ 643 private Map<DynamicAnimation.ViewProperty, Float> mInitialPropertyValues = new HashMap<>(); 644 645 /** The animation controller that last retrieved this animator instance. */ 646 private PhysicsAnimationController mAssociatedController; 647 648 /** 649 * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As 650 * the path is traversed, the view's translation spring animation final positions are 651 * updated such that the view 'follows' the current position on the path. 652 */ 653 @Nullable private ObjectAnimator mPathAnimator; 654 655 /** Current position on the path. This is animated by {@link #mPathAnimator}. */ 656 private PointF mCurrentPointOnPath = new PointF(); 657 658 /** 659 * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value 660 * of {@link #mCurrentPointOnPath}. 661 */ 662 private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty = 663 new FloatProperty<PhysicsPropertyAnimator>("PathX") { 664 @Override 665 public void setValue(PhysicsPropertyAnimator object, float value) { 666 mCurrentPointOnPath.x = value; 667 } 668 669 @Override 670 public Float get(PhysicsPropertyAnimator object) { 671 return mCurrentPointOnPath.x; 672 } 673 }; 674 675 private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty = 676 new FloatProperty<PhysicsPropertyAnimator>("PathY") { 677 @Override 678 public void setValue(PhysicsPropertyAnimator object, float value) { 679 mCurrentPointOnPath.y = value; 680 } 681 682 @Override 683 public Float get(PhysicsPropertyAnimator object) { 684 return mCurrentPointOnPath.y; 685 } 686 }; 687 688 protected PhysicsPropertyAnimator(View view) { 689 this.mView = view; 690 } 691 692 /** Animate a property to the given value, then call the optional end actions. */ 693 public PhysicsPropertyAnimator property( 694 DynamicAnimation.ViewProperty property, float value, Runnable... endActions) { 695 mAnimatedProperties.put(property, value); 696 mEndActionsForProperty.put(property, endActions); 697 return this; 698 } 699 700 /** Animate the view's alpha value to the provided value. */ 701 public PhysicsPropertyAnimator alpha(float alpha, Runnable... endActions) { 702 return property(DynamicAnimation.ALPHA, alpha, endActions); 703 } 704 705 /** Set the view's alpha value to 'from', then animate it to the given value. */ 706 public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) { 707 mInitialPropertyValues.put(DynamicAnimation.ALPHA, from); 708 return alpha(to, endActions); 709 } 710 711 /** Animate the view's translationX value to the provided value. */ 712 public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) { 713 mPathAnimator = null; // We aren't using the path anymore if we're translating. 714 return property(DynamicAnimation.TRANSLATION_X, translationX, endActions); 715 } 716 717 /** Set the view's translationX value to 'from', then animate it to the given value. */ 718 public PhysicsPropertyAnimator translationX( 719 float from, float to, Runnable... endActions) { 720 mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_X, from); 721 return translationX(to, endActions); 722 } 723 724 /** Animate the view's translationY value to the provided value. */ 725 public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) { 726 mPathAnimator = null; // We aren't using the path anymore if we're translating. 727 return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions); 728 } 729 730 /** Set the view's translationY value to 'from', then animate it to the given value. */ 731 public PhysicsPropertyAnimator translationY( 732 float from, float to, Runnable... endActions) { 733 mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_Y, from); 734 return translationY(to, endActions); 735 } 736 737 /** 738 * Animate the view's translationX and translationY values, and call the end actions only 739 * once both TRANSLATION_X and TRANSLATION_Y animations have completed. 740 */ 741 public PhysicsPropertyAnimator position( 742 float translationX, float translationY, Runnable... endActions) { 743 mPositionEndActions = endActions; 744 translationX(translationX); 745 return translationY(translationY); 746 } 747 748 /** 749 * Animates a 'target' point that moves along the given path, using the provided duration 750 * and interpolator to animate the target. The view itself is animated using physics-based 751 * animations, whose final positions are updated to the target position as it animates. This 752 * results in the view 'following' the target in a realistic way. 753 * 754 * This method will override earlier calls to {@link #translationX}, {@link #translationY}, 755 * or {@link #position}, ultimately animating the view's position to the final point on the 756 * given path. 757 * 758 * Any provided end listeners will be called when the physics-based animations kicked off by 759 * the moving target have completed - not when the target animation completes. 760 */ 761 public PhysicsPropertyAnimator followAnimatedTargetAlongPath( 762 Path path, 763 int targetAnimDuration, 764 TimeInterpolator targetAnimInterpolator, 765 Runnable... endActions) { 766 mPathAnimator = ObjectAnimator.ofFloat( 767 this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path); 768 mPathAnimator.setDuration(targetAnimDuration); 769 mPathAnimator.setInterpolator(targetAnimInterpolator); 770 771 mPositionEndActions = endActions; 772 773 // Remove translation related values since we're going to ignore them and follow the 774 // path instead. 775 clearTranslationValues(); 776 return this; 777 } 778 779 private void clearTranslationValues() { 780 mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X); 781 mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y); 782 mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X); 783 mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y); 784 mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X); 785 mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y); 786 } 787 788 /** Animate the view's scaleX value to the provided value. */ 789 public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) { 790 return property(DynamicAnimation.SCALE_X, scaleX, endActions); 791 } 792 793 /** Set the view's scaleX value to 'from', then animate it to the given value. */ 794 public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) { 795 mInitialPropertyValues.put(DynamicAnimation.SCALE_X, from); 796 return scaleX(to, endActions); 797 } 798 799 /** Animate the view's scaleY value to the provided value. */ 800 public PhysicsPropertyAnimator scaleY(float scaleY, Runnable... endActions) { 801 return property(DynamicAnimation.SCALE_Y, scaleY, endActions); 802 } 803 804 /** Set the view's scaleY value to 'from', then animate it to the given value. */ 805 public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) { 806 mInitialPropertyValues.put(DynamicAnimation.SCALE_Y, from); 807 return scaleY(to, endActions); 808 } 809 810 /** Set the start velocity to use for all property animations. */ 811 public PhysicsPropertyAnimator withStartVelocity(float startVel) { 812 mDefaultStartVelocity = startVel; 813 return this; 814 } 815 816 /** 817 * Set the damping ratio to use for this animation. If not supplied, will default to the 818 * value from {@link PhysicsAnimationController#getSpringForce}. 819 */ 820 public PhysicsPropertyAnimator withDampingRatio(float dampingRatio) { 821 mDampingRatio = dampingRatio; 822 return this; 823 } 824 825 /** 826 * Set the stiffness to use for this animation. If not supplied, will default to the 827 * value from {@link PhysicsAnimationController#getSpringForce}. 828 */ 829 public PhysicsPropertyAnimator withStiffness(float stiffness) { 830 mStiffness = stiffness; 831 return this; 832 } 833 834 /** 835 * Set the start velocities to use for TRANSLATION_X and TRANSLATION_Y animations. This 836 * overrides any value set via {@link #withStartVelocity(float)} for those properties. 837 */ 838 public PhysicsPropertyAnimator withPositionStartVelocities(float velX, float velY) { 839 mPositionStartVelocities.put(DynamicAnimation.TRANSLATION_X, velX); 840 mPositionStartVelocities.put(DynamicAnimation.TRANSLATION_Y, velY); 841 return this; 842 } 843 844 /** Set a delay, in milliseconds, before kicking off the animations. */ 845 public PhysicsPropertyAnimator withStartDelay(long startDelay) { 846 mStartDelay = startDelay; 847 return this; 848 } 849 850 /** 851 * Start the animations, and call the optional end actions once all animations for every 852 * animated property on every child (including chained animations) have ended. 853 */ 854 public void start(Runnable... after) { 855 if (!isActiveController(mAssociatedController)) { 856 Log.w(TAG, "Only the active animation controller is allowed to start animations. " 857 + "Use PhysicsAnimationLayout#setActiveController to set the active " 858 + "animation controller."); 859 return; 860 } 861 862 final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties(); 863 864 // If there are end actions, set an end listener on the layout for all the properties 865 // we're about to animate. 866 if (after != null && after.length > 0) { 867 final DynamicAnimation.ViewProperty[] propertiesArray = 868 properties.toArray(new DynamicAnimation.ViewProperty[0]); 869 mAssociatedController.setEndActionForMultipleProperties(() -> { 870 for (Runnable callback : after) { 871 callback.run(); 872 } 873 }, propertiesArray); 874 } 875 876 // If we used position-specific end actions, we'll need to listen for both TRANSLATION_X 877 // and TRANSLATION_Y animations ending, and call them once both have finished. 878 if (mPositionEndActions != null) { 879 final SpringAnimation translationXAnim = 880 getAnimationFromView(DynamicAnimation.TRANSLATION_X, mView); 881 final SpringAnimation translationYAnim = 882 getAnimationFromView(DynamicAnimation.TRANSLATION_Y, mView); 883 final Runnable waitForBothXAndY = () -> { 884 if (!translationXAnim.isRunning() && !translationYAnim.isRunning()) { 885 if (mPositionEndActions != null) { 886 for (Runnable callback : mPositionEndActions) { 887 callback.run(); 888 } 889 } 890 891 mPositionEndActions = null; 892 } 893 }; 894 895 mEndActionsForProperty.put(DynamicAnimation.TRANSLATION_X, 896 new Runnable[]{waitForBothXAndY}); 897 mEndActionsForProperty.put(DynamicAnimation.TRANSLATION_Y, 898 new Runnable[]{waitForBothXAndY}); 899 } 900 901 if (mPathAnimator != null) { 902 startPathAnimation(); 903 } 904 905 // Actually start the animations. 906 for (DynamicAnimation.ViewProperty property : properties) { 907 // Don't start translation animations if we're using a path animator, the update 908 // listeners added to that animator will take care of that. 909 if (mPathAnimator != null 910 && (property.equals(DynamicAnimation.TRANSLATION_X) 911 || property.equals(DynamicAnimation.TRANSLATION_Y))) { 912 return; 913 } 914 915 if (mInitialPropertyValues.containsKey(property)) { 916 property.setValue(mView, mInitialPropertyValues.get(property)); 917 } 918 919 final SpringForce defaultSpringForce = mController.getSpringForce(property, mView); 920 animateValueForChild( 921 property, 922 mView, 923 mAnimatedProperties.get(property), 924 mPositionStartVelocities.getOrDefault(property, mDefaultStartVelocity), 925 mStartDelay, 926 mStiffness >= 0 ? mStiffness : defaultSpringForce.getStiffness(), 927 mDampingRatio >= 0 ? mDampingRatio : defaultSpringForce.getDampingRatio(), 928 mEndActionsForProperty.get(property)); 929 } 930 931 clearAnimator(); 932 } 933 934 /** Returns the set of properties that will animate once {@link #start} is called. */ 935 protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() { 936 final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>( 937 mAnimatedProperties.keySet()); 938 939 // If we're using a path animator, it'll kick off translation animations. 940 if (mPathAnimator != null) { 941 animatedProperties.add(DynamicAnimation.TRANSLATION_X); 942 animatedProperties.add(DynamicAnimation.TRANSLATION_Y); 943 } 944 945 return animatedProperties; 946 } 947 948 /** 949 * Animates the property of the given child view, then runs the callback provided when the 950 * animation ends. 951 */ 952 protected void animateValueForChild( 953 DynamicAnimation.ViewProperty property, 954 View view, 955 float value, 956 float startVel, 957 long startDelay, 958 float stiffness, 959 float dampingRatio, 960 Runnable... afterCallbacks) { 961 if (view != null) { 962 final SpringAnimation animation = 963 (SpringAnimation) view.getTag(getTagIdForProperty(property)); 964 if (afterCallbacks != null) { 965 animation.addEndListener(new OneTimeEndListener() { 966 @Override 967 public void onAnimationEnd(DynamicAnimation animation, boolean canceled, 968 float value, float velocity) { 969 super.onAnimationEnd(animation, canceled, value, velocity); 970 for (Runnable runnable : afterCallbacks) { 971 runnable.run(); 972 } 973 } 974 }); 975 } 976 977 final SpringForce animationSpring = animation.getSpring(); 978 979 if (animationSpring == null) { 980 return; 981 } 982 983 final Runnable configureAndStartAnimation = () -> { 984 animationSpring.setStiffness(stiffness); 985 animationSpring.setDampingRatio(dampingRatio); 986 987 if (startVel > -Float.MAX_VALUE) { 988 animation.setStartVelocity(startVel); 989 } 990 991 animationSpring.setFinalPosition(value); 992 animation.start(); 993 }; 994 995 if (startDelay > 0) { 996 postDelayed(configureAndStartAnimation, startDelay); 997 } else { 998 configureAndStartAnimation.run(); 999 } 1000 } 1001 } 1002 1003 /** 1004 * Updates the final position of a view's animation, without changing any of the animation's 1005 * other settings. Calling this before an initial call to {@link #animateValueForChild} will 1006 * work, but result in unknown values for stiffness, etc. and is not recommended. 1007 */ 1008 private void updateValueForChild( 1009 DynamicAnimation.ViewProperty property, View view, float position) { 1010 if (view != null) { 1011 final SpringAnimation animation = 1012 (SpringAnimation) view.getTag(getTagIdForProperty(property)); 1013 final SpringForce animationSpring = animation.getSpring(); 1014 1015 if (animationSpring == null) { 1016 return; 1017 } 1018 1019 animationSpring.setFinalPosition(position); 1020 animation.start(); 1021 } 1022 } 1023 1024 /** 1025 * Configures the path animator to respect the settings passed into the animation builder 1026 * and adds update listeners that update the translation physics animations. Then, starts 1027 * the path animation. 1028 */ 1029 protected void startPathAnimation() { 1030 final SpringForce defaultSpringForceX = mController.getSpringForce( 1031 DynamicAnimation.TRANSLATION_X, mView); 1032 final SpringForce defaultSpringForceY = mController.getSpringForce( 1033 DynamicAnimation.TRANSLATION_Y, mView); 1034 1035 if (mStartDelay > 0) { 1036 mPathAnimator.setStartDelay(mStartDelay); 1037 } 1038 1039 final Runnable updatePhysicsAnims = () -> { 1040 updateValueForChild( 1041 DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x); 1042 updateValueForChild( 1043 DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y); 1044 }; 1045 1046 mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run()); 1047 mPathAnimator.addListener(new AnimatorListenerAdapter() { 1048 @Override 1049 public void onAnimationStart(Animator animation) { 1050 animateValueForChild( 1051 DynamicAnimation.TRANSLATION_X, 1052 mView, 1053 mCurrentPointOnPath.x, 1054 mDefaultStartVelocity, 1055 0 /* startDelay */, 1056 mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(), 1057 mDampingRatio >= 0 1058 ? mDampingRatio 1059 : defaultSpringForceX.getDampingRatio()); 1060 1061 animateValueForChild( 1062 DynamicAnimation.TRANSLATION_Y, 1063 mView, 1064 mCurrentPointOnPath.y, 1065 mDefaultStartVelocity, 1066 0 /* startDelay */, 1067 mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(), 1068 mDampingRatio >= 0 1069 ? mDampingRatio 1070 : defaultSpringForceY.getDampingRatio()); 1071 } 1072 1073 @Override 1074 public void onAnimationEnd(Animator animation) { 1075 updatePhysicsAnims.run(); 1076 } 1077 }); 1078 1079 // If there's a target animator saved for the view, make sure it's not running. 1080 final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView); 1081 if (targetAnimator != null) { 1082 targetAnimator.cancel(); 1083 } 1084 1085 mView.setTag(R.id.target_animator_tag, mPathAnimator); 1086 mPathAnimator.start(); 1087 } 1088 1089 private void clearAnimator() { 1090 mInitialPropertyValues.clear(); 1091 mAnimatedProperties.clear(); 1092 mPositionStartVelocities.clear(); 1093 mDefaultStartVelocity = -Float.MAX_VALUE; 1094 mStartDelay = 0; 1095 mStiffness = -1; 1096 mDampingRatio = -1; 1097 mEndActionsForProperty.clear(); 1098 mPathAnimator = null; 1099 mPositionEndActions = null; 1100 } 1101 1102 /** 1103 * Sets the controller that last retrieved this animator instance, so that we can prevent 1104 * {@link #start} from actually starting animations if called by a non-active controller. 1105 */ 1106 private void setAssociatedController(PhysicsAnimationController controller) { 1107 mAssociatedController = controller; 1108 } 1109 } 1110 1111 @Override 1112 protected boolean canReceivePointerEvents() { 1113 return false; 1114 } 1115 } 1116