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