1 // Copyright 2017 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 package org.chromium.chrome.browser.layouts.animation; 6 7 import static org.chromium.base.ContextUtils.getApplicationContext; 8 9 import android.animation.Animator; 10 import android.animation.TimeInterpolator; 11 import android.animation.ValueAnimator; 12 import android.provider.Settings; 13 14 import androidx.annotation.IntDef; 15 import androidx.annotation.NonNull; 16 import androidx.annotation.Nullable; 17 import androidx.annotation.VisibleForTesting; 18 19 import org.chromium.base.Log; 20 import org.chromium.base.ObserverList; 21 import org.chromium.base.supplier.Supplier; 22 import org.chromium.components.browser_ui.widget.animation.Interpolators; 23 import org.chromium.ui.modelutil.PropertyModel; 24 import org.chromium.ui.modelutil.PropertyModel.WritableFloatPropertyKey; 25 26 import java.lang.annotation.Retention; 27 import java.lang.annotation.RetentionPolicy; 28 import java.lang.ref.WeakReference; 29 import java.util.ArrayList; 30 31 /** 32 * An animator that can be used for animations in the Browser Compositor. 33 */ 34 public class CompositorAnimator extends Animator { 35 private static final String TAG = "CompositorAnimator"; 36 37 /** The different states that this animator can be in. */ 38 @IntDef({AnimationState.STARTED, AnimationState.RUNNING, AnimationState.CANCELED, 39 AnimationState.ENDED}) 40 @Retention(RetentionPolicy.SOURCE) 41 private @interface AnimationState { 42 int STARTED = 0; 43 int RUNNING = 1; 44 int CANCELED = 2; 45 int ENDED = 3; 46 } 47 48 /** 49 * The scale honoring Settings.Global.ANIMATOR_DURATION_SCALE. Use static to reduce updating. 50 * See {@link ValueAnimator}. 51 **/ 52 @VisibleForTesting 53 public static float sDurationScale = 1; 54 55 /** The {@link CompositorAnimationHandler} running the animation. */ 56 private final WeakReference<CompositorAnimationHandler> mHandler; 57 58 /** The list of listeners for events through the life of an animation. */ 59 private final ObserverList<AnimatorListener> mListeners = new ObserverList<>(); 60 61 /** The list of frame update listeners for this animation. */ 62 private final ArrayList<AnimatorUpdateListener> mAnimatorUpdateListeners = new ArrayList<>(); 63 64 /** 65 * A cached copy of the list of {@link AnimatorUpdateListener}s to prevent allocating a new list 66 * every update. 67 */ 68 private final ArrayList<AnimatorUpdateListener> mCachedList = new ArrayList<>(); 69 70 /** The time interpolator for the animator. */ 71 private TimeInterpolator mTimeInterpolator; 72 73 /** 74 * The amount of time in ms that has passed since the animation has started. This includes any 75 * delay that was set. 76 */ 77 private long mTimeSinceStartMs; 78 79 /** 80 * The fraction that the animation is complete. This number is in the range [0, 1] and accounts 81 * for the set time interpolator. 82 */ 83 private float mAnimatedFraction; 84 85 /** The value that the animation should start with (ending at {@link #mEndValue}). */ 86 private Supplier<Float> mStartValue; 87 88 /** The value that the animation will transition to (starting at {@link #mStartValue}). */ 89 private Supplier<Float> mEndValue; 90 91 /** The duration of the animation in ms. */ 92 private long mDurationMs; 93 94 /** 95 * The animator's start delay in ms. Once {@link #start()} is called, updates are not sent until 96 * this time has passed. 97 */ 98 private long mStartDelayMs; 99 100 /** The current state of the animation. */ 101 @AnimationState 102 private int mAnimationState = AnimationState.ENDED; 103 104 /** 105 * Whether the animation ended because of frame updates. This is used to determine if any 106 * listeners need to be updated one more time. 107 */ 108 private boolean mDidUpdateToCompletion; 109 110 /** 111 * A utility for creating a basic animator. 112 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 113 * @param startValue The starting animation value. 114 * @param endValue The end animation value. 115 * @param durationMs The duration of the animation in ms. 116 * @param listener An update listener if specific actions need to be performed. 117 * @return A {@link CompositorAnimator} for the property. 118 */ ofFloat(CompositorAnimationHandler handler, float startValue, float endValue, long durationMs, @Nullable AnimatorUpdateListener listener)119 public static CompositorAnimator ofFloat(CompositorAnimationHandler handler, float startValue, 120 float endValue, long durationMs, @Nullable AnimatorUpdateListener listener) { 121 CompositorAnimator animator = new CompositorAnimator(handler); 122 animator.setValues(startValue, endValue); 123 if (listener != null) animator.addUpdateListener(listener); 124 animator.setDuration(durationMs); 125 return animator; 126 } 127 128 /** 129 * A utility for creating a basic animator. 130 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 131 * @param target The object to modify. 132 * @param property The property of the object to modify. 133 * @param startValue The starting animation value. 134 * @param endValue The end animation value. 135 * @param durationMs The duration of the animation in ms. 136 * @param interpolator The time interpolator for the animation. 137 * @return A {@link CompositorAnimator} for the property. 138 */ ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, float startValue, float endValue, long durationMs, TimeInterpolator interpolator)139 public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler, 140 final T target, final FloatProperty<T> property, float startValue, float endValue, 141 long durationMs, TimeInterpolator interpolator) { 142 CompositorAnimator animator = new CompositorAnimator(handler); 143 animator.setValues(startValue, endValue); 144 animator.setDuration(durationMs); 145 animator.addUpdateListener( 146 (CompositorAnimator a) -> property.setValue(target, a.getAnimatedValue())); 147 animator.setInterpolator(interpolator); 148 return animator; 149 } 150 151 /** 152 * A utility for creating a basic animator. 153 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 154 * @param target The object to modify. 155 * @param property The property of the object to modify. 156 * @param startValue The {@link Supplier} of the starting animation value. 157 * @param endValue The {@link Supplier} of the end animation value. 158 * @param durationMs The duration of the animation in ms. 159 * @param interpolator The time interpolator for the animation. 160 * @return A {@link CompositorAnimator} for the property. 161 */ ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, Supplier<Float> startValue, Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator)162 public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler, 163 final T target, final FloatProperty<T> property, Supplier<Float> startValue, 164 Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator) { 165 CompositorAnimator animator = new CompositorAnimator(handler); 166 animator.setValues(startValue, endValue); 167 animator.setDuration(durationMs); 168 animator.addUpdateListener( 169 (CompositorAnimator a) -> property.setValue(target, a.getAnimatedValue())); 170 animator.setInterpolator(interpolator); 171 return animator; 172 } 173 174 /** 175 * A utility for creating a basic animator. 176 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 177 * @param target The object to modify. 178 * @param property The property of the object to modify. 179 * @param startValue The starting animation value. 180 * @param endValue The end animation value. 181 * @param durationMs The duration of the animation in ms. 182 * @return A {@link CompositorAnimator} for the property. 183 */ ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, float startValue, float endValue, long durationMs)184 public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler, 185 final T target, final FloatProperty<T> property, float startValue, float endValue, 186 long durationMs) { 187 return ofFloatProperty(handler, target, property, startValue, endValue, durationMs, 188 Interpolators.DECELERATE_INTERPOLATOR); 189 } 190 191 /** 192 * A utility for creating a basic animator. 193 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 194 * @param target The object to modify. 195 * @param property The property of the object to modify. 196 * @param startValue The {@link Supplier} of the starting animation value. 197 * @param endValue The {@link Supplier} of the end animation value. 198 * @param durationMs The duration of the animation in ms. 199 * @return A {@link CompositorAnimator} for the property. 200 */ ofFloatProperty(CompositorAnimationHandler handler, final T target, final FloatProperty<T> property, Supplier<Float> startValue, Supplier<Float> endValue, long durationMs)201 public static <T> CompositorAnimator ofFloatProperty(CompositorAnimationHandler handler, 202 final T target, final FloatProperty<T> property, Supplier<Float> startValue, 203 Supplier<Float> endValue, long durationMs) { 204 return ofFloatProperty(handler, target, property, startValue, endValue, durationMs, 205 Interpolators.DECELERATE_INTERPOLATOR); 206 } 207 208 /** 209 * Create a {@link CompositorAnimator} to animate the {@link WritableFloatPropertyKey}. 210 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 211 * @param model The {@link PropertyModel} to modify. 212 * @param key The {@link WritableFloatPropertyKey} in the model to update. 213 * @param startValue The {@link Supplier} of the starting animation value. 214 * @param endValue The {@link Supplier} of the end animation value. 215 * @param durationMs The duration of the animation in ms. 216 * @param interpolator The time interpolator for the animation. 217 * @return {@link CompositorAnimator} for animating the {@link WritableFloatPropertyKey}. 218 */ ofWritableFloatPropertyKey(CompositorAnimationHandler handler, final PropertyModel model, WritableFloatPropertyKey key, Supplier<Float> startValue, Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator)219 public static CompositorAnimator ofWritableFloatPropertyKey(CompositorAnimationHandler handler, 220 final PropertyModel model, WritableFloatPropertyKey key, Supplier<Float> startValue, 221 Supplier<Float> endValue, long durationMs, TimeInterpolator interpolator) { 222 CompositorAnimator animator = new CompositorAnimator(handler); 223 animator.setValues(startValue, endValue); 224 animator.setDuration(durationMs); 225 animator.addUpdateListener((CompositorAnimator a) -> model.set(key, a.getAnimatedValue())); 226 animator.setInterpolator(interpolator); 227 return animator; 228 } 229 230 /** 231 * Create a {@link CompositorAnimator} to animate the {@link WritableFloatPropertyKey}. 232 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 233 * @param model The {@link PropertyModel} to modify. 234 * @param key The {@link WritableFloatPropertyKey} in the model to update. 235 * @param startValue The starting animation value. 236 * @param endValue The end animation value. 237 * @param durationMs The duration of the animation in ms. 238 * @param interpolator The time interpolator for the animation. 239 * @return {@link CompositorAnimator} for animating the {@link WritableFloatPropertyKey}. 240 */ ofWritableFloatPropertyKey(CompositorAnimationHandler handler, final PropertyModel model, WritableFloatPropertyKey key, float startValue, float endValue, long durationMs, TimeInterpolator interpolator)241 public static CompositorAnimator ofWritableFloatPropertyKey(CompositorAnimationHandler handler, 242 final PropertyModel model, WritableFloatPropertyKey key, float startValue, 243 float endValue, long durationMs, TimeInterpolator interpolator) { 244 return ofWritableFloatPropertyKey( 245 handler, model, key, () -> startValue, () -> endValue, durationMs, interpolator); 246 } 247 248 /** 249 * Create a {@link CompositorAnimator} to animate the {@link WritableFloatPropertyKey}. 250 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 251 * @param model The {@link PropertyModel} to modify. 252 * @param key The {@link WritableFloatPropertyKey} in the model to update. 253 * @param startValue The starting animation value. 254 * @param endValue The end animation value. 255 * @param durationMs The duration of the animation in ms. 256 * @return {@link CompositorAnimator} for animating the {@link WritableFloatPropertyKey}. 257 */ ofWritableFloatPropertyKey(CompositorAnimationHandler handler, final PropertyModel model, WritableFloatPropertyKey key, float startValue, float endValue, long durationMs)258 public static CompositorAnimator ofWritableFloatPropertyKey(CompositorAnimationHandler handler, 259 final PropertyModel model, WritableFloatPropertyKey key, float startValue, 260 float endValue, long durationMs) { 261 return ofWritableFloatPropertyKey(handler, model, key, startValue, endValue, durationMs, 262 Interpolators.DECELERATE_INTERPOLATOR); 263 } 264 265 /** An interface for listening for frames of an animation. */ 266 public interface AnimatorUpdateListener { 267 /** 268 * A notification of the occurrence of another frame of the animation. 269 * @param animator The animator that was updated. 270 */ onAnimationUpdate(CompositorAnimator animator)271 void onAnimationUpdate(CompositorAnimator animator); 272 } 273 274 /** 275 * Create a new animator for the current context. 276 * @param handler The {@link CompositorAnimationHandler} responsible for running the animation. 277 */ CompositorAnimator(@onNull CompositorAnimationHandler handler)278 public CompositorAnimator(@NonNull CompositorAnimationHandler handler) { 279 mHandler = new WeakReference<>(handler); 280 281 // The default interpolator is decelerate; this mimics the existing ChromeAnimation 282 // behavior. 283 mTimeInterpolator = Interpolators.DECELERATE_INTERPOLATOR; 284 285 // By default, animate for 0 to 1. 286 setValues(0, 1); 287 288 // Try to update from the system setting, but not too frequently. 289 sDurationScale = Settings.Global.getFloat(getApplicationContext().getContentResolver(), 290 Settings.Global.ANIMATOR_DURATION_SCALE, sDurationScale); 291 if (sDurationScale != 1) { 292 Log.i(TAG, "Settings.Global.ANIMATOR_DURATION_SCALE = %f", sDurationScale); 293 } 294 } 295 getScaledDuration()296 private long getScaledDuration() { 297 return (long) (mDurationMs * sDurationScale); 298 } 299 300 /** 301 * Push an update to the animation. This should be called while the start delay is active and 302 * assumes that the animated object is at the starting position when {@link #start} is called. 303 * @param deltaTimeMs The time since the previous frame. 304 */ doAnimationFrame(long deltaTimeMs)305 public void doAnimationFrame(long deltaTimeMs) { 306 mTimeSinceStartMs += deltaTimeMs; 307 308 // Clamp to the animator's duration, taking into account the start delay. 309 long finalTimeMs = Math.min( 310 (long) (mTimeSinceStartMs - mStartDelayMs * sDurationScale), getScaledDuration()); 311 312 // Wait until the start delay has passed. 313 if (finalTimeMs < 0) return; 314 315 // In the case where duration is 0, the animation is complete. 316 mAnimatedFraction = 1; 317 if (getScaledDuration() > 0) { 318 mAnimatedFraction = 319 mTimeInterpolator.getInterpolation(finalTimeMs / (float) getScaledDuration()); 320 } 321 322 // Push update to listeners. 323 mCachedList.addAll(mAnimatorUpdateListeners); 324 for (int i = 0; i < mCachedList.size(); i++) mCachedList.get(i).onAnimationUpdate(this); 325 mCachedList.clear(); 326 327 if (finalTimeMs == getScaledDuration()) { 328 mDidUpdateToCompletion = true; 329 end(); 330 } 331 } 332 333 /** 334 * @return The animated fraction after being passed through the time interpolator, if set. 335 */ 336 @VisibleForTesting getAnimatedFraction()337 float getAnimatedFraction() { 338 return mAnimatedFraction; 339 } 340 341 /** 342 * Add a listener for frame occurrences. 343 * @param listener The listener to add. 344 */ addUpdateListener(AnimatorUpdateListener listener)345 public void addUpdateListener(AnimatorUpdateListener listener) { 346 mAnimatorUpdateListeners.add(listener); 347 } 348 349 /** 350 * @return Whether or not the animation has ended after being started. If the animation is 351 * started after ending, this value will be reset to true. 352 */ hasEnded()353 public boolean hasEnded() { 354 return mAnimationState == AnimationState.ENDED; 355 } 356 357 /** 358 * Set the values to animate between. 359 * @param start The value to begin the animation with. 360 * @param end The value to end the animation at. 361 */ setValues(float start, float end)362 void setValues(float start, float end) { 363 setValues(() -> start, () -> end); 364 } 365 366 /** 367 * Set the values to animate between. 368 * @param start The value to begin the animation with. 369 * @param end The value to end the animation at. 370 */ 371 @VisibleForTesting setValues(Supplier<Float> start, Supplier<Float> end)372 void setValues(Supplier<Float> start, Supplier<Float> end) { 373 mStartValue = start; 374 mEndValue = end; 375 } 376 377 /** 378 * @return The current value between the floats set by {@link #setValues(float, float)}. 379 */ getAnimatedValue()380 public float getAnimatedValue() { 381 return mStartValue.get() + (getAnimatedFraction() * (mEndValue.get() - mStartValue.get())); 382 } 383 384 @Override addListener(AnimatorListener listener)385 public void addListener(AnimatorListener listener) { 386 mListeners.addObserver(listener); 387 } 388 389 @Override removeListener(AnimatorListener listener)390 public void removeListener(AnimatorListener listener) { 391 mListeners.removeObserver(listener); 392 } 393 394 @Override removeAllListeners()395 public void removeAllListeners() { 396 mListeners.clear(); 397 mAnimatorUpdateListeners.clear(); 398 } 399 400 @Override 401 @SuppressWarnings("unchecked") start()402 public void start() { 403 if (mAnimationState != AnimationState.ENDED) return; 404 405 super.start(); 406 mAnimationState = AnimationState.RUNNING; 407 mDidUpdateToCompletion = false; 408 CompositorAnimationHandler handler = mHandler.get(); 409 if (handler != null) handler.registerAndStartAnimator(this); 410 mTimeSinceStartMs = 0; 411 412 for (AnimatorListener listener : mListeners) listener.onAnimationStart(this); 413 } 414 415 @Override 416 @SuppressWarnings("unchecked") cancel()417 public void cancel() { 418 if (mAnimationState == AnimationState.ENDED) return; 419 420 mAnimationState = AnimationState.CANCELED; 421 422 super.cancel(); 423 424 for (AnimatorListener listener : mListeners) listener.onAnimationCancel(this); 425 426 end(); 427 } 428 429 @Override 430 @SuppressWarnings("unchecked") end()431 public void end() { 432 if (mAnimationState == AnimationState.ENDED) return; 433 434 super.end(); 435 boolean wasCanceled = mAnimationState == AnimationState.CANCELED; 436 mAnimationState = AnimationState.ENDED; 437 438 // If the animation was ended early but not canceled, push one last update to the listeners. 439 if (!mDidUpdateToCompletion && !wasCanceled) { 440 mAnimatedFraction = 1f; 441 for (AnimatorUpdateListener listener : mAnimatorUpdateListeners) { 442 listener.onAnimationUpdate(this); 443 } 444 } 445 446 for (AnimatorListener listener : mListeners) listener.onAnimationEnd(this); 447 } 448 449 @Override getStartDelay()450 public long getStartDelay() { 451 return mStartDelayMs; 452 } 453 454 @Override setStartDelay(long startDelayMs)455 public void setStartDelay(long startDelayMs) { 456 if (startDelayMs < 0) startDelayMs = 0; 457 mStartDelayMs = startDelayMs; 458 } 459 460 @Override setDuration(long durationMs)461 public CompositorAnimator setDuration(long durationMs) { 462 if (durationMs < 0) durationMs = 0; 463 mDurationMs = durationMs; 464 return this; 465 } 466 467 @Override getDuration()468 public long getDuration() { 469 return mDurationMs; 470 } 471 472 @Override setInterpolator(TimeInterpolator timeInterpolator)473 public void setInterpolator(TimeInterpolator timeInterpolator) { 474 assert timeInterpolator != null; 475 mTimeInterpolator = timeInterpolator; 476 } 477 478 @Override isRunning()479 public boolean isRunning() { 480 return mAnimationState == AnimationState.RUNNING; 481 } 482 } 483