1 /* 2 * Copyright (C) 2017 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 package com.android.launcher3.anim; 17 18 import static com.android.launcher3.anim.Interpolators.LINEAR; 19 import static com.android.launcher3.config.FeatureFlags.QUICKSTEP_SPRINGS; 20 21 import android.animation.Animator; 22 import android.animation.Animator.AnimatorListener; 23 import android.animation.AnimatorListenerAdapter; 24 import android.animation.AnimatorSet; 25 import android.animation.TimeInterpolator; 26 import android.animation.ValueAnimator; 27 import android.util.Log; 28 29 import androidx.dynamicanimation.animation.DynamicAnimation; 30 import androidx.dynamicanimation.animation.SpringAnimation; 31 32 import java.util.ArrayList; 33 import java.util.Collections; 34 import java.util.HashSet; 35 import java.util.List; 36 import java.util.Set; 37 38 /** 39 * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators 40 * and durations. 41 * 42 * Note: The implementation does not support start delays on child animations or 43 * sequential playbacks. 44 */ 45 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener { 46 47 private static final String TAG = "AnimatorPlaybackCtrler"; 48 private static boolean DEBUG = false; 49 wrap(AnimatorSet anim, long duration)50 public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) { 51 return wrap(anim, duration, null); 52 } 53 54 /** 55 * Creates an animation controller for the provided animation. 56 * The actual duration does not matter as the animation is manually controlled. It just 57 * needs to be larger than the total number of pixels so that we don't have jittering due 58 * to float (animation-fraction * total duration) to int conversion. 59 */ wrap(AnimatorSet anim, long duration, Runnable onCancelRunnable)60 public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration, 61 Runnable onCancelRunnable) { 62 63 /** 64 * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed. 65 */ 66 return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable); 67 } 68 69 private final ValueAnimator mAnimationPlayer; 70 private final long mDuration; 71 72 protected final AnimatorSet mAnim; 73 private Set<SpringAnimation> mSprings; 74 75 protected float mCurrentFraction; 76 private Runnable mEndAction; 77 78 protected boolean mTargetCancelled = false; 79 protected Runnable mOnCancelRunnable; 80 81 private OnAnimationEndDispatcher mEndListener; 82 private DynamicAnimation.OnAnimationEndListener mSpringEndListener; 83 // We need this variable to ensure the end listener is called immediately, otherwise we run into 84 // issues where the callback interferes with the states of the swipe detector. 85 private boolean mSkipToEnd = false; 86 AnimatorPlaybackController(AnimatorSet anim, long duration, Runnable onCancelRunnable)87 protected AnimatorPlaybackController(AnimatorSet anim, long duration, 88 Runnable onCancelRunnable) { 89 mAnim = anim; 90 mDuration = duration; 91 mOnCancelRunnable = onCancelRunnable; 92 93 mAnimationPlayer = ValueAnimator.ofFloat(0, 1); 94 mAnimationPlayer.setInterpolator(LINEAR); 95 mEndListener = new OnAnimationEndDispatcher(); 96 mAnimationPlayer.addListener(mEndListener); 97 mAnimationPlayer.addUpdateListener(this); 98 99 mAnim.addListener(new AnimatorListenerAdapter() { 100 @Override 101 public void onAnimationCancel(Animator animation) { 102 mTargetCancelled = true; 103 if (mOnCancelRunnable != null) { 104 mOnCancelRunnable.run(); 105 mOnCancelRunnable = null; 106 } 107 } 108 109 @Override 110 public void onAnimationEnd(Animator animation) { 111 mTargetCancelled = false; 112 mOnCancelRunnable = null; 113 } 114 115 @Override 116 public void onAnimationStart(Animator animation) { 117 mTargetCancelled = false; 118 } 119 }); 120 121 mSprings = new HashSet<>(); 122 mSpringEndListener = (animation, canceled, value, velocity1) -> { 123 if (canceled) { 124 mEndListener.onAnimationCancel(mAnimationPlayer); 125 } else { 126 mEndListener.onAnimationEnd(mAnimationPlayer); 127 } 128 }; 129 } 130 getTarget()131 public AnimatorSet getTarget() { 132 return mAnim; 133 } 134 getDuration()135 public long getDuration() { 136 return mDuration; 137 } 138 getInterpolator()139 public TimeInterpolator getInterpolator() { 140 return mAnim.getInterpolator() != null ? mAnim.getInterpolator() : LINEAR; 141 } 142 143 /** 144 * Starts playing the animation forward from current position. 145 */ start()146 public void start() { 147 mAnimationPlayer.setFloatValues(mCurrentFraction, 1); 148 mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction)); 149 mAnimationPlayer.start(); 150 } 151 152 /** 153 * Starts playing the animation backwards from current position 154 */ reverse()155 public void reverse() { 156 mAnimationPlayer.setFloatValues(mCurrentFraction, 0); 157 mAnimationPlayer.setDuration(clampDuration(mCurrentFraction)); 158 mAnimationPlayer.start(); 159 } 160 161 /** 162 * Pauses the currently playing animation. 163 */ pause()164 public void pause() { 165 mAnimationPlayer.cancel(); 166 } 167 168 /** 169 * Returns the underlying animation used for controlling the set. 170 */ getAnimationPlayer()171 public ValueAnimator getAnimationPlayer() { 172 return mAnimationPlayer; 173 } 174 175 /** 176 * Sets the current animation position and updates all the child animators accordingly. 177 */ setPlayFraction(float fraction)178 public abstract void setPlayFraction(float fraction); 179 getProgressFraction()180 public float getProgressFraction() { 181 return mCurrentFraction; 182 } 183 getInterpolatedProgress()184 public float getInterpolatedProgress() { 185 return getInterpolator().getInterpolation(mCurrentFraction); 186 } 187 188 /** 189 * Sets the action to be called when the animation is completed. Also clears any 190 * previously set action. 191 */ setEndAction(Runnable runnable)192 public void setEndAction(Runnable runnable) { 193 mEndAction = runnable; 194 } 195 196 @Override onAnimationUpdate(ValueAnimator valueAnimator)197 public void onAnimationUpdate(ValueAnimator valueAnimator) { 198 setPlayFraction((float) valueAnimator.getAnimatedValue()); 199 } 200 clampDuration(float fraction)201 protected long clampDuration(float fraction) { 202 float playPos = mDuration * fraction; 203 if (playPos <= 0) { 204 return 0; 205 } else { 206 return Math.min((long) playPos, mDuration); 207 } 208 } 209 210 /** 211 * Starts playback and sets the spring. 212 */ dispatchOnStartWithVelocity(float end, float velocity)213 public void dispatchOnStartWithVelocity(float end, float velocity) { 214 if (!QUICKSTEP_SPRINGS.get()) { 215 dispatchOnStart(); 216 return; 217 } 218 219 if (DEBUG) Log.d(TAG, "dispatchOnStartWithVelocity#end=" + end + ", velocity=" + velocity); 220 221 for (Animator a : mAnim.getChildAnimations()) { 222 if (a instanceof SpringObjectAnimator) { 223 if (DEBUG) Log.d(TAG, "Found springAnimator=" + a); 224 SpringObjectAnimator springAnimator = (SpringObjectAnimator) a; 225 mSprings.add(springAnimator.getSpring()); 226 springAnimator.startSpring(end, velocity, mSpringEndListener); 227 } 228 } 229 230 dispatchOnStart(); 231 } 232 dispatchOnStart()233 public void dispatchOnStart() { 234 dispatchOnStartRecursively(mAnim); 235 } 236 dispatchOnStartRecursively(Animator animator)237 private void dispatchOnStartRecursively(Animator animator) { 238 List<AnimatorListener> listeners = animator instanceof SpringObjectAnimator 239 ? nonNullList(((SpringObjectAnimator) animator).getObjectAnimatorListeners()) 240 : nonNullList(animator.getListeners()); 241 242 for (AnimatorListener l : listeners) { 243 l.onAnimationStart(animator); 244 } 245 246 if (animator instanceof AnimatorSet) { 247 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 248 dispatchOnStartRecursively(anim); 249 } 250 } 251 } 252 253 /** 254 * Sets mOnCancelRunnable = null before dispatching the cancel and restoring the runnable. This 255 * is intended to be used only if you need to cancel but want to defer cleaning up yourself. 256 */ dispatchOnCancelWithoutCancelRunnable()257 public void dispatchOnCancelWithoutCancelRunnable() { 258 Runnable onCancel = mOnCancelRunnable; 259 setOnCancelRunnable(null); 260 dispatchOnCancel(); 261 setOnCancelRunnable(onCancel); 262 } 263 dispatchOnCancel()264 public void dispatchOnCancel() { 265 dispatchOnCancelRecursively(mAnim); 266 } 267 dispatchOnCancelRecursively(Animator animator)268 private void dispatchOnCancelRecursively(Animator animator) { 269 for (AnimatorListener l : nonNullList(animator.getListeners())) { 270 l.onAnimationCancel(animator); 271 } 272 273 if (animator instanceof AnimatorSet) { 274 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 275 dispatchOnCancelRecursively(anim); 276 } 277 } 278 } 279 dispatchSetInterpolator(TimeInterpolator interpolator)280 public void dispatchSetInterpolator(TimeInterpolator interpolator) { 281 dispatchSetInterpolatorRecursively(mAnim, interpolator); 282 } 283 dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator)284 private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) { 285 anim.setInterpolator(interpolator); 286 if (anim instanceof AnimatorSet) { 287 for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) { 288 dispatchSetInterpolatorRecursively(child, interpolator); 289 } 290 } 291 } 292 setOnCancelRunnable(Runnable runnable)293 public void setOnCancelRunnable(Runnable runnable) { 294 mOnCancelRunnable = runnable; 295 } 296 skipToEnd()297 public void skipToEnd() { 298 mSkipToEnd = true; 299 for (SpringAnimation spring : mSprings) { 300 if (spring.canSkipToEnd()) { 301 spring.skipToEnd(); 302 } 303 } 304 mAnimationPlayer.end(); 305 mSkipToEnd = false; 306 } 307 308 public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController { 309 310 private final ValueAnimator[] mChildAnimations; 311 AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, Runnable onCancelRunnable)312 private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, 313 Runnable onCancelRunnable) { 314 super(anim, duration, onCancelRunnable); 315 316 // Build animation list 317 ArrayList<ValueAnimator> childAnims = new ArrayList<>(); 318 getAnimationsRecur(mAnim, childAnims); 319 mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]); 320 } 321 getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out)322 private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) { 323 long forceDuration = anim.getDuration(); 324 TimeInterpolator forceInterpolator = anim.getInterpolator(); 325 for (Animator child : anim.getChildAnimations()) { 326 if (forceDuration > 0) { 327 child.setDuration(forceDuration); 328 } 329 if (forceInterpolator != null) { 330 child.setInterpolator(forceInterpolator); 331 } 332 if (child instanceof ValueAnimator) { 333 out.add((ValueAnimator) child); 334 } else if (child instanceof AnimatorSet) { 335 getAnimationsRecur((AnimatorSet) child, out); 336 } else { 337 throw new RuntimeException("Unknown animation type " + child); 338 } 339 } 340 } 341 342 @Override setPlayFraction(float fraction)343 public void setPlayFraction(float fraction) { 344 mCurrentFraction = fraction; 345 // Let the animator report the progress but don't apply the progress to child 346 // animations if it has been cancelled. 347 if (mTargetCancelled) { 348 return; 349 } 350 long playPos = clampDuration(fraction); 351 for (ValueAnimator anim : mChildAnimations) { 352 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration())); 353 } 354 } 355 } 356 isAnySpringRunning()357 private boolean isAnySpringRunning() { 358 for (SpringAnimation spring : mSprings) { 359 if (spring.isRunning()) { 360 return true; 361 } 362 } 363 return false; 364 } 365 366 /** 367 * Only dispatches the on end actions once the animator and all springs have completed running. 368 */ 369 private class OnAnimationEndDispatcher extends AnimationSuccessListener { 370 371 boolean mAnimatorDone = false; 372 boolean mSpringsDone = false; 373 boolean mDispatched = false; 374 375 @Override onAnimationStart(Animator animation)376 public void onAnimationStart(Animator animation) { 377 mCancelled = false; 378 mDispatched = false; 379 } 380 381 @Override onAnimationSuccess(Animator animator)382 public void onAnimationSuccess(Animator animator) { 383 if (mSprings.isEmpty()) { 384 mSpringsDone = mAnimatorDone = true; 385 } 386 if (isAnySpringRunning()) { 387 mAnimatorDone = true; 388 } else { 389 mSpringsDone = true; 390 } 391 392 // We wait for the spring (if any) to finish running before completing the end callback. 393 if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) { 394 dispatchOnEndRecursively(mAnim); 395 if (mEndAction != null) { 396 mEndAction.run(); 397 } 398 mDispatched = true; 399 } 400 } 401 dispatchOnEndRecursively(Animator animator)402 private void dispatchOnEndRecursively(Animator animator) { 403 for (AnimatorListener l : nonNullList(animator.getListeners())) { 404 l.onAnimationEnd(animator); 405 } 406 407 if (animator instanceof AnimatorSet) { 408 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 409 dispatchOnEndRecursively(anim); 410 } 411 } 412 } 413 } 414 nonNullList(ArrayList<T> list)415 private static <T> List<T> nonNullList(ArrayList<T> list) { 416 return list == null ? Collections.emptyList() : list; 417 } 418 } 419