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