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