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