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