Loading core/java/android/animation/ValueAnimator.java +120 −110 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.AndroidRuntimeException; import android.view.Choreographer; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; Loading @@ -45,17 +46,10 @@ public class ValueAnimator extends Animator { * Internal constants */ /* * The default amount of time in ms between animation frames */ private static final long DEFAULT_FRAME_DELAY = 10; /** * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent * by the handler to itself to process the next animation frame * Messages sent to timing handler: START is sent when an animation first begins. */ static final int ANIMATION_START = 0; static final int ANIMATION_FRAME = 1; /** * Values used with internal variable mPlayingState to indicate the current state of an Loading Loading @@ -162,9 +156,6 @@ public class ValueAnimator extends Animator { // The amount of time in ms to delay starting the animation after start() is called private long mStartDelay = 0; // The number of milliseconds between animation frames private static long sFrameDelay = DEFAULT_FRAME_DELAY; // The number of times the animation will repeat. The default is 0, which means the animation // will play only once private int mRepeatCount = 0; Loading Loading @@ -511,7 +502,8 @@ public class ValueAnimator extends Animator { * animations possible. * */ private static class AnimationHandler extends Handler { private static class AnimationHandler extends Handler implements Choreographer.OnAnimateListener { // The per-thread list of all active animations private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); Loading @@ -526,40 +518,41 @@ public class ValueAnimator extends Animator { private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>(); private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>(); private final Choreographer mChoreographer; private boolean mIsChoreographed; private AnimationHandler() { mChoreographer = Choreographer.getInstance(); } /** * There are only two messages that we care about: ANIMATION_START and * ANIMATION_FRAME. The START message is sent when an animation's start() * method is called. It cannot start synchronously when start() is called * The START message is sent when an animation's start() method is called. * It cannot start synchronously when start() is called * because the call may be on the wrong thread, and it would also not be * synchronized with other animations because it would not start on a common * timing pulse. So each animation sends a START message to the handler, which * causes the handler to place the animation on the active animations queue and * start processing frames for that animation. * The FRAME message is the one that is sent over and over while there are any * active animations to process. */ @Override public void handleMessage(Message msg) { boolean callAgain = true; ArrayList<ValueAnimator> animations = mAnimations; ArrayList<ValueAnimator> delayedAnims = mDelayedAnims; switch (msg.what) { // TODO: should we avoid sending frame message when starting if we // were already running? case ANIMATION_START: ArrayList<ValueAnimator> pendingAnimations = mPendingAnimations; if (animations.size() > 0 || delayedAnims.size() > 0) { callAgain = false; doAnimationStart(); break; } // pendingAnims holds any animations that have requested to be started // We're going to clear sPendingAnimations, but starting animation may } private void doAnimationStart() { // mPendingAnimations holds any animations that have requested to be started // We're going to clear mPendingAnimations, but starting animation may // cause more to be added to the pending list (for example, if one animation // starting triggers another starting). So we loop until sPendingAnimations // starting triggers another starting). So we loop until mPendingAnimations // is empty. while (pendingAnimations.size() > 0) { while (mPendingAnimations.size() > 0) { ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) pendingAnimations.clone(); pendingAnimations.clear(); (ArrayList<ValueAnimator>) mPendingAnimations.clone(); mPendingAnimations.clear(); int count = pendingCopy.size(); for (int i = 0; i < count; ++i) { ValueAnimator anim = pendingCopy.get(i); Loading @@ -567,48 +560,48 @@ public class ValueAnimator extends Animator { if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { delayedAnims.add(anim); mDelayedAnims.add(anim); } } } // fall through to process first frame of new animations case ANIMATION_FRAME: doAnimationFrame(); } private void doAnimationFrame() { // currentTime holds the common time for all animations processed // during this frame long currentTime = AnimationUtils.currentAnimationTimeMillis(); ArrayList<ValueAnimator> readyAnims = mReadyAnims; ArrayList<ValueAnimator> endingAnims = mEndingAnims; // First, process animations currently sitting on the delayed queue, adding // them to the active animations if they are ready int numDelayedAnims = delayedAnims.size(); int numDelayedAnims = mDelayedAnims.size(); for (int i = 0; i < numDelayedAnims; ++i) { ValueAnimator anim = delayedAnims.get(i); ValueAnimator anim = mDelayedAnims.get(i); if (anim.delayedAnimationFrame(currentTime)) { readyAnims.add(anim); mReadyAnims.add(anim); } } int numReadyAnims = readyAnims.size(); int numReadyAnims = mReadyAnims.size(); if (numReadyAnims > 0) { for (int i = 0; i < numReadyAnims; ++i) { ValueAnimator anim = readyAnims.get(i); ValueAnimator anim = mReadyAnims.get(i); anim.startAnimation(this); anim.mRunning = true; delayedAnims.remove(anim); mDelayedAnims.remove(anim); } readyAnims.clear(); mReadyAnims.clear(); } // Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended int numAnims = animations.size(); int numAnims = mAnimations.size(); int i = 0; while (i < numAnims) { ValueAnimator anim = animations.get(i); ValueAnimator anim = mAnimations.get(i); if (anim.animationFrame(currentTime)) { endingAnims.add(anim); mEndingAnims.add(anim); } if (animations.size() == numAnims) { if (mAnimations.size() == numAnims) { ++i; } else { // An animation might be canceled or ended by client code Loading @@ -619,25 +612,36 @@ public class ValueAnimator extends Animator { // but that entails garbage and processing overhead that would // be nice to avoid. --numAnims; endingAnims.remove(anim); mEndingAnims.remove(anim); } } if (endingAnims.size() > 0) { for (i = 0; i < endingAnims.size(); ++i) { endingAnims.get(i).endAnimation(this); if (mEndingAnims.size() > 0) { for (i = 0; i < mEndingAnims.size(); ++i) { mEndingAnims.get(i).endAnimation(this); } endingAnims.clear(); mEndingAnims.clear(); } // If there are still active or delayed animations, call the handler again // after the frameDelay if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) { sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - (AnimationUtils.currentAnimationTimeMillis() - currentTime))); // If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { if (!mIsChoreographed) { mIsChoreographed = true; mChoreographer.addOnAnimateListener(this); } mChoreographer.scheduleAnimation(); } else { if (mIsChoreographed) { mIsChoreographed = false; mChoreographer.removeOnAnimateListener(this); } break; } } @Override public void onAnimate() { doAnimationFrame(); } } /** Loading Loading @@ -667,10 +671,13 @@ public class ValueAnimator extends Animator { * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @return the requested time between frames, in milliseconds */ public static long getFrameDelay() { return sFrameDelay; return Choreographer.getFrameDelay(); } /** Loading @@ -680,10 +687,13 @@ public class ValueAnimator extends Animator { * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @param frameDelay the requested time between frames, in milliseconds */ public static void setFrameDelay(long frameDelay) { sFrameDelay = frameDelay; Choreographer.setFrameDelay(frameDelay); } /** Loading core/java/android/view/Choreographer.java 0 → 100644 +380 −0 Original line number Diff line number Diff line /* * Copyright (C) 2011 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.view; import com.android.internal.util.ArrayUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; /** * Coodinates animations and drawing for UI on a particular thread. * @hide */ public final class Choreographer extends Handler { private static final String TAG = "Choreographer"; private static final boolean DEBUG = false; // The default amount of time in ms between animation frames. // When vsync is not enabled, we want to have some idea of how long we should // wait before posting the next animation message. It is important that the // default value be less than the true inter-frame delay on all devices to avoid // situations where we might skip frames by waiting too long (we must compensate // for jitter and hardware variations). Regardless of this value, the animation // and display loop is ultimately rate-limited by how fast new graphics buffers can // be dequeued. private static final long DEFAULT_FRAME_DELAY = 10; // The number of milliseconds between animation frames. private static long sFrameDelay = DEFAULT_FRAME_DELAY; // Thread local storage for the choreographer. private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } return new Choreographer(looper); } }; // System property to enable/disable vsync for animations and drawing. // Enabled by default. private static final boolean USE_VSYNC = SystemProperties.getBoolean( "debug.choreographer.vsync", true); // System property to enable/disable the use of the vsync / animation timer // for drawing rather than drawing immediately. // Enabled by default. private static final boolean USE_ANIMATION_TIMER_FOR_DRAW = SystemProperties.getBoolean( "debug.choreographer.animdraw", true); private static final int MSG_DO_ANIMATION = 0; private static final int MSG_DO_DRAW = 1; private final Looper mLooper; private OnAnimateListener[] mOnAnimateListeners; private OnDrawListener[] mOnDrawListeners; private boolean mAnimationScheduled; private boolean mDrawScheduled; private FrameDisplayEventReceiver mFrameDisplayEventReceiver; private long mLastAnimationTime; private long mLastDrawTime; private Choreographer(Looper looper) { super(looper); mLooper = looper; mLastAnimationTime = Long.MIN_VALUE; mLastDrawTime = Long.MIN_VALUE; } /** * Gets the choreographer for this thread. * Must be called on the UI thread. * * @return The choreographer for this thread. * @throws IllegalStateException if the thread does not have a looper. */ public static Choreographer getInstance() { return sThreadInstance.get(); } /** * The amount of time, in milliseconds, between each frame of the animation. This is a * requested time that the animation will attempt to honor, but the actual delay between * frames may be different, depending on system load and capabilities. This is a static * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @return the requested time between frames, in milliseconds */ public static long getFrameDelay() { return sFrameDelay; } /** * The amount of time, in milliseconds, between each frame of the animation. This is a * requested time that the animation will attempt to honor, but the actual delay between * frames may be different, depending on system load and capabilities. This is a static * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @param frameDelay the requested time between frames, in milliseconds */ public static void setFrameDelay(long frameDelay) { sFrameDelay = frameDelay; } /** * Schedules animation (and drawing) to occur on the next frame synchronization boundary. * Must be called on the UI thread. */ public void scheduleAnimation() { if (!mAnimationScheduled) { mAnimationScheduled = true; if (USE_VSYNC) { if (DEBUG) { Log.d(TAG, "Scheduling vsync for animation."); } if (mFrameDisplayEventReceiver == null) { mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper); } mFrameDisplayEventReceiver.scheduleVsync(); } else { final long now = SystemClock.uptimeMillis(); final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now); if (DEBUG) { Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms."); } sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime); } } } /** * Schedules drawing to occur on the next frame synchronization boundary. * Must be called on the UI thread. */ public void scheduleDraw() { if (!mDrawScheduled) { mDrawScheduled = true; if (USE_ANIMATION_TIMER_FOR_DRAW) { scheduleAnimation(); } else { if (DEBUG) { Log.d(TAG, "Scheduling draw immediately."); } sendEmptyMessage(MSG_DO_DRAW); } } } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_ANIMATION: doAnimation(); break; case MSG_DO_DRAW: doDraw(); break; } } private void doAnimation() { if (mAnimationScheduled) { mAnimationScheduled = false; final long start = SystemClock.uptimeMillis(); if (DEBUG) { Log.d(TAG, "Performing animation: " + Math.max(0, start - mLastAnimationTime) + " ms have elapsed since previous animation."); } mLastAnimationTime = start; final OnAnimateListener[] listeners = mOnAnimateListeners; if (listeners != null) { for (int i = 0; i < listeners.length; i++) { listeners[i].onAnimate(); } } if (DEBUG) { Log.d(TAG, "Animation took " + (SystemClock.uptimeMillis() - start) + " ms."); } } if (USE_ANIMATION_TIMER_FOR_DRAW) { doDraw(); } } private void doDraw() { if (mDrawScheduled) { mDrawScheduled = false; final long start = SystemClock.uptimeMillis(); if (DEBUG) { Log.d(TAG, "Performing draw: " + Math.max(0, start - mLastDrawTime) + " ms have elapsed since previous draw."); } mLastDrawTime = start; final OnDrawListener[] listeners = mOnDrawListeners; if (listeners != null) { for (int i = 0; i < listeners.length; i++) { listeners[i].onDraw(); } } if (DEBUG) { Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms."); } } } /** * Adds an animation listener. * Must be called on the UI thread. * * @param listener The listener to add. */ public void addOnAnimateListener(OnAnimateListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Adding onAnimate listener: " + listener); } mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class, mOnAnimateListeners, listener); } /** * Removes an animation listener. * Must be called on the UI thread. * * @param listener The listener to remove. */ public void removeOnAnimateListener(OnAnimateListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Removing onAnimate listener: " + listener); } mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class, mOnAnimateListeners, listener); stopTimingLoopIfNoListeners(); } /** * Adds a draw listener. * Must be called on the UI thread. * * @param listener The listener to add. */ public void addOnDrawListener(OnDrawListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Adding onDraw listener: " + listener); } mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class, mOnDrawListeners, listener); } /** * Removes a draw listener. * Must be called on the UI thread. * * @param listener The listener to remove. */ public void removeOnDrawListener(OnDrawListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Removing onDraw listener: " + listener); } mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class, mOnDrawListeners, listener); stopTimingLoopIfNoListeners(); } private void stopTimingLoopIfNoListeners() { if (mOnDrawListeners == null && mOnAnimateListeners == null) { if (DEBUG) { Log.d(TAG, "Stopping timing loop."); } if (mAnimationScheduled) { mAnimationScheduled = false; if (!USE_VSYNC) { removeMessages(MSG_DO_ANIMATION); } } if (mDrawScheduled) { mDrawScheduled = false; if (!USE_ANIMATION_TIMER_FOR_DRAW) { removeMessages(MSG_DO_DRAW); } } if (mFrameDisplayEventReceiver != null) { mFrameDisplayEventReceiver.dispose(); mFrameDisplayEventReceiver = null; } } } /** * Listens for animation frame timing events. */ public static interface OnAnimateListener { /** * Called to animate properties before drawing the frame. */ public void onAnimate(); } /** * Listens for draw frame timing events. */ public static interface OnDrawListener { /** * Called to draw the frame. */ public void onDraw(); } private final class FrameDisplayEventReceiver extends DisplayEventReceiver { public FrameDisplayEventReceiver(Looper looper) { super(looper); } @Override public void onVsync(long timestampNanos, int frame) { doAnimation(); } } } core/java/android/view/ViewRootImpl.java +197 −200 File changed.Preview size limit exceeded, changes collapsed. Show changes core/java/com/android/internal/util/ArrayUtils.java +52 −1 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ package com.android.internal.util; import java.lang.reflect.Array; import java.util.Collection; // XXX these should be changed to reflect the actual memory allocator we use. // it looks like right now objects want to be powers of 2 minus 8 Loading Loading @@ -142,4 +141,56 @@ public class ArrayUtils } return false; } /** * Appends an element to a copy of the array and returns the copy. * @param array The original array, or null to represent an empty array. * @param element The element to add. * @return A new array that contains all of the elements of the original array * with the specified element added at the end. */ @SuppressWarnings("unchecked") public static <T> T[] appendElement(Class<T> kind, T[] array, T element) { final T[] result; final int end; if (array != null) { end = array.length; result = (T[])Array.newInstance(kind, end + 1); System.arraycopy(array, 0, result, 0, end); } else { end = 0; result = (T[])Array.newInstance(kind, 1); } result[end] = element; return result; } /** * Removes an element from a copy of the array and returns the copy. * If the element is not present, then the original array is returned unmodified. * @param array The original array, or null to represent an empty array. * @param element The element to remove. * @return A new array that contains all of the elements of the original array * except the first copy of the specified element removed. If the specified element * was not present, then returns the original array. Returns null if the result * would be an empty array. */ @SuppressWarnings("unchecked") public static <T> T[] removeElement(Class<T> kind, T[] array, T element) { if (array != null) { final int length = array.length; for (int i = 0; i < length; i++) { if (array[i] == element) { if (length == 1) { return null; } T[] result = (T[])Array.newInstance(kind, length - 1); System.arraycopy(array, 0, result, 0, i); System.arraycopy(array, i + 1, result, i, length - i - 1); return result; } } } return array; } } tools/layoutlib/bridge/src/android/animation/AnimationThread.java +5 −2 Original line number Diff line number Diff line Loading @@ -86,8 +86,11 @@ public abstract class AnimationThread extends Thread { try { Handler_Delegate.setCallback(new IHandlerCallback() { public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { if (msg.what == ValueAnimator.ANIMATION_START || msg.what == ValueAnimator.ANIMATION_FRAME) { if (msg.what == ValueAnimator.ANIMATION_START /*|| FIXME: The ANIMATION_FRAME message no longer exists. Instead, the animation timing loop is based on a Choreographer object that schedules animation and drawing frames. msg.what == ValueAnimator.ANIMATION_FRAME*/) { mQueue.add(new MessageBundle(handler, msg, uptimeMillis)); } else { // just ignore. Loading Loading
core/java/android/animation/ValueAnimator.java +120 −110 Original line number Diff line number Diff line Loading @@ -20,6 +20,7 @@ import android.os.Handler; import android.os.Looper; import android.os.Message; import android.util.AndroidRuntimeException; import android.view.Choreographer; import android.view.animation.AccelerateDecelerateInterpolator; import android.view.animation.AnimationUtils; import android.view.animation.LinearInterpolator; Loading @@ -45,17 +46,10 @@ public class ValueAnimator extends Animator { * Internal constants */ /* * The default amount of time in ms between animation frames */ private static final long DEFAULT_FRAME_DELAY = 10; /** * Messages sent to timing handler: START is sent when an animation first begins, FRAME is sent * by the handler to itself to process the next animation frame * Messages sent to timing handler: START is sent when an animation first begins. */ static final int ANIMATION_START = 0; static final int ANIMATION_FRAME = 1; /** * Values used with internal variable mPlayingState to indicate the current state of an Loading Loading @@ -162,9 +156,6 @@ public class ValueAnimator extends Animator { // The amount of time in ms to delay starting the animation after start() is called private long mStartDelay = 0; // The number of milliseconds between animation frames private static long sFrameDelay = DEFAULT_FRAME_DELAY; // The number of times the animation will repeat. The default is 0, which means the animation // will play only once private int mRepeatCount = 0; Loading Loading @@ -511,7 +502,8 @@ public class ValueAnimator extends Animator { * animations possible. * */ private static class AnimationHandler extends Handler { private static class AnimationHandler extends Handler implements Choreographer.OnAnimateListener { // The per-thread list of all active animations private final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>(); Loading @@ -526,40 +518,41 @@ public class ValueAnimator extends Animator { private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>(); private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>(); private final Choreographer mChoreographer; private boolean mIsChoreographed; private AnimationHandler() { mChoreographer = Choreographer.getInstance(); } /** * There are only two messages that we care about: ANIMATION_START and * ANIMATION_FRAME. The START message is sent when an animation's start() * method is called. It cannot start synchronously when start() is called * The START message is sent when an animation's start() method is called. * It cannot start synchronously when start() is called * because the call may be on the wrong thread, and it would also not be * synchronized with other animations because it would not start on a common * timing pulse. So each animation sends a START message to the handler, which * causes the handler to place the animation on the active animations queue and * start processing frames for that animation. * The FRAME message is the one that is sent over and over while there are any * active animations to process. */ @Override public void handleMessage(Message msg) { boolean callAgain = true; ArrayList<ValueAnimator> animations = mAnimations; ArrayList<ValueAnimator> delayedAnims = mDelayedAnims; switch (msg.what) { // TODO: should we avoid sending frame message when starting if we // were already running? case ANIMATION_START: ArrayList<ValueAnimator> pendingAnimations = mPendingAnimations; if (animations.size() > 0 || delayedAnims.size() > 0) { callAgain = false; doAnimationStart(); break; } // pendingAnims holds any animations that have requested to be started // We're going to clear sPendingAnimations, but starting animation may } private void doAnimationStart() { // mPendingAnimations holds any animations that have requested to be started // We're going to clear mPendingAnimations, but starting animation may // cause more to be added to the pending list (for example, if one animation // starting triggers another starting). So we loop until sPendingAnimations // starting triggers another starting). So we loop until mPendingAnimations // is empty. while (pendingAnimations.size() > 0) { while (mPendingAnimations.size() > 0) { ArrayList<ValueAnimator> pendingCopy = (ArrayList<ValueAnimator>) pendingAnimations.clone(); pendingAnimations.clear(); (ArrayList<ValueAnimator>) mPendingAnimations.clone(); mPendingAnimations.clear(); int count = pendingCopy.size(); for (int i = 0; i < count; ++i) { ValueAnimator anim = pendingCopy.get(i); Loading @@ -567,48 +560,48 @@ public class ValueAnimator extends Animator { if (anim.mStartDelay == 0) { anim.startAnimation(this); } else { delayedAnims.add(anim); mDelayedAnims.add(anim); } } } // fall through to process first frame of new animations case ANIMATION_FRAME: doAnimationFrame(); } private void doAnimationFrame() { // currentTime holds the common time for all animations processed // during this frame long currentTime = AnimationUtils.currentAnimationTimeMillis(); ArrayList<ValueAnimator> readyAnims = mReadyAnims; ArrayList<ValueAnimator> endingAnims = mEndingAnims; // First, process animations currently sitting on the delayed queue, adding // them to the active animations if they are ready int numDelayedAnims = delayedAnims.size(); int numDelayedAnims = mDelayedAnims.size(); for (int i = 0; i < numDelayedAnims; ++i) { ValueAnimator anim = delayedAnims.get(i); ValueAnimator anim = mDelayedAnims.get(i); if (anim.delayedAnimationFrame(currentTime)) { readyAnims.add(anim); mReadyAnims.add(anim); } } int numReadyAnims = readyAnims.size(); int numReadyAnims = mReadyAnims.size(); if (numReadyAnims > 0) { for (int i = 0; i < numReadyAnims; ++i) { ValueAnimator anim = readyAnims.get(i); ValueAnimator anim = mReadyAnims.get(i); anim.startAnimation(this); anim.mRunning = true; delayedAnims.remove(anim); mDelayedAnims.remove(anim); } readyAnims.clear(); mReadyAnims.clear(); } // Now process all active animations. The return value from animationFrame() // tells the handler whether it should now be ended int numAnims = animations.size(); int numAnims = mAnimations.size(); int i = 0; while (i < numAnims) { ValueAnimator anim = animations.get(i); ValueAnimator anim = mAnimations.get(i); if (anim.animationFrame(currentTime)) { endingAnims.add(anim); mEndingAnims.add(anim); } if (animations.size() == numAnims) { if (mAnimations.size() == numAnims) { ++i; } else { // An animation might be canceled or ended by client code Loading @@ -619,25 +612,36 @@ public class ValueAnimator extends Animator { // but that entails garbage and processing overhead that would // be nice to avoid. --numAnims; endingAnims.remove(anim); mEndingAnims.remove(anim); } } if (endingAnims.size() > 0) { for (i = 0; i < endingAnims.size(); ++i) { endingAnims.get(i).endAnimation(this); if (mEndingAnims.size() > 0) { for (i = 0; i < mEndingAnims.size(); ++i) { mEndingAnims.get(i).endAnimation(this); } endingAnims.clear(); mEndingAnims.clear(); } // If there are still active or delayed animations, call the handler again // after the frameDelay if (callAgain && (!animations.isEmpty() || !delayedAnims.isEmpty())) { sendEmptyMessageDelayed(ANIMATION_FRAME, Math.max(0, sFrameDelay - (AnimationUtils.currentAnimationTimeMillis() - currentTime))); // If there are still active or delayed animations, schedule a future call to // onAnimate to process the next frame of the animations. if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) { if (!mIsChoreographed) { mIsChoreographed = true; mChoreographer.addOnAnimateListener(this); } mChoreographer.scheduleAnimation(); } else { if (mIsChoreographed) { mIsChoreographed = false; mChoreographer.removeOnAnimateListener(this); } break; } } @Override public void onAnimate() { doAnimationFrame(); } } /** Loading Loading @@ -667,10 +671,13 @@ public class ValueAnimator extends Animator { * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @return the requested time between frames, in milliseconds */ public static long getFrameDelay() { return sFrameDelay; return Choreographer.getFrameDelay(); } /** Loading @@ -680,10 +687,13 @@ public class ValueAnimator extends Animator { * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @param frameDelay the requested time between frames, in milliseconds */ public static void setFrameDelay(long frameDelay) { sFrameDelay = frameDelay; Choreographer.setFrameDelay(frameDelay); } /** Loading
core/java/android/view/Choreographer.java 0 → 100644 +380 −0 Original line number Diff line number Diff line /* * Copyright (C) 2011 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.view; import com.android.internal.util.ArrayUtils; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.os.SystemClock; import android.os.SystemProperties; import android.util.Log; /** * Coodinates animations and drawing for UI on a particular thread. * @hide */ public final class Choreographer extends Handler { private static final String TAG = "Choreographer"; private static final boolean DEBUG = false; // The default amount of time in ms between animation frames. // When vsync is not enabled, we want to have some idea of how long we should // wait before posting the next animation message. It is important that the // default value be less than the true inter-frame delay on all devices to avoid // situations where we might skip frames by waiting too long (we must compensate // for jitter and hardware variations). Regardless of this value, the animation // and display loop is ultimately rate-limited by how fast new graphics buffers can // be dequeued. private static final long DEFAULT_FRAME_DELAY = 10; // The number of milliseconds between animation frames. private static long sFrameDelay = DEFAULT_FRAME_DELAY; // Thread local storage for the choreographer. private static final ThreadLocal<Choreographer> sThreadInstance = new ThreadLocal<Choreographer>() { @Override protected Choreographer initialValue() { Looper looper = Looper.myLooper(); if (looper == null) { throw new IllegalStateException("The current thread must have a looper!"); } return new Choreographer(looper); } }; // System property to enable/disable vsync for animations and drawing. // Enabled by default. private static final boolean USE_VSYNC = SystemProperties.getBoolean( "debug.choreographer.vsync", true); // System property to enable/disable the use of the vsync / animation timer // for drawing rather than drawing immediately. // Enabled by default. private static final boolean USE_ANIMATION_TIMER_FOR_DRAW = SystemProperties.getBoolean( "debug.choreographer.animdraw", true); private static final int MSG_DO_ANIMATION = 0; private static final int MSG_DO_DRAW = 1; private final Looper mLooper; private OnAnimateListener[] mOnAnimateListeners; private OnDrawListener[] mOnDrawListeners; private boolean mAnimationScheduled; private boolean mDrawScheduled; private FrameDisplayEventReceiver mFrameDisplayEventReceiver; private long mLastAnimationTime; private long mLastDrawTime; private Choreographer(Looper looper) { super(looper); mLooper = looper; mLastAnimationTime = Long.MIN_VALUE; mLastDrawTime = Long.MIN_VALUE; } /** * Gets the choreographer for this thread. * Must be called on the UI thread. * * @return The choreographer for this thread. * @throws IllegalStateException if the thread does not have a looper. */ public static Choreographer getInstance() { return sThreadInstance.get(); } /** * The amount of time, in milliseconds, between each frame of the animation. This is a * requested time that the animation will attempt to honor, but the actual delay between * frames may be different, depending on system load and capabilities. This is a static * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @return the requested time between frames, in milliseconds */ public static long getFrameDelay() { return sFrameDelay; } /** * The amount of time, in milliseconds, between each frame of the animation. This is a * requested time that the animation will attempt to honor, but the actual delay between * frames may be different, depending on system load and capabilities. This is a static * function because the same delay will be applied to all animations, since they are all * run off of a single timing loop. * * The frame delay may be ignored when the animation system uses an external timing * source, such as the display refresh rate (vsync), to govern animations. * * @param frameDelay the requested time between frames, in milliseconds */ public static void setFrameDelay(long frameDelay) { sFrameDelay = frameDelay; } /** * Schedules animation (and drawing) to occur on the next frame synchronization boundary. * Must be called on the UI thread. */ public void scheduleAnimation() { if (!mAnimationScheduled) { mAnimationScheduled = true; if (USE_VSYNC) { if (DEBUG) { Log.d(TAG, "Scheduling vsync for animation."); } if (mFrameDisplayEventReceiver == null) { mFrameDisplayEventReceiver = new FrameDisplayEventReceiver(mLooper); } mFrameDisplayEventReceiver.scheduleVsync(); } else { final long now = SystemClock.uptimeMillis(); final long nextAnimationTime = Math.max(mLastAnimationTime + sFrameDelay, now); if (DEBUG) { Log.d(TAG, "Scheduling animation in " + (nextAnimationTime - now) + " ms."); } sendEmptyMessageAtTime(MSG_DO_ANIMATION, nextAnimationTime); } } } /** * Schedules drawing to occur on the next frame synchronization boundary. * Must be called on the UI thread. */ public void scheduleDraw() { if (!mDrawScheduled) { mDrawScheduled = true; if (USE_ANIMATION_TIMER_FOR_DRAW) { scheduleAnimation(); } else { if (DEBUG) { Log.d(TAG, "Scheduling draw immediately."); } sendEmptyMessage(MSG_DO_DRAW); } } } @Override public void handleMessage(Message msg) { switch (msg.what) { case MSG_DO_ANIMATION: doAnimation(); break; case MSG_DO_DRAW: doDraw(); break; } } private void doAnimation() { if (mAnimationScheduled) { mAnimationScheduled = false; final long start = SystemClock.uptimeMillis(); if (DEBUG) { Log.d(TAG, "Performing animation: " + Math.max(0, start - mLastAnimationTime) + " ms have elapsed since previous animation."); } mLastAnimationTime = start; final OnAnimateListener[] listeners = mOnAnimateListeners; if (listeners != null) { for (int i = 0; i < listeners.length; i++) { listeners[i].onAnimate(); } } if (DEBUG) { Log.d(TAG, "Animation took " + (SystemClock.uptimeMillis() - start) + " ms."); } } if (USE_ANIMATION_TIMER_FOR_DRAW) { doDraw(); } } private void doDraw() { if (mDrawScheduled) { mDrawScheduled = false; final long start = SystemClock.uptimeMillis(); if (DEBUG) { Log.d(TAG, "Performing draw: " + Math.max(0, start - mLastDrawTime) + " ms have elapsed since previous draw."); } mLastDrawTime = start; final OnDrawListener[] listeners = mOnDrawListeners; if (listeners != null) { for (int i = 0; i < listeners.length; i++) { listeners[i].onDraw(); } } if (DEBUG) { Log.d(TAG, "Draw took " + (SystemClock.uptimeMillis() - start) + " ms."); } } } /** * Adds an animation listener. * Must be called on the UI thread. * * @param listener The listener to add. */ public void addOnAnimateListener(OnAnimateListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Adding onAnimate listener: " + listener); } mOnAnimateListeners = ArrayUtils.appendElement(OnAnimateListener.class, mOnAnimateListeners, listener); } /** * Removes an animation listener. * Must be called on the UI thread. * * @param listener The listener to remove. */ public void removeOnAnimateListener(OnAnimateListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Removing onAnimate listener: " + listener); } mOnAnimateListeners = ArrayUtils.removeElement(OnAnimateListener.class, mOnAnimateListeners, listener); stopTimingLoopIfNoListeners(); } /** * Adds a draw listener. * Must be called on the UI thread. * * @param listener The listener to add. */ public void addOnDrawListener(OnDrawListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Adding onDraw listener: " + listener); } mOnDrawListeners = ArrayUtils.appendElement(OnDrawListener.class, mOnDrawListeners, listener); } /** * Removes a draw listener. * Must be called on the UI thread. * * @param listener The listener to remove. */ public void removeOnDrawListener(OnDrawListener listener) { if (listener == null) { throw new IllegalArgumentException("listener must not be null"); } if (DEBUG) { Log.d(TAG, "Removing onDraw listener: " + listener); } mOnDrawListeners = ArrayUtils.removeElement(OnDrawListener.class, mOnDrawListeners, listener); stopTimingLoopIfNoListeners(); } private void stopTimingLoopIfNoListeners() { if (mOnDrawListeners == null && mOnAnimateListeners == null) { if (DEBUG) { Log.d(TAG, "Stopping timing loop."); } if (mAnimationScheduled) { mAnimationScheduled = false; if (!USE_VSYNC) { removeMessages(MSG_DO_ANIMATION); } } if (mDrawScheduled) { mDrawScheduled = false; if (!USE_ANIMATION_TIMER_FOR_DRAW) { removeMessages(MSG_DO_DRAW); } } if (mFrameDisplayEventReceiver != null) { mFrameDisplayEventReceiver.dispose(); mFrameDisplayEventReceiver = null; } } } /** * Listens for animation frame timing events. */ public static interface OnAnimateListener { /** * Called to animate properties before drawing the frame. */ public void onAnimate(); } /** * Listens for draw frame timing events. */ public static interface OnDrawListener { /** * Called to draw the frame. */ public void onDraw(); } private final class FrameDisplayEventReceiver extends DisplayEventReceiver { public FrameDisplayEventReceiver(Looper looper) { super(looper); } @Override public void onVsync(long timestampNanos, int frame) { doAnimation(); } } }
core/java/android/view/ViewRootImpl.java +197 −200 File changed.Preview size limit exceeded, changes collapsed. Show changes
core/java/com/android/internal/util/ArrayUtils.java +52 −1 Original line number Diff line number Diff line Loading @@ -17,7 +17,6 @@ package com.android.internal.util; import java.lang.reflect.Array; import java.util.Collection; // XXX these should be changed to reflect the actual memory allocator we use. // it looks like right now objects want to be powers of 2 minus 8 Loading Loading @@ -142,4 +141,56 @@ public class ArrayUtils } return false; } /** * Appends an element to a copy of the array and returns the copy. * @param array The original array, or null to represent an empty array. * @param element The element to add. * @return A new array that contains all of the elements of the original array * with the specified element added at the end. */ @SuppressWarnings("unchecked") public static <T> T[] appendElement(Class<T> kind, T[] array, T element) { final T[] result; final int end; if (array != null) { end = array.length; result = (T[])Array.newInstance(kind, end + 1); System.arraycopy(array, 0, result, 0, end); } else { end = 0; result = (T[])Array.newInstance(kind, 1); } result[end] = element; return result; } /** * Removes an element from a copy of the array and returns the copy. * If the element is not present, then the original array is returned unmodified. * @param array The original array, or null to represent an empty array. * @param element The element to remove. * @return A new array that contains all of the elements of the original array * except the first copy of the specified element removed. If the specified element * was not present, then returns the original array. Returns null if the result * would be an empty array. */ @SuppressWarnings("unchecked") public static <T> T[] removeElement(Class<T> kind, T[] array, T element) { if (array != null) { final int length = array.length; for (int i = 0; i < length; i++) { if (array[i] == element) { if (length == 1) { return null; } T[] result = (T[])Array.newInstance(kind, length - 1); System.arraycopy(array, 0, result, 0, i); System.arraycopy(array, i + 1, result, i, length - i - 1); return result; } } } return array; } }
tools/layoutlib/bridge/src/android/animation/AnimationThread.java +5 −2 Original line number Diff line number Diff line Loading @@ -86,8 +86,11 @@ public abstract class AnimationThread extends Thread { try { Handler_Delegate.setCallback(new IHandlerCallback() { public void sendMessageAtTime(Handler handler, Message msg, long uptimeMillis) { if (msg.what == ValueAnimator.ANIMATION_START || msg.what == ValueAnimator.ANIMATION_FRAME) { if (msg.what == ValueAnimator.ANIMATION_START /*|| FIXME: The ANIMATION_FRAME message no longer exists. Instead, the animation timing loop is based on a Choreographer object that schedules animation and drawing frames. msg.what == ValueAnimator.ANIMATION_FRAME*/) { mQueue.add(new MessageBundle(handler, msg, uptimeMillis)); } else { // just ignore. Loading