/core/java/android/animation/AnimationHandler.java
Java | 316 lines | 187 code | 32 blank | 97 comment | 36 complexity | 35a52c3bd5872669f9a81d2381fc0b63 MD5 | raw file
Possible License(s): LGPL-2.1, MPL-2.0-no-copyleft-exception, CC0-1.0, BitTorrent-1.0, BSD-3-Clause
- /*
- * Copyright (C) 2015 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
- package android.animation;
- import android.os.SystemClock;
- import android.util.ArrayMap;
- import android.view.Choreographer;
- import java.util.ArrayList;
- /**
- * This custom, static handler handles the timing pulse that is shared by all active
- * ValueAnimators. This approach ensures that the setting of animation values will happen on the
- * same thread that animations start on, and that all animations will share the same times for
- * calculating their values, which makes synchronizing animations possible.
- *
- * The handler uses the Choreographer by default for doing periodic callbacks. A custom
- * AnimationFrameCallbackProvider can be set on the handler to provide timing pulse that
- * may be independent of UI frame update. This could be useful in testing.
- *
- * @hide
- */
- public class AnimationHandler {
- /**
- * Internal per-thread collections used to avoid set collisions as animations start and end
- * while being processed.
- * @hide
- */
- private final ArrayMap<AnimationFrameCallback, Long> mDelayedCallbackStartTime =
- new ArrayMap<>();
- private final ArrayList<AnimationFrameCallback> mAnimationCallbacks =
- new ArrayList<>();
- private final ArrayList<AnimationFrameCallback> mCommitCallbacks =
- new ArrayList<>();
- private AnimationFrameCallbackProvider mProvider;
- private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
- @Override
- public void doFrame(long frameTimeNanos) {
- doAnimationFrame(getProvider().getFrameTime());
- if (mAnimationCallbacks.size() > 0) {
- getProvider().postFrameCallback(this);
- }
- }
- };
- public final static ThreadLocal<AnimationHandler> sAnimatorHandler = new ThreadLocal<>();
- private boolean mListDirty = false;
- public static AnimationHandler getInstance() {
- if (sAnimatorHandler.get() == null) {
- sAnimatorHandler.set(new AnimationHandler());
- }
- return sAnimatorHandler.get();
- }
- /**
- * By default, the Choreographer is used to provide timing for frame callbacks. A custom
- * provider can be used here to provide different timing pulse.
- */
- public void setProvider(AnimationFrameCallbackProvider provider) {
- if (provider == null) {
- mProvider = new MyFrameCallbackProvider();
- } else {
- mProvider = provider;
- }
- }
- private AnimationFrameCallbackProvider getProvider() {
- if (mProvider == null) {
- mProvider = new MyFrameCallbackProvider();
- }
- return mProvider;
- }
- /**
- * Register to get a callback on the next frame after the delay.
- */
- public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
- if (mAnimationCallbacks.size() == 0) {
- getProvider().postFrameCallback(mFrameCallback);
- }
- if (!mAnimationCallbacks.contains(callback)) {
- mAnimationCallbacks.add(callback);
- }
- if (delay > 0) {
- mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
- }
- }
- /**
- * Register to get a one shot callback for frame commit timing. Frame commit timing is the
- * time *after* traversals are done, as opposed to the animation frame timing, which is
- * before any traversals. This timing can be used to adjust the start time of an animation
- * when expensive traversals create big delta between the animation frame timing and the time
- * that animation is first shown on screen.
- *
- * Note this should only be called when the animation has already registered to receive
- * animation frame callbacks. This callback will be guaranteed to happen *after* the next
- * animation frame callback.
- */
- public void addOneShotCommitCallback(final AnimationFrameCallback callback) {
- if (!mCommitCallbacks.contains(callback)) {
- mCommitCallbacks.add(callback);
- }
- }
- /**
- * Removes the given callback from the list, so it will no longer be called for frame related
- * timing.
- */
- public void removeCallback(AnimationFrameCallback callback) {
- mCommitCallbacks.remove(callback);
- mDelayedCallbackStartTime.remove(callback);
- int id = mAnimationCallbacks.indexOf(callback);
- if (id >= 0) {
- mAnimationCallbacks.set(id, null);
- mListDirty = true;
- }
- }
- private void doAnimationFrame(long frameTime) {
- int size = mAnimationCallbacks.size();
- long currentTime = SystemClock.uptimeMillis();
- for (int i = 0; i < size; i++) {
- final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
- if (callback == null) {
- continue;
- }
- if (isCallbackDue(callback, currentTime)) {
- callback.doAnimationFrame(frameTime);
- if (mCommitCallbacks.contains(callback)) {
- getProvider().postCommitCallback(new Runnable() {
- @Override
- public void run() {
- commitAnimationFrame(callback, getProvider().getFrameTime());
- }
- });
- }
- }
- }
- cleanUpList();
- }
- private void commitAnimationFrame(AnimationFrameCallback callback, long frameTime) {
- if (!mDelayedCallbackStartTime.containsKey(callback) &&
- mCommitCallbacks.contains(callback)) {
- callback.commitAnimationFrame(frameTime);
- mCommitCallbacks.remove(callback);
- }
- }
- /**
- * Remove the callbacks from mDelayedCallbackStartTime once they have passed the initial delay
- * so that they can start getting frame callbacks.
- *
- * @return true if they have passed the initial delay or have no delay, false otherwise.
- */
- private boolean isCallbackDue(AnimationFrameCallback callback, long currentTime) {
- Long startTime = mDelayedCallbackStartTime.get(callback);
- if (startTime == null) {
- return true;
- }
- if (startTime < currentTime) {
- mDelayedCallbackStartTime.remove(callback);
- return true;
- }
- return false;
- }
- /**
- * Return the number of callbacks that have registered for frame callbacks.
- */
- public static int getAnimationCount() {
- AnimationHandler handler = sAnimatorHandler.get();
- if (handler == null) {
- return 0;
- }
- return handler.getCallbackSize();
- }
- public static void setFrameDelay(long delay) {
- getInstance().getProvider().setFrameDelay(delay);
- }
- public static long getFrameDelay() {
- return getInstance().getProvider().getFrameDelay();
- }
- void autoCancelBasedOn(ObjectAnimator objectAnimator) {
- for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
- AnimationFrameCallback cb = mAnimationCallbacks.get(i);
- if (cb == null) {
- continue;
- }
- if (objectAnimator.shouldAutoCancel(cb)) {
- ((Animator) mAnimationCallbacks.get(i)).cancel();
- }
- }
- }
- private void cleanUpList() {
- if (mListDirty) {
- for (int i = mAnimationCallbacks.size() - 1; i >= 0; i--) {
- if (mAnimationCallbacks.get(i) == null) {
- mAnimationCallbacks.remove(i);
- }
- }
- mListDirty = false;
- }
- }
- private int getCallbackSize() {
- int count = 0;
- int size = mAnimationCallbacks.size();
- for (int i = size - 1; i >= 0; i--) {
- if (mAnimationCallbacks.get(i) != null) {
- count++;
- }
- }
- return count;
- }
- /**
- * Default provider of timing pulse that uses Choreographer for frame callbacks.
- */
- private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider {
- final Choreographer mChoreographer = Choreographer.getInstance();
- @Override
- public void postFrameCallback(Choreographer.FrameCallback callback) {
- mChoreographer.postFrameCallback(callback);
- }
- @Override
- public void postCommitCallback(Runnable runnable) {
- mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, runnable, null);
- }
- @Override
- public long getFrameTime() {
- return mChoreographer.getFrameTime();
- }
- @Override
- public long getFrameDelay() {
- return Choreographer.getFrameDelay();
- }
- @Override
- public void setFrameDelay(long delay) {
- Choreographer.setFrameDelay(delay);
- }
- }
- /**
- * Callbacks that receives notifications for animation timing and frame commit timing.
- */
- interface AnimationFrameCallback {
- /**
- * Run animation based on the frame time.
- * @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
- * base.
- */
- void doAnimationFrame(long frameTime);
- /**
- * This notifies the callback of frame commit time. Frame commit time is the time after
- * traversals happen, as opposed to the normal animation frame time that is before
- * traversals. This is used to compensate expensive traversals that happen as the
- * animation starts. When traversals take a long time to complete, the rendering of the
- * initial frame will be delayed (by a long time). But since the startTime of the
- * animation is set before the traversal, by the time of next frame, a lot of time would
- * have passed since startTime was set, the animation will consequently skip a few frames
- * to respect the new frameTime. By having the commit time, we can adjust the start time to
- * when the first frame was drawn (after any expensive traversals) so that no frames
- * will be skipped.
- *
- * @param frameTime The frame time after traversals happen, if any, in the
- * {@link SystemClock#uptimeMillis()} time base.
- */
- void commitAnimationFrame(long frameTime);
- }
- /**
- * The intention for having this interface is to increase the testability of ValueAnimator.
- * Specifically, we can have a custom implementation of the interface below and provide
- * timing pulse without using Choreographer. That way we could use any arbitrary interval for
- * our timing pulse in the tests.
- *
- * @hide
- */
- public interface AnimationFrameCallbackProvider {
- void postFrameCallback(Choreographer.FrameCallback callback);
- void postCommitCallback(Runnable runnable);
- long getFrameTime();
- long getFrameDelay();
- void setFrameDelay(long delay);
- }
- }