blob: 563a0a7e43e13a0b4892ede16843f341559282fb [file] [log] [blame]
Joshua Tsujib1a796b2019-01-16 15:43:12 -08001/*
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
17package com.android.systemui.bubbles.animation;
18
Joshua Tsujidebd8312019-06-06 17:17:08 -040019import android.animation.Animator;
20import android.animation.AnimatorListenerAdapter;
21import android.animation.ObjectAnimator;
22import android.animation.TimeInterpolator;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080023import android.content.Context;
Joshua Tsujidebd8312019-06-06 17:17:08 -040024import android.graphics.Path;
25import android.graphics.PointF;
26import android.util.FloatProperty;
Joshua Tsujic36ee6f2019-05-28 17:00:16 -040027import android.util.Log;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080028import android.view.View;
29import android.view.ViewGroup;
30import android.widget.FrameLayout;
31
Joshua Tsujic36ee6f2019-05-28 17:00:16 -040032import androidx.annotation.Nullable;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080033import androidx.dynamicanimation.animation.DynamicAnimation;
34import androidx.dynamicanimation.animation.SpringAnimation;
35import androidx.dynamicanimation.animation.SpringForce;
36
37import com.android.systemui.R;
38
Joshua Tsujic1108432019-02-22 16:10:12 -050039import java.util.ArrayList;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080040import java.util.HashMap;
Joshua Tsujia08b6d32019-01-29 16:15:52 -050041import java.util.HashSet;
Joshua Tsujic1108432019-02-22 16:10:12 -050042import java.util.List;
43import java.util.Map;
Joshua Tsujib1a796b2019-01-16 15:43:12 -080044import java.util.Set;
45
46/**
47 * Layout that constructs physics-based animations for each of its children, which behave according
48 * to settings provided by a {@link PhysicsAnimationController} instance.
49 *
50 * See physics-animation-layout.md.
51 */
52public class PhysicsAnimationLayout extends FrameLayout {
53 private static final String TAG = "Bubbs.PAL";
54
55 /**
56 * Controls the construction, configuration, and use of the physics animations supplied by this
57 * layout.
58 */
59 abstract static class PhysicsAnimationController {
60
Joshua Tsujic1108432019-02-22 16:10:12 -050061 /** Configures a given {@link PhysicsPropertyAnimator} for a view at the given index. */
62 interface ChildAnimationConfigurator {
63
64 /**
65 * Called to configure the animator for the view at the given index.
66 *
67 * This method should make use of methods such as
68 * {@link PhysicsPropertyAnimator#translationX} and
69 * {@link PhysicsPropertyAnimator#withStartDelay} to configure the animation.
70 *
71 * Implementations should not call {@link PhysicsPropertyAnimator#start}, this will
72 * happen elsewhere after configuration is complete.
73 */
74 void configureAnimationForChildAtIndex(int index, PhysicsPropertyAnimator animation);
75 }
76
77 /**
78 * Returned by {@link #animationsForChildrenFromIndex} to allow starting multiple animations
79 * on multiple child views at the same time.
80 */
81 interface MultiAnimationStarter {
82
83 /**
84 * Start all animations and call the given end actions once all animations have
85 * completed.
86 */
87 void startAll(Runnable... endActions);
88 }
89
Joshua Tsujib1a796b2019-01-16 15:43:12 -080090 /**
91 * Constant to return from {@link #getNextAnimationInChain} if the animation should not be
92 * chained at all.
93 */
94 protected static final int NONE = -1;
95
96 /** Set of properties for which the layout should construct physics animations. */
97 abstract Set<DynamicAnimation.ViewProperty> getAnimatedProperties();
98
99 /**
100 * Returns the index of the next animation after the given index in the animation chain, or
101 * {@link #NONE} if it should not be chained, or if the chain should end at the given index.
102 *
103 * If a next index is returned, an update listener will be added to the animation at the
104 * given index that dispatches value updates to the animation at the next index. This
105 * creates a 'following' effect.
106 *
107 * Typical implementations of this method will return either index + 1, or index - 1, to
108 * create forward or backward chains between adjacent child views, but this is not required.
109 */
110 abstract int getNextAnimationInChain(DynamicAnimation.ViewProperty property, int index);
111
112 /**
113 * Offsets to be added to the value that chained animations of the given property dispatch
114 * to subsequent child animations.
115 *
116 * This is used for things like maintaining the 'stack' effect in Bubbles, where bubbles
117 * stack off to the left or right side slightly.
118 */
119 abstract float getOffsetForChainedPropertyAnimation(DynamicAnimation.ViewProperty property);
120
121 /**
122 * Returns the SpringForce to be used for the given child view's property animation. Despite
123 * these usually being similar or identical across properties and views, {@link SpringForce}
124 * also contains the SpringAnimation's final position, so we have to construct a new one for
125 * each animation rather than using a constant.
126 */
127 abstract SpringForce getSpringForce(DynamicAnimation.ViewProperty property, View view);
128
129 /**
130 * Called when a new child is added at the specified index. Controllers can use this
131 * opportunity to animate in the new view.
132 */
133 abstract void onChildAdded(View child, int index);
134
135 /**
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500136 * Called with a child view that has been removed from the layout, from the given index. The
137 * passed view has been removed from the layout and added back as a transient view, which
138 * renders normally, but is not part of the normal view hierarchy and will not be considered
139 * by getChildAt() and getChildCount().
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800140 *
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500141 * The controller can perform animations on the child (either manually, or by using
Joshua Tsujic1108432019-02-22 16:10:12 -0500142 * {@link #animationForChild(View)}), and then call finishRemoval when complete.
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500143 *
144 * finishRemoval must be called by implementations of this method, or transient views will
145 * never be removed.
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800146 */
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500147 abstract void onChildRemoved(View child, int index, Runnable finishRemoval);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800148
Joshua Tsujif49ee142019-05-29 16:32:01 -0400149 /** Called when a child view has been reordered in the view hierachy. */
150 abstract void onChildReordered(View child, int oldIndex, int newIndex);
151
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400152 /**
153 * Called when the controller is set as the active animation controller for the given
154 * layout. Once active, the controller can start animations using the animator instances
155 * returned by {@link #animationForChild}.
156 *
157 * While all animations started by the previous controller will be cancelled, the new
158 * controller should not make any assumptions about the state of the layout or its children.
159 * Their translation, alpha, scale, etc. values may have been changed by the previous
160 * controller and should be reset here if relevant.
161 */
162 abstract void onActiveControllerForLayout(PhysicsAnimationLayout layout);
163
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800164 protected PhysicsAnimationLayout mLayout;
165
166 PhysicsAnimationController() { }
167
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400168 /** Whether this controller is the currently active controller for its associated layout. */
169 protected boolean isActiveController() {
Joshua Tsuji395bcfe2019-07-02 19:23:23 -0400170 return mLayout != null && this == mLayout.mController;
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400171 }
172
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800173 protected void setLayout(PhysicsAnimationLayout layout) {
174 this.mLayout = layout;
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400175 onActiveControllerForLayout(layout);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800176 }
177
178 protected PhysicsAnimationLayout getLayout() {
179 return mLayout;
180 }
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500181
182 /**
Joshua Tsujic1108432019-02-22 16:10:12 -0500183 * Returns a {@link PhysicsPropertyAnimator} instance for the given child view.
184 */
185 protected PhysicsPropertyAnimator animationForChild(View child) {
186 PhysicsPropertyAnimator animator =
187 (PhysicsPropertyAnimator) child.getTag(R.id.physics_animator_tag);
188
189 if (animator == null) {
190 animator = mLayout.new PhysicsPropertyAnimator(child);
191 child.setTag(R.id.physics_animator_tag, animator);
192 }
193
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400194 animator.clearAnimator();
195 animator.setAssociatedController(this);
196
Joshua Tsujic1108432019-02-22 16:10:12 -0500197 return animator;
198 }
199
200 /** Returns a {@link PhysicsPropertyAnimator} instance for child at the given index. */
201 protected PhysicsPropertyAnimator animationForChildAtIndex(int index) {
202 return animationForChild(mLayout.getChildAt(index));
203 }
204
205 /**
206 * Returns a {@link MultiAnimationStarter} whose startAll method will start the physics
207 * animations for all children from startIndex onward. The provided configurator will be
208 * called with each child's {@link PhysicsPropertyAnimator}, where it can set up each
209 * animation appropriately.
210 */
211 protected MultiAnimationStarter animationsForChildrenFromIndex(
212 int startIndex, ChildAnimationConfigurator configurator) {
213 final Set<DynamicAnimation.ViewProperty> allAnimatedProperties = new HashSet<>();
214 final List<PhysicsPropertyAnimator> allChildAnims = new ArrayList<>();
215
216 // Retrieve the animator for each child, ask the configurator to configure it, then save
217 // it and the properties it chose to animate.
218 for (int i = startIndex; i < mLayout.getChildCount(); i++) {
219 final PhysicsPropertyAnimator anim = animationForChildAtIndex(i);
220 configurator.configureAnimationForChildAtIndex(i, anim);
221 allAnimatedProperties.addAll(anim.getAnimatedProperties());
222 allChildAnims.add(anim);
223 }
224
225 // Return a MultiAnimationStarter that will start all of the child animations, and also
226 // add a multiple property end listener to the layout that will call the end action
227 // provided to startAll() once all animations on the animated properties complete.
228 return (endActions) -> {
Joshua Tsujid54e8e02019-04-05 14:21:53 -0400229 final Runnable runAllEndActions = () -> {
230 for (Runnable action : endActions) {
231 action.run();
232 }
233 };
234
235 // If there aren't any children to animate, just run the end actions.
236 if (mLayout.getChildCount() == 0) {
237 runAllEndActions.run();
238 return;
239 }
240
Joshua Tsujic1108432019-02-22 16:10:12 -0500241 if (endActions != null) {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400242 setEndActionForMultipleProperties(
Joshua Tsujid54e8e02019-04-05 14:21:53 -0400243 runAllEndActions,
Joshua Tsujic1108432019-02-22 16:10:12 -0500244 allAnimatedProperties.toArray(
245 new DynamicAnimation.ViewProperty[0]));
246 }
247
248 for (PhysicsPropertyAnimator childAnim : allChildAnims) {
249 childAnim.start();
250 }
251 };
252 }
Joshua Tsujidebd8312019-06-06 17:17:08 -0400253
254 /**
255 * Sets an end action that will be run when all child animations for a given property have
256 * stopped running.
257 */
258 protected void setEndActionForProperty(
259 Runnable action, DynamicAnimation.ViewProperty property) {
260 mLayout.mEndActionForProperty.put(property, action);
261 }
262
263 /**
264 * Sets an end action that will be run when all child animations for all of the given
265 * properties have stopped running.
266 */
267 protected void setEndActionForMultipleProperties(
268 Runnable action, DynamicAnimation.ViewProperty... properties) {
269 final Runnable checkIfAllFinished = () -> {
270 if (!mLayout.arePropertiesAnimating(properties)) {
271 action.run();
272
273 for (DynamicAnimation.ViewProperty property : properties) {
274 removeEndActionForProperty(property);
275 }
276 }
277 };
278
279 for (DynamicAnimation.ViewProperty property : properties) {
280 setEndActionForProperty(checkIfAllFinished, property);
281 }
282 }
283
284 /**
285 * Removes the end listener that would have been called when all child animations for a
286 * given property stopped running.
287 */
288 protected void removeEndActionForProperty(DynamicAnimation.ViewProperty property) {
289 mLayout.mEndActionForProperty.remove(property);
290 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800291 }
292
293 /**
Joshua Tsujic1108432019-02-22 16:10:12 -0500294 * End actions that are called when every child's animation of the given property has finished.
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800295 */
Joshua Tsujic1108432019-02-22 16:10:12 -0500296 protected final HashMap<DynamicAnimation.ViewProperty, Runnable> mEndActionForProperty =
297 new HashMap<>();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800298
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800299 /** The currently active animation controller. */
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400300 @Nullable protected PhysicsAnimationController mController;
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800301
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800302 public PhysicsAnimationLayout(Context context) {
303 super(context);
304 }
305
306 /**
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800307 * Sets the animation controller and constructs or reconfigures the layout's physics animations
308 * to meet the controller's specifications.
309 */
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400310 public void setActiveController(PhysicsAnimationController controller) {
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800311 cancelAllAnimations();
Joshua Tsujic1108432019-02-22 16:10:12 -0500312 mEndActionForProperty.clear();
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800313
314 this.mController = controller;
315 mController.setLayout(this);
316
317 // Set up animations for this controller's animated properties.
318 for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
319 setUpAnimationsForProperty(property);
320 }
321 }
322
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800323 @Override
324 public void addView(View child, int index, ViewGroup.LayoutParams params) {
Joshua Tsujif49ee142019-05-29 16:32:01 -0400325 addViewInternal(child, index, params, false /* isReorder */);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800326 }
327
328 @Override
329 public void removeView(View view) {
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800330 if (mController != null) {
331 final int index = indexOfChild(view);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800332
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500333 // Remove the view and add it back as a transient view so we can animate it out.
334 super.removeView(view);
335 addTransientView(view, index);
336
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500337 // Tell the controller to animate this view out, and call the callback when it's
338 // finished.
Joshua Tsuji1575e6b2019-01-30 13:43:28 -0500339 mController.onChildRemoved(view, index, () -> {
Joshua Tsuji7df42862019-03-22 15:54:31 -0400340 // The controller says it's done with the transient view, cancel animations in case
341 // any are still running and then remove it.
342 cancelAnimationsOnView(view);
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500343 removeTransientView(view);
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500344 });
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800345 } else {
346 // Without a controller, nobody will animate this view out, so it gets an unceremonious
347 // departure.
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500348 super.removeView(view);
Joshua Tsujif49ee142019-05-29 16:32:01 -0400349 }
350 }
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500351
Joshua Tsujif49ee142019-05-29 16:32:01 -0400352 @Override
353 public void removeViewAt(int index) {
354 removeView(getChildAt(index));
355 }
356
357 /** Immediately re-orders the view to the given index. */
358 public void reorderView(View view, int index) {
359 final int oldIndex = indexOfChild(view);
360
361 super.removeView(view);
362 addViewInternal(view, index, view.getLayoutParams(), true /* isReorder */);
363
364 if (mController != null) {
365 mController.onChildReordered(view, oldIndex, index);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800366 }
367 }
368
369 /** Checks whether any animations of the given properties are still running. */
370 public boolean arePropertiesAnimating(DynamicAnimation.ViewProperty... properties) {
371 for (int i = 0; i < getChildCount(); i++) {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400372 if (arePropertiesAnimatingOnView(getChildAt(i), properties)) {
373 return true;
374 }
375 }
376
377 return false;
378 }
379
380 /** Checks whether any animations of the given properties are running on the given view. */
381 public boolean arePropertiesAnimatingOnView(
382 View view, DynamicAnimation.ViewProperty... properties) {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400383 final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400384 for (DynamicAnimation.ViewProperty property : properties) {
385 final SpringAnimation animation = getAnimationFromView(property, view);
386 if (animation != null && animation.isRunning()) {
387 return true;
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800388 }
Joshua Tsujidebd8312019-06-06 17:17:08 -0400389
390 // If the target animator is running, its update listener will trigger the translation
391 // physics animations at some point. We should consider the translation properties to be
392 // be animating in this case, even if the physics animations haven't been started yet.
393 final boolean isTranslation =
394 property.equals(DynamicAnimation.TRANSLATION_X)
395 || property.equals(DynamicAnimation.TRANSLATION_Y);
396 if (isTranslation && targetAnimator != null && targetAnimator.isRunning()) {
397 return true;
398 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800399 }
400
401 return false;
402 }
403
404 /** Cancels all animations that are running on all child views, for all properties. */
405 public void cancelAllAnimations() {
406 if (mController == null) {
407 return;
408 }
409
Joshua Tsujif75ca272019-08-02 10:18:51 -0400410 cancelAllAnimationsOfProperties(
411 mController.getAnimatedProperties().toArray(new DynamicAnimation.ViewProperty[]{}));
412 }
413
414 /** Cancels all animations that are running on all child views, for the given properties. */
415 public void cancelAllAnimationsOfProperties(DynamicAnimation.ViewProperty... properties) {
416 if (mController == null) {
417 return;
418 }
419
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800420 for (int i = 0; i < getChildCount(); i++) {
Joshua Tsujif75ca272019-08-02 10:18:51 -0400421 for (DynamicAnimation.ViewProperty property : properties) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500422 final DynamicAnimation anim = getAnimationAtIndex(property, i);
423 if (anim != null) {
424 anim.cancel();
425 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800426 }
427 }
428 }
429
Joshua Tsuji442b6272019-02-08 13:23:43 -0500430 /** Cancels all of the physics animations running on the given view. */
431 public void cancelAnimationsOnView(View view) {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400432 // If present, cancel the target animator so it doesn't restart the translation physics
433 // animations.
434 final ObjectAnimator targetAnimator = getTargetAnimatorFromView(view);
435 if (targetAnimator != null) {
436 targetAnimator.cancel();
437 }
438
439 // Cancel physics animations on the view.
Joshua Tsuji442b6272019-02-08 13:23:43 -0500440 for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
441 getAnimationFromView(property, view).cancel();
442 }
443 }
444
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400445 protected boolean isActiveController(PhysicsAnimationController controller) {
446 return mController == controller;
447 }
448
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800449 /** Whether the first child would be left of center if translated to the given x value. */
450 protected boolean isFirstChildXLeftOfCenter(float x) {
451 if (getChildCount() > 0) {
452 return x + (getChildAt(0).getWidth() / 2) < getWidth() / 2;
453 } else {
454 return false; // If there's no first child, really anything is correct, right?
455 }
456 }
457
458 /** ViewProperty's toString is useless, this returns a readable name for debug logging. */
459 protected static String getReadablePropertyName(DynamicAnimation.ViewProperty property) {
460 if (property.equals(DynamicAnimation.TRANSLATION_X)) {
461 return "TRANSLATION_X";
462 } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
463 return "TRANSLATION_Y";
464 } else if (property.equals(DynamicAnimation.SCALE_X)) {
465 return "SCALE_X";
466 } else if (property.equals(DynamicAnimation.SCALE_Y)) {
467 return "SCALE_Y";
468 } else if (property.equals(DynamicAnimation.ALPHA)) {
469 return "ALPHA";
470 } else {
471 return "Unknown animation property.";
472 }
473 }
474
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800475 /**
Joshua Tsujif49ee142019-05-29 16:32:01 -0400476 * Adds a view to the layout. If this addition is not the result of a call to
477 * {@link #reorderView}, this will also notify the controller via
478 * {@link PhysicsAnimationController#onChildAdded} and set up animations for the view.
479 */
480 private void addViewInternal(
481 View child, int index, ViewGroup.LayoutParams params, boolean isReorder) {
482 super.addView(child, index, params);
483
484 // Set up animations for the new view, if the controller is set. If it isn't set, we'll be
485 // setting up animations for all children when setActiveController is called.
486 if (mController != null && !isReorder) {
487 for (DynamicAnimation.ViewProperty property : mController.getAnimatedProperties()) {
488 setUpAnimationForChild(property, child, index);
489 }
490
491 mController.onChildAdded(child, index);
492 }
493 }
494
495 /**
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800496 * Retrieves the animation of the given property from the view at the given index via the view
497 * tag system.
498 */
499 private SpringAnimation getAnimationAtIndex(
500 DynamicAnimation.ViewProperty property, int index) {
Joshua Tsuji442b6272019-02-08 13:23:43 -0500501 return getAnimationFromView(property, getChildAt(index));
502 }
503
504 /** Retrieves the animation of the given property from the view via the view tag system. */
505 private SpringAnimation getAnimationFromView(
506 DynamicAnimation.ViewProperty property, View view) {
507 return (SpringAnimation) view.getTag(getTagIdForProperty(property));
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800508 }
509
Joshua Tsujidebd8312019-06-06 17:17:08 -0400510 /** Retrieves the target animator from the view via the view tag system. */
511 @Nullable private ObjectAnimator getTargetAnimatorFromView(View view) {
512 return (ObjectAnimator) view.getTag(R.id.target_animator_tag);
513 }
514
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800515 /** Sets up SpringAnimations of the given property for each child view in the layout. */
516 private void setUpAnimationsForProperty(DynamicAnimation.ViewProperty property) {
517 for (int i = 0; i < getChildCount(); i++) {
518 setUpAnimationForChild(property, getChildAt(i), i);
519 }
520 }
521
522 /** Constructs a SpringAnimation of the given property for a child view. */
523 private void setUpAnimationForChild(
524 DynamicAnimation.ViewProperty property, View child, int index) {
525 SpringAnimation newAnim = new SpringAnimation(child, property);
526 newAnim.addUpdateListener((animation, value, velocity) -> {
Joshua Tsuji7df42862019-03-22 15:54:31 -0400527 final int indexOfChild = indexOfChild(child);
Joshua Tsujicb97a112019-05-29 16:20:41 -0400528 final int nextAnimInChain = mController.getNextAnimationInChain(property, indexOfChild);
Joshua Tsujia08b6d32019-01-29 16:15:52 -0500529
Joshua Tsuji7df42862019-03-22 15:54:31 -0400530 if (nextAnimInChain == PhysicsAnimationController.NONE || indexOfChild < 0) {
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800531 return;
532 }
533
Joshua Tsujicb97a112019-05-29 16:20:41 -0400534 final float offset = mController.getOffsetForChainedPropertyAnimation(property);
535 if (nextAnimInChain < getChildCount()) {
536 getAnimationAtIndex(property, nextAnimInChain)
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800537 .animateToFinalPosition(value + offset);
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800538 }
539 });
540
541 newAnim.setSpring(mController.getSpringForce(property, child));
542 newAnim.addEndListener(new AllAnimationsForPropertyFinishedEndListener(property));
543 child.setTag(getTagIdForProperty(property), newAnim);
544 }
545
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800546 /** Return a stable ID to use as a tag key for the given property's animations. */
547 private int getTagIdForProperty(DynamicAnimation.ViewProperty property) {
548 if (property.equals(DynamicAnimation.TRANSLATION_X)) {
549 return R.id.translation_x_dynamicanimation_tag;
550 } else if (property.equals(DynamicAnimation.TRANSLATION_Y)) {
551 return R.id.translation_y_dynamicanimation_tag;
552 } else if (property.equals(DynamicAnimation.SCALE_X)) {
553 return R.id.scale_x_dynamicanimation_tag;
554 } else if (property.equals(DynamicAnimation.SCALE_Y)) {
555 return R.id.scale_y_dynamicanimation_tag;
556 } else if (property.equals(DynamicAnimation.ALPHA)) {
557 return R.id.alpha_dynamicanimation_tag;
558 }
559
560 return -1;
561 }
562
563 /**
564 * End listener that is added to each individual DynamicAnimation, which dispatches to a single
565 * listener when every other animation of the given property is no longer running.
566 *
567 * This is required since chained DynamicAnimations can stop and start again due to changes in
568 * upstream animations. This means that adding an end listener to just the last animation is not
569 * sufficient. By firing only when every other animation on the property has stopped running, we
570 * ensure that no animation will be restarted after the single end listener is called.
571 */
572 protected class AllAnimationsForPropertyFinishedEndListener
573 implements DynamicAnimation.OnAnimationEndListener {
574 private DynamicAnimation.ViewProperty mProperty;
575
576 AllAnimationsForPropertyFinishedEndListener(DynamicAnimation.ViewProperty property) {
577 this.mProperty = property;
578 }
579
580 @Override
581 public void onAnimationEnd(
582 DynamicAnimation anim, boolean canceled, float value, float velocity) {
583 if (!arePropertiesAnimating(mProperty)) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500584 if (mEndActionForProperty.containsKey(mProperty)) {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400585 final Runnable callback = mEndActionForProperty.get(mProperty);
586
587 if (callback != null) {
588 callback.run();
589 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -0800590 }
591 }
592 }
593 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500594
595 /**
Joshua Tsujic1108432019-02-22 16:10:12 -0500596 * Animator class returned by {@link PhysicsAnimationController#animationForChild}, to allow
597 * controllers to animate child views using physics animations.
598 *
599 * See docs/physics-animation-layout.md for documentation and examples.
Joshua Tsuji442b6272019-02-08 13:23:43 -0500600 */
Joshua Tsujic1108432019-02-22 16:10:12 -0500601 protected class PhysicsPropertyAnimator {
602 /** The view whose properties this animator animates. */
603 private View mView;
Joshua Tsuji442b6272019-02-08 13:23:43 -0500604
Joshua Tsujic1108432019-02-22 16:10:12 -0500605 /** Start velocity to use for all property animations. */
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400606 private float mDefaultStartVelocity = -Float.MAX_VALUE;
Joshua Tsujic1108432019-02-22 16:10:12 -0500607
608 /** Start delay to use when start is called. */
609 private long mStartDelay = 0;
610
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400611 /** Damping ratio to use for the animations. */
612 private float mDampingRatio = -1;
613
614 /** Stiffness to use for the animations. */
615 private float mStiffness = -1;
616
Joshua Tsujic1108432019-02-22 16:10:12 -0500617 /** End actions to call when animations for the given property complete. */
618 private Map<DynamicAnimation.ViewProperty, Runnable[]> mEndActionsForProperty =
619 new HashMap<>();
620
621 /**
622 * Start velocities to use for TRANSLATION_X and TRANSLATION_Y, since these are often
623 * provided by VelocityTrackers and differ from each other.
624 */
625 private Map<DynamicAnimation.ViewProperty, Float> mPositionStartVelocities =
626 new HashMap<>();
627
628 /**
629 * End actions to call when both TRANSLATION_X and TRANSLATION_Y animations have completed,
630 * if {@link #position} was used to animate TRANSLATION_X and TRANSLATION_Y simultaneously.
631 */
Joshua Tsuji6e78ae72019-07-11 13:45:53 -0400632 @Nullable private Runnable[] mPositionEndActions;
Joshua Tsujic1108432019-02-22 16:10:12 -0500633
634 /**
635 * All of the properties that have been set and will animate when {@link #start} is called.
636 */
637 private Map<DynamicAnimation.ViewProperty, Float> mAnimatedProperties = new HashMap<>();
638
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400639 /**
640 * All of the initial property values that have been set. These values will be instantly set
641 * when {@link #start} is called, just before the animation begins.
642 */
643 private Map<DynamicAnimation.ViewProperty, Float> mInitialPropertyValues = new HashMap<>();
644
645 /** The animation controller that last retrieved this animator instance. */
646 private PhysicsAnimationController mAssociatedController;
647
Joshua Tsujidebd8312019-06-06 17:17:08 -0400648 /**
649 * Animator used to traverse the path provided to {@link #followAnimatedTargetAlongPath}. As
650 * the path is traversed, the view's translation spring animation final positions are
651 * updated such that the view 'follows' the current position on the path.
652 */
653 @Nullable private ObjectAnimator mPathAnimator;
654
655 /** Current position on the path. This is animated by {@link #mPathAnimator}. */
656 private PointF mCurrentPointOnPath = new PointF();
657
658 /**
659 * FloatProperty instances that can be passed to {@link ObjectAnimator} to animate the value
660 * of {@link #mCurrentPointOnPath}.
661 */
662 private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathXProperty =
663 new FloatProperty<PhysicsPropertyAnimator>("PathX") {
664 @Override
665 public void setValue(PhysicsPropertyAnimator object, float value) {
666 mCurrentPointOnPath.x = value;
667 }
668
669 @Override
670 public Float get(PhysicsPropertyAnimator object) {
671 return mCurrentPointOnPath.x;
672 }
673 };
674
675 private final FloatProperty<PhysicsPropertyAnimator> mCurrentPointOnPathYProperty =
676 new FloatProperty<PhysicsPropertyAnimator>("PathY") {
677 @Override
678 public void setValue(PhysicsPropertyAnimator object, float value) {
679 mCurrentPointOnPath.y = value;
680 }
681
682 @Override
683 public Float get(PhysicsPropertyAnimator object) {
684 return mCurrentPointOnPath.y;
685 }
686 };
687
Joshua Tsujic1108432019-02-22 16:10:12 -0500688 protected PhysicsPropertyAnimator(View view) {
689 this.mView = view;
Joshua Tsuji442b6272019-02-08 13:23:43 -0500690 }
691
Joshua Tsujic1108432019-02-22 16:10:12 -0500692 /** Animate a property to the given value, then call the optional end actions. */
693 public PhysicsPropertyAnimator property(
694 DynamicAnimation.ViewProperty property, float value, Runnable... endActions) {
695 mAnimatedProperties.put(property, value);
696 mEndActionsForProperty.put(property, endActions);
697 return this;
698 }
Joshua Tsuji442b6272019-02-08 13:23:43 -0500699
Joshua Tsujic1108432019-02-22 16:10:12 -0500700 /** Animate the view's alpha value to the provided value. */
701 public PhysicsPropertyAnimator alpha(float alpha, Runnable... endActions) {
702 return property(DynamicAnimation.ALPHA, alpha, endActions);
703 }
704
705 /** Set the view's alpha value to 'from', then animate it to the given value. */
706 public PhysicsPropertyAnimator alpha(float from, float to, Runnable... endActions) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400707 mInitialPropertyValues.put(DynamicAnimation.ALPHA, from);
Joshua Tsujic1108432019-02-22 16:10:12 -0500708 return alpha(to, endActions);
709 }
710
711 /** Animate the view's translationX value to the provided value. */
712 public PhysicsPropertyAnimator translationX(float translationX, Runnable... endActions) {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400713 mPathAnimator = null; // We aren't using the path anymore if we're translating.
Joshua Tsujic1108432019-02-22 16:10:12 -0500714 return property(DynamicAnimation.TRANSLATION_X, translationX, endActions);
715 }
716
717 /** Set the view's translationX value to 'from', then animate it to the given value. */
718 public PhysicsPropertyAnimator translationX(
719 float from, float to, Runnable... endActions) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400720 mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_X, from);
Joshua Tsujic1108432019-02-22 16:10:12 -0500721 return translationX(to, endActions);
722 }
723
724 /** Animate the view's translationY value to the provided value. */
725 public PhysicsPropertyAnimator translationY(float translationY, Runnable... endActions) {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400726 mPathAnimator = null; // We aren't using the path anymore if we're translating.
Joshua Tsujic1108432019-02-22 16:10:12 -0500727 return property(DynamicAnimation.TRANSLATION_Y, translationY, endActions);
728 }
729
730 /** Set the view's translationY value to 'from', then animate it to the given value. */
731 public PhysicsPropertyAnimator translationY(
732 float from, float to, Runnable... endActions) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400733 mInitialPropertyValues.put(DynamicAnimation.TRANSLATION_Y, from);
Joshua Tsujic1108432019-02-22 16:10:12 -0500734 return translationY(to, endActions);
735 }
736
737 /**
738 * Animate the view's translationX and translationY values, and call the end actions only
739 * once both TRANSLATION_X and TRANSLATION_Y animations have completed.
740 */
741 public PhysicsPropertyAnimator position(
742 float translationX, float translationY, Runnable... endActions) {
743 mPositionEndActions = endActions;
744 translationX(translationX);
745 return translationY(translationY);
746 }
747
Joshua Tsujidebd8312019-06-06 17:17:08 -0400748 /**
749 * Animates a 'target' point that moves along the given path, using the provided duration
750 * and interpolator to animate the target. The view itself is animated using physics-based
751 * animations, whose final positions are updated to the target position as it animates. This
752 * results in the view 'following' the target in a realistic way.
753 *
754 * This method will override earlier calls to {@link #translationX}, {@link #translationY},
755 * or {@link #position}, ultimately animating the view's position to the final point on the
756 * given path.
757 *
758 * Any provided end listeners will be called when the physics-based animations kicked off by
759 * the moving target have completed - not when the target animation completes.
760 */
761 public PhysicsPropertyAnimator followAnimatedTargetAlongPath(
762 Path path,
763 int targetAnimDuration,
764 TimeInterpolator targetAnimInterpolator,
765 Runnable... endActions) {
766 mPathAnimator = ObjectAnimator.ofFloat(
767 this, mCurrentPointOnPathXProperty, mCurrentPointOnPathYProperty, path);
768 mPathAnimator.setDuration(targetAnimDuration);
769 mPathAnimator.setInterpolator(targetAnimInterpolator);
770
771 mPositionEndActions = endActions;
772
773 // Remove translation related values since we're going to ignore them and follow the
774 // path instead.
775 clearTranslationValues();
776 return this;
777 }
778
779 private void clearTranslationValues() {
780 mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_X);
781 mAnimatedProperties.remove(DynamicAnimation.TRANSLATION_Y);
782 mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_X);
783 mInitialPropertyValues.remove(DynamicAnimation.TRANSLATION_Y);
784 mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_X);
785 mEndActionForProperty.remove(DynamicAnimation.TRANSLATION_Y);
786 }
787
Joshua Tsujic1108432019-02-22 16:10:12 -0500788 /** Animate the view's scaleX value to the provided value. */
789 public PhysicsPropertyAnimator scaleX(float scaleX, Runnable... endActions) {
790 return property(DynamicAnimation.SCALE_X, scaleX, endActions);
791 }
792
793 /** Set the view's scaleX value to 'from', then animate it to the given value. */
794 public PhysicsPropertyAnimator scaleX(float from, float to, Runnable... endActions) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400795 mInitialPropertyValues.put(DynamicAnimation.SCALE_X, from);
Joshua Tsujic1108432019-02-22 16:10:12 -0500796 return scaleX(to, endActions);
797 }
798
799 /** Animate the view's scaleY value to the provided value. */
800 public PhysicsPropertyAnimator scaleY(float scaleY, Runnable... endActions) {
801 return property(DynamicAnimation.SCALE_Y, scaleY, endActions);
802 }
803
804 /** Set the view's scaleY value to 'from', then animate it to the given value. */
805 public PhysicsPropertyAnimator scaleY(float from, float to, Runnable... endActions) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400806 mInitialPropertyValues.put(DynamicAnimation.SCALE_Y, from);
Joshua Tsujic1108432019-02-22 16:10:12 -0500807 return scaleY(to, endActions);
808 }
809
810 /** Set the start velocity to use for all property animations. */
811 public PhysicsPropertyAnimator withStartVelocity(float startVel) {
812 mDefaultStartVelocity = startVel;
813 return this;
814 }
815
816 /**
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400817 * Set the damping ratio to use for this animation. If not supplied, will default to the
818 * value from {@link PhysicsAnimationController#getSpringForce}.
819 */
820 public PhysicsPropertyAnimator withDampingRatio(float dampingRatio) {
821 mDampingRatio = dampingRatio;
822 return this;
823 }
824
825 /**
826 * Set the stiffness to use for this animation. If not supplied, will default to the
827 * value from {@link PhysicsAnimationController#getSpringForce}.
828 */
829 public PhysicsPropertyAnimator withStiffness(float stiffness) {
830 mStiffness = stiffness;
831 return this;
832 }
833
834 /**
Joshua Tsujic1108432019-02-22 16:10:12 -0500835 * Set the start velocities to use for TRANSLATION_X and TRANSLATION_Y animations. This
836 * overrides any value set via {@link #withStartVelocity(float)} for those properties.
837 */
838 public PhysicsPropertyAnimator withPositionStartVelocities(float velX, float velY) {
839 mPositionStartVelocities.put(DynamicAnimation.TRANSLATION_X, velX);
840 mPositionStartVelocities.put(DynamicAnimation.TRANSLATION_Y, velY);
841 return this;
842 }
843
844 /** Set a delay, in milliseconds, before kicking off the animations. */
845 public PhysicsPropertyAnimator withStartDelay(long startDelay) {
846 mStartDelay = startDelay;
847 return this;
848 }
849
850 /**
851 * Start the animations, and call the optional end actions once all animations for every
852 * animated property on every child (including chained animations) have ended.
853 */
854 public void start(Runnable... after) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400855 if (!isActiveController(mAssociatedController)) {
856 Log.w(TAG, "Only the active animation controller is allowed to start animations. "
857 + "Use PhysicsAnimationLayout#setActiveController to set the active "
858 + "animation controller.");
859 return;
860 }
861
Joshua Tsujic1108432019-02-22 16:10:12 -0500862 final Set<DynamicAnimation.ViewProperty> properties = getAnimatedProperties();
863
864 // If there are end actions, set an end listener on the layout for all the properties
865 // we're about to animate.
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400866 if (after != null && after.length > 0) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500867 final DynamicAnimation.ViewProperty[] propertiesArray =
868 properties.toArray(new DynamicAnimation.ViewProperty[0]);
Joshua Tsujidebd8312019-06-06 17:17:08 -0400869 mAssociatedController.setEndActionForMultipleProperties(() -> {
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400870 for (Runnable callback : after) {
871 callback.run();
872 }
873 }, propertiesArray);
Joshua Tsujic1108432019-02-22 16:10:12 -0500874 }
875
876 // If we used position-specific end actions, we'll need to listen for both TRANSLATION_X
877 // and TRANSLATION_Y animations ending, and call them once both have finished.
878 if (mPositionEndActions != null) {
879 final SpringAnimation translationXAnim =
880 getAnimationFromView(DynamicAnimation.TRANSLATION_X, mView);
881 final SpringAnimation translationYAnim =
882 getAnimationFromView(DynamicAnimation.TRANSLATION_Y, mView);
883 final Runnable waitForBothXAndY = () -> {
884 if (!translationXAnim.isRunning() && !translationYAnim.isRunning()) {
885 if (mPositionEndActions != null) {
886 for (Runnable callback : mPositionEndActions) {
887 callback.run();
888 }
889 }
890
891 mPositionEndActions = null;
892 }
893 };
894
895 mEndActionsForProperty.put(DynamicAnimation.TRANSLATION_X,
896 new Runnable[]{waitForBothXAndY});
897 mEndActionsForProperty.put(DynamicAnimation.TRANSLATION_Y,
898 new Runnable[]{waitForBothXAndY});
899 }
900
Joshua Tsujidebd8312019-06-06 17:17:08 -0400901 if (mPathAnimator != null) {
902 startPathAnimation();
903 }
904
Joshua Tsujic1108432019-02-22 16:10:12 -0500905 // Actually start the animations.
906 for (DynamicAnimation.ViewProperty property : properties) {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400907 // Don't start translation animations if we're using a path animator, the update
908 // listeners added to that animator will take care of that.
909 if (mPathAnimator != null
910 && (property.equals(DynamicAnimation.TRANSLATION_X)
911 || property.equals(DynamicAnimation.TRANSLATION_Y))) {
912 return;
913 }
914
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400915 if (mInitialPropertyValues.containsKey(property)) {
916 property.setValue(mView, mInitialPropertyValues.get(property));
917 }
918
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400919 final SpringForce defaultSpringForce = mController.getSpringForce(property, mView);
Joshua Tsujic1108432019-02-22 16:10:12 -0500920 animateValueForChild(
921 property,
922 mView,
923 mAnimatedProperties.get(property),
924 mPositionStartVelocities.getOrDefault(property, mDefaultStartVelocity),
925 mStartDelay,
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400926 mStiffness >= 0 ? mStiffness : defaultSpringForce.getStiffness(),
927 mDampingRatio >= 0 ? mDampingRatio : defaultSpringForce.getDampingRatio(),
Joshua Tsujic1108432019-02-22 16:10:12 -0500928 mEndActionsForProperty.get(property));
929 }
930
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400931 clearAnimator();
Joshua Tsujic1108432019-02-22 16:10:12 -0500932 }
933
934 /** Returns the set of properties that will animate once {@link #start} is called. */
935 protected Set<DynamicAnimation.ViewProperty> getAnimatedProperties() {
Joshua Tsujidebd8312019-06-06 17:17:08 -0400936 final HashSet<DynamicAnimation.ViewProperty> animatedProperties = new HashSet<>(
937 mAnimatedProperties.keySet());
938
939 // If we're using a path animator, it'll kick off translation animations.
940 if (mPathAnimator != null) {
941 animatedProperties.add(DynamicAnimation.TRANSLATION_X);
942 animatedProperties.add(DynamicAnimation.TRANSLATION_Y);
943 }
944
945 return animatedProperties;
Joshua Tsujic1108432019-02-22 16:10:12 -0500946 }
947
948 /**
949 * Animates the property of the given child view, then runs the callback provided when the
950 * animation ends.
951 */
952 protected void animateValueForChild(
953 DynamicAnimation.ViewProperty property,
954 View view,
955 float value,
956 float startVel,
957 long startDelay,
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400958 float stiffness,
959 float dampingRatio,
Joshua Tsujidebd8312019-06-06 17:17:08 -0400960 Runnable... afterCallbacks) {
Joshua Tsujic1108432019-02-22 16:10:12 -0500961 if (view != null) {
962 final SpringAnimation animation =
963 (SpringAnimation) view.getTag(getTagIdForProperty(property));
964 if (afterCallbacks != null) {
965 animation.addEndListener(new OneTimeEndListener() {
966 @Override
967 public void onAnimationEnd(DynamicAnimation animation, boolean canceled,
968 float value, float velocity) {
969 super.onAnimationEnd(animation, canceled, value, velocity);
970 for (Runnable runnable : afterCallbacks) {
971 runnable.run();
972 }
973 }
974 });
975 }
976
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400977 final SpringForce animationSpring = animation.getSpring();
Joshua Tsuji4accf5982019-04-22 17:36:11 -0400978
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400979 if (animationSpring == null) {
980 return;
Joshua Tsujic1108432019-02-22 16:10:12 -0500981 }
982
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400983 final Runnable configureAndStartAnimation = () -> {
984 animationSpring.setStiffness(stiffness);
985 animationSpring.setDampingRatio(dampingRatio);
986
987 if (startVel > -Float.MAX_VALUE) {
988 animation.setStartVelocity(startVel);
989 }
990
991 animationSpring.setFinalPosition(value);
992 animation.start();
993 };
994
Joshua Tsujic1108432019-02-22 16:10:12 -0500995 if (startDelay > 0) {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400996 postDelayed(configureAndStartAnimation, startDelay);
Joshua Tsujic1108432019-02-22 16:10:12 -0500997 } else {
Joshua Tsujic36ee6f2019-05-28 17:00:16 -0400998 configureAndStartAnimation.run();
Joshua Tsuji442b6272019-02-08 13:23:43 -0500999 }
1000 }
1001 }
Joshua Tsujic36ee6f2019-05-28 17:00:16 -04001002
Joshua Tsujidebd8312019-06-06 17:17:08 -04001003 /**
1004 * Updates the final position of a view's animation, without changing any of the animation's
1005 * other settings. Calling this before an initial call to {@link #animateValueForChild} will
1006 * work, but result in unknown values for stiffness, etc. and is not recommended.
1007 */
1008 private void updateValueForChild(
1009 DynamicAnimation.ViewProperty property, View view, float position) {
1010 if (view != null) {
1011 final SpringAnimation animation =
1012 (SpringAnimation) view.getTag(getTagIdForProperty(property));
1013 final SpringForce animationSpring = animation.getSpring();
1014
1015 if (animationSpring == null) {
1016 return;
1017 }
1018
1019 animationSpring.setFinalPosition(position);
1020 animation.start();
1021 }
1022 }
1023
1024 /**
1025 * Configures the path animator to respect the settings passed into the animation builder
1026 * and adds update listeners that update the translation physics animations. Then, starts
1027 * the path animation.
1028 */
1029 protected void startPathAnimation() {
1030 final SpringForce defaultSpringForceX = mController.getSpringForce(
1031 DynamicAnimation.TRANSLATION_X, mView);
1032 final SpringForce defaultSpringForceY = mController.getSpringForce(
1033 DynamicAnimation.TRANSLATION_Y, mView);
1034
1035 if (mStartDelay > 0) {
1036 mPathAnimator.setStartDelay(mStartDelay);
1037 }
1038
1039 final Runnable updatePhysicsAnims = () -> {
1040 updateValueForChild(
1041 DynamicAnimation.TRANSLATION_X, mView, mCurrentPointOnPath.x);
1042 updateValueForChild(
1043 DynamicAnimation.TRANSLATION_Y, mView, mCurrentPointOnPath.y);
1044 };
1045
1046 mPathAnimator.addUpdateListener(pathAnim -> updatePhysicsAnims.run());
1047 mPathAnimator.addListener(new AnimatorListenerAdapter() {
1048 @Override
1049 public void onAnimationStart(Animator animation) {
1050 animateValueForChild(
1051 DynamicAnimation.TRANSLATION_X,
1052 mView,
1053 mCurrentPointOnPath.x,
1054 mDefaultStartVelocity,
1055 0 /* startDelay */,
1056 mStiffness >= 0 ? mStiffness : defaultSpringForceX.getStiffness(),
1057 mDampingRatio >= 0
1058 ? mDampingRatio
1059 : defaultSpringForceX.getDampingRatio());
1060
1061 animateValueForChild(
1062 DynamicAnimation.TRANSLATION_Y,
1063 mView,
1064 mCurrentPointOnPath.y,
1065 mDefaultStartVelocity,
1066 0 /* startDelay */,
1067 mStiffness >= 0 ? mStiffness : defaultSpringForceY.getStiffness(),
1068 mDampingRatio >= 0
1069 ? mDampingRatio
1070 : defaultSpringForceY.getDampingRatio());
1071 }
1072
1073 @Override
1074 public void onAnimationEnd(Animator animation) {
1075 updatePhysicsAnims.run();
1076 }
1077 });
1078
1079 // If there's a target animator saved for the view, make sure it's not running.
1080 final ObjectAnimator targetAnimator = getTargetAnimatorFromView(mView);
1081 if (targetAnimator != null) {
1082 targetAnimator.cancel();
1083 }
1084
1085 mView.setTag(R.id.target_animator_tag, mPathAnimator);
1086 mPathAnimator.start();
1087 }
1088
Joshua Tsujic36ee6f2019-05-28 17:00:16 -04001089 private void clearAnimator() {
1090 mInitialPropertyValues.clear();
1091 mAnimatedProperties.clear();
1092 mPositionStartVelocities.clear();
1093 mDefaultStartVelocity = -Float.MAX_VALUE;
1094 mStartDelay = 0;
1095 mStiffness = -1;
1096 mDampingRatio = -1;
1097 mEndActionsForProperty.clear();
Joshua Tsujidebd8312019-06-06 17:17:08 -04001098 mPathAnimator = null;
Joshua Tsuji6e78ae72019-07-11 13:45:53 -04001099 mPositionEndActions = null;
Joshua Tsujic36ee6f2019-05-28 17:00:16 -04001100 }
1101
1102 /**
1103 * Sets the controller that last retrieved this animator instance, so that we can prevent
1104 * {@link #start} from actually starting animations if called by a non-active controller.
1105 */
1106 private void setAssociatedController(PhysicsAnimationController controller) {
1107 mAssociatedController = controller;
1108 }
Joshua Tsuji442b6272019-02-08 13:23:43 -05001109 }
Tiger Huang2b210c22019-03-18 21:21:26 +08001110
1111 @Override
1112 protected boolean canReceivePointerEvents() {
1113 return false;
1114 }
Joshua Tsujib1a796b2019-01-16 15:43:12 -08001115}