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 android.animation.Animator; 19 import android.animation.Animator.AnimatorListener; 20 import android.animation.AnimatorListenerAdapter; 21 import android.animation.AnimatorSet; 22 import android.animation.TimeInterpolator; 23 import android.animation.ValueAnimator; 24 25 import java.util.ArrayList; 26 import java.util.Collections; 27 import java.util.List; 28 29 /** 30 * Helper class to control the playback of an {@link AnimatorSet}, with custom interpolators 31 * and durations. 32 * 33 * Note: The implementation does not support start delays on child animations or 34 * sequential playbacks. 35 */ 36 public abstract class AnimatorPlaybackController implements ValueAnimator.AnimatorUpdateListener { 37 38 public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration) { 39 return wrap(anim, duration, null); 40 } 41 42 /** 43 * Creates an animation controller for the provided animation. 44 * The actual duration does not matter as the animation is manually controlled. It just 45 * needs to be larger than the total number of pixels so that we don't have jittering due 46 * to float (animation-fraction * total duration) to int conversion. 47 */ 48 public static AnimatorPlaybackController wrap(AnimatorSet anim, long duration, 49 Runnable onCancelRunnable) { 50 51 /** 52 * TODO: use {@link AnimatorSet#setCurrentPlayTime(long)} once b/68382377 is fixed. 53 */ 54 return new AnimatorPlaybackControllerVL(anim, duration, onCancelRunnable); 55 } 56 57 private final ValueAnimator mAnimationPlayer; 58 private final long mDuration; 59 60 protected final AnimatorSet mAnim; 61 62 protected float mCurrentFraction; 63 private Runnable mEndAction; 64 65 protected boolean mTargetCancelled = false; 66 protected Runnable mOnCancelRunnable; 67 68 protected AnimatorPlaybackController(AnimatorSet anim, long duration, 69 Runnable onCancelRunnable) { 70 mAnim = anim; 71 mDuration = duration; 72 mOnCancelRunnable = onCancelRunnable; 73 74 mAnimationPlayer = ValueAnimator.ofFloat(0, 1); 75 mAnimationPlayer.setInterpolator(Interpolators.LINEAR); 76 mAnimationPlayer.addListener(new OnAnimationEndDispatcher()); 77 mAnimationPlayer.addUpdateListener(this); 78 79 mAnim.addListener(new AnimatorListenerAdapter() { 80 @Override 81 public void onAnimationCancel(Animator animation) { 82 mTargetCancelled = true; 83 if (mOnCancelRunnable != null) { 84 mOnCancelRunnable.run(); 85 mOnCancelRunnable = null; 86 } 87 } 88 89 @Override 90 public void onAnimationEnd(Animator animation) { 91 mTargetCancelled = false; 92 mOnCancelRunnable = null; 93 } 94 95 @Override 96 public void onAnimationStart(Animator animation) { 97 mTargetCancelled = false; 98 } 99 }); 100 } 101 102 public AnimatorSet getTarget() { 103 return mAnim; 104 } 105 106 public long getDuration() { 107 return mDuration; 108 } 109 110 /** 111 * Starts playing the animation forward from current position. 112 */ 113 public void start() { 114 mAnimationPlayer.setFloatValues(mCurrentFraction, 1); 115 mAnimationPlayer.setDuration(clampDuration(1 - mCurrentFraction)); 116 mAnimationPlayer.start(); 117 } 118 119 /** 120 * Starts playing the animation backwards from current position 121 */ 122 public void reverse() { 123 mAnimationPlayer.setFloatValues(mCurrentFraction, 0); 124 mAnimationPlayer.setDuration(clampDuration(mCurrentFraction)); 125 mAnimationPlayer.start(); 126 } 127 128 /** 129 * Pauses the currently playing animation. 130 */ 131 public void pause() { 132 mAnimationPlayer.cancel(); 133 } 134 135 /** 136 * Returns the underlying animation used for controlling the set. 137 */ 138 public ValueAnimator getAnimationPlayer() { 139 return mAnimationPlayer; 140 } 141 142 /** 143 * Sets the current animation position and updates all the child animators accordingly. 144 */ 145 public abstract void setPlayFraction(float fraction); 146 147 public float getProgressFraction() { 148 return mCurrentFraction; 149 } 150 151 /** 152 * Sets the action to be called when the animation is completed. Also clears any 153 * previously set action. 154 */ 155 public void setEndAction(Runnable runnable) { 156 mEndAction = runnable; 157 } 158 159 @Override 160 public void onAnimationUpdate(ValueAnimator valueAnimator) { 161 setPlayFraction((float) valueAnimator.getAnimatedValue()); 162 } 163 164 protected long clampDuration(float fraction) { 165 float playPos = mDuration * fraction; 166 if (playPos <= 0) { 167 return 0; 168 } else { 169 return Math.min((long) playPos, mDuration); 170 } 171 } 172 173 public void dispatchOnStart() { 174 dispatchOnStartRecursively(mAnim); 175 } 176 177 private void dispatchOnStartRecursively(Animator animator) { 178 for (AnimatorListener l : nonNullList(animator.getListeners())) { 179 l.onAnimationStart(animator); 180 } 181 182 if (animator instanceof AnimatorSet) { 183 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 184 dispatchOnStartRecursively(anim); 185 } 186 } 187 } 188 189 public void dispatchOnCancel() { 190 dispatchOnCancelRecursively(mAnim); 191 } 192 193 private void dispatchOnCancelRecursively(Animator animator) { 194 for (AnimatorListener l : nonNullList(animator.getListeners())) { 195 l.onAnimationCancel(animator); 196 } 197 198 if (animator instanceof AnimatorSet) { 199 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 200 dispatchOnCancelRecursively(anim); 201 } 202 } 203 } 204 205 public void setOnCancelRunnable(Runnable runnable) { 206 mOnCancelRunnable = runnable; 207 } 208 209 public Runnable getOnCancelRunnable() { 210 return mOnCancelRunnable; 211 } 212 213 public static class AnimatorPlaybackControllerVL extends AnimatorPlaybackController { 214 215 private final ValueAnimator[] mChildAnimations; 216 217 private AnimatorPlaybackControllerVL(AnimatorSet anim, long duration, 218 Runnable onCancelRunnable) { 219 super(anim, duration, onCancelRunnable); 220 221 // Build animation list 222 ArrayList<ValueAnimator> childAnims = new ArrayList<>(); 223 getAnimationsRecur(mAnim, childAnims); 224 mChildAnimations = childAnims.toArray(new ValueAnimator[childAnims.size()]); 225 } 226 227 private void getAnimationsRecur(AnimatorSet anim, ArrayList<ValueAnimator> out) { 228 long forceDuration = anim.getDuration(); 229 TimeInterpolator forceInterpolator = anim.getInterpolator(); 230 for (Animator child : anim.getChildAnimations()) { 231 if (forceDuration > 0) { 232 child.setDuration(forceDuration); 233 } 234 if (forceInterpolator != null) { 235 child.setInterpolator(forceInterpolator); 236 } 237 if (child instanceof ValueAnimator) { 238 out.add((ValueAnimator) child); 239 } else if (child instanceof AnimatorSet) { 240 getAnimationsRecur((AnimatorSet) child, out); 241 } else { 242 throw new RuntimeException("Unknown animation type " + child); 243 } 244 } 245 } 246 247 @Override 248 public void setPlayFraction(float fraction) { 249 mCurrentFraction = fraction; 250 // Let the animator report the progress but don't apply the progress to child 251 // animations if it has been cancelled. 252 if (mTargetCancelled) { 253 return; 254 } 255 long playPos = clampDuration(fraction); 256 for (ValueAnimator anim : mChildAnimations) { 257 anim.setCurrentPlayTime(Math.min(playPos, anim.getDuration())); 258 } 259 } 260 } 261 262 private class OnAnimationEndDispatcher extends AnimationSuccessListener { 263 264 @Override 265 public void onAnimationStart(Animator animation) { 266 mCancelled = false; 267 } 268 269 @Override 270 public void onAnimationSuccess(Animator animator) { 271 dispatchOnEndRecursively(mAnim); 272 if (mEndAction != null) { 273 mEndAction.run(); 274 } 275 } 276 277 private void dispatchOnEndRecursively(Animator animator) { 278 for (AnimatorListener l : nonNullList(animator.getListeners())) { 279 l.onAnimationEnd(animator); 280 } 281 282 if (animator instanceof AnimatorSet) { 283 for (Animator anim : nonNullList(((AnimatorSet) animator).getChildAnimations())) { 284 dispatchOnEndRecursively(anim); 285 } 286 } 287 } 288 } 289 290 private static <T> List<T> nonNullList(ArrayList<T> list) { 291 return list == null ? Collections.emptyList() : list; 292 } 293 } 294