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 java.util.ArrayList; 30 import java.util.Collections; 31 import java.util.HashSet; 32 import java.util.List; 33 import java.util.Set; 34 35 import androidx.dynamicanimation.animation.DynamicAnimation; 36 import androidx.dynamicanimation.animation.SpringAnimation; 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 dispatchOnCancel()253 public void dispatchOnCancel() { 254 dispatchOnCancelRecursively(mAnim); 255 } 256 dispatchOnCancelRecursively(Animator animator)257 private void dispatchOnCancelRecursively(Animator animator) { 258 for (AnimatorListener l : nonNullList(animator.getListeners())) { 259 l.onAnimationCancel(animator); 260 } 261 262 if (animator instanceof AnimatorSet) { 263 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 264 dispatchOnCancelRecursively(anim); 265 } 266 } 267 } 268 dispatchSetInterpolator(TimeInterpolator interpolator)269 public void dispatchSetInterpolator(TimeInterpolator interpolator) { 270 dispatchSetInterpolatorRecursively(mAnim, interpolator); 271 } 272 dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator)273 private void dispatchSetInterpolatorRecursively(Animator anim, TimeInterpolator interpolator) { 274 anim.setInterpolator(interpolator); 275 if (anim instanceof AnimatorSet) { 276 for (Animator child : nonNullList(((AnimatorSet) anim).getChildAnimations())) { 277 dispatchSetInterpolatorRecursively(child, interpolator); 278 } 279 } 280 } 281 setOnCancelRunnable(Runnable runnable)282 public void setOnCancelRunnable(Runnable runnable) { 283 mOnCancelRunnable = runnable; 284 } 285 getOnCancelRunnable()286 public Runnable getOnCancelRunnable() { 287 return mOnCancelRunnable; 288 } 289 skipToEnd()290 public void skipToEnd() { 291 mSkipToEnd = true; 292 for (SpringAnimation spring : mSprings) { 293 if (spring.canSkipToEnd()) { 294 spring.skipToEnd(); 295 } 296 } 297 mAnimationPlayer.end(); 298 mSkipToEnd = false; 299 } 300 301 public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController { 302 303 private final ValueAnimator[] mChildAnimations; 304 AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, Runnable onCancelRunnable)305 private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, 306 Runnable onCancelRunnable) { 307 super(anim, duration, onCancelRunnable); 308 309 // Build animation list 310 ArrayList<ValueAnimator> childAnims = new ArrayList<>(); 311 getAnimationsRecur(mAnim, childAnims); 312 mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]); 313 } 314 getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out)315 private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) { 316 long forceDuration = anim.getDuration(); 317 TimeInterpolator forceInterpolator = anim.getInterpolator(); 318 for (Animator child : anim.getChildAnimations()) { 319 if (forceDuration > 0) { 320 child.setDuration(forceDuration); 321 } 322 if (forceInterpolator != null) { 323 child.setInterpolator(forceInterpolator); 324 } 325 if (child instanceof ValueAnimator) { 326 out.add((ValueAnimator) child); 327 } else if (child instanceof AnimatorSet) { 328 getAnimationsRecur((AnimatorSet) child, out); 329 } else { 330 throw new RuntimeException("Unknown animation type " + child); 331 } 332 } 333 } 334 335 @Override setPlayFraction(float fraction)336 public void setPlayFraction(float fraction) { 337 mCurrentFraction = fraction; 338 // Let the animator report the progress but don't apply the progress to child 339 // animations if it has been cancelled. 340 if (mTargetCancelled) { 341 return; 342 } 343 long playPos = clampDuration(fraction); 344 for (ValueAnimator anim : mChildAnimations) { 345 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration())); 346 } 347 } 348 } 349 isAnySpringRunning()350 private boolean isAnySpringRunning() { 351 for (SpringAnimation spring : mSprings) { 352 if (spring.isRunning()) { 353 return true; 354 } 355 } 356 return false; 357 } 358 359 /** 360 * Only dispatches the on end actions once the animator and all springs have completed running. 361 */ 362 private class OnAnimationEndDispatcher extends AnimationSuccessListener { 363 364 boolean mAnimatorDone = false; 365 boolean mSpringsDone = false; 366 boolean mDispatched = false; 367 368 @Override onAnimationStart(Animator animation)369 public void onAnimationStart(Animator animation) { 370 mCancelled = false; 371 mDispatched = false; 372 } 373 374 @Override onAnimationSuccess(Animator animator)375 public void onAnimationSuccess(Animator animator) { 376 if (mSprings.isEmpty()) { 377 mSpringsDone = mAnimatorDone = true; 378 } 379 if (isAnySpringRunning()) { 380 mAnimatorDone = true; 381 } else { 382 mSpringsDone = true; 383 } 384 385 // We wait for the spring (if any) to finish running before completing the end callback. 386 if (!mDispatched && (mSkipToEnd || (mAnimatorDone && mSpringsDone))) { 387 dispatchOnEndRecursively(mAnim); 388 if (mEndAction != null) { 389 mEndAction.run(); 390 } 391 mDispatched = true; 392 } 393 } 394 dispatchOnEndRecursively(Animator animator)395 private void dispatchOnEndRecursively(Animator animator) { 396 for (AnimatorListener l : nonNullList(animator.getListeners())) { 397 l.onAnimationEnd(animator); 398 } 399 400 if (animator instanceof AnimatorSet) { 401 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 402 dispatchOnEndRecursively(anim); 403 } 404 } 405 } 406 } 407 nonNullList(ArrayList<T> list)408 private static <T> List<T> nonNullList(ArrayList<T> list) { 409 return list == null ? Collections.emptyList() : list; 410 } 411 } 412