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