属性动画实现主要有以下两个类:ValueAnimator,ObjectAnimator。其中ObjectAnimator是ValueAnimator的子类,下面我们先从ValueAnimator源码开始分析。
获取ValueAnimator实例有多种方式,包括如下ValueAnimator()空的构造方法,多个静态方法:public static ValueAnimator ofInt(int… values),public static ValueAnimator ofArgb(int… values), public static ValueAnimator ofFloat(float… values),public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder… values),public static ValueAnimator ofObject(TypeEvaluator evaluator, Object… values)。这些静态方法的实现基本相同,一下就拿一个静态方法来分析:
/**
* Constructs and returns a ValueAnimator that animates between int values. A single
* value implies that that value is the one being animated to. However, this is not typically
* useful in a ValueAnimator object because there is no way for the object to determine the
* starting value for the animation (unlike ObjectAnimator, which can derive that value
* from the target object and property being animated). Therefore, there should typically
* be two or more values.
*
* @param values A set of values that the animation will animate between over time.
* @return A ValueAnimator object that is set up to animate between the given values.
*/
public static ValueAnimator ofInt(int… values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
内部实现其实就是通过调用唯一的构造方法来初始化这个对象,再将传进来的值给赋值过去,其实就是一种简化初始化对象的方法。
研究ValueAnimator的实现原理,我们先从它的start方法开始分析,但是在这个分析之前我们先得了解下AnimationHandler.AnimationFrameCallback这个接口,进入这个接口我们可以看到如下的接口定义
interface AnimationFrameCallback {
/**
* Run animation based on the frame time.
* @param frameTime The frame start time, in the {@link SystemClock#uptimeMillis()} time
* base.
* @return if the animation has finished.
*
*/
boolean 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);
}
随着时间的变化会不断的调用doAnimationFrame方法来处理每一帧动画。
commitAnimationFrame
对动画进行调整,以补偿动画第一次运行时和绘制帧之间产生的偏差。
下面我们从start方法开始研究属性动画是怎么运行的
@Override
public void start() {
start(false);
}
我们看到它调用了如下方法
/**
* Start the animation playing. This version of start() takes a boolean flag that indicates
* whether the animation should play in reverse. The flag is usually false, but may be set
* to true if called from the reverse() method.
*
*
The animation started by calling this method will be run on the thread that called
* this method. This thread should have a Looper on it (a runtime exception will be thrown if
* this is not the case). Also, if the animation will animate
* properties of objects in the view hierarchy, then the calling thread should be the UI
* thread for that view hierarchy.
if (mStartDelay == 0 || mSeekFraction >= 0) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
接下来会进入setCurrentFraction这个方法
/**
* Sets the position of the animation to the specified fraction. This fraction should
* be between 0 and the total fraction of the animation, including any repetition. That is,
* a fraction of 0 will position the animation at the beginning, a value of 1 at the end,
* and a value of 2 at the end of a reversing animator that repeats once. If
* the animation has not yet been started, then it will not advance forward after it is
* set to this fraction; it will simply set the fraction to this value and perform any
* appropriate actions based on that fraction. If the animation is already running, then
* setCurrentFraction() will set the current fraction to this value and continue
* playing from that point. {@link Animator.AnimatorListener} events are not called
* due to changing the fraction; those events are only processed while the animation
* is running.
*
* @param fraction The fraction to which the animation is advanced or rewound. Values
* outside the range of 0 to the maximum fraction for the animator will be clamped to
* the correct range.
*/
public void setCurrentFraction(float fraction) {
initAnimation();
fraction = clampFraction(fraction);
long seekTime = (long) (getScaledDuration() * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
mStartTime = currentTime - seekTime;
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
if (!isPulsingInternal()) {
// If the animation loop hasn’t started, the startTime will be adjusted in the first
// frame based on seek fraction.
mSeekFraction = fraction;
}
mOverallFraction = fraction;
final float currentIterationFraction = getCurrentIterationFraction(fraction);
animateValue(currentIterationFraction);
}
我们看到它最终会进入animateValue这个方法,如下
/**
* This method is called with the elapsed fraction of the animation during every
* animation frame. This function turns the elapsed fraction into an interpolated fraction
* and then into an animated value (from the evaluator. The function is called mostly during
* animation updates, but it is also called when the end()
* function is called, to set the final value on the property.
*
*
Overrides of this method must call the superclass to perform the calculation
* of the animated value.
我们重新进到
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
throw new AndroidRuntimeException(“Animators may only be run on Looper threads”);
}
mReversing = playBackwards;
// Special case: reversing from seek-to-0 should act as if not seeked at all.
if (playBackwards && mSeekFraction != -1 && mSeekFraction != 0) {
if (mRepeatCount == INFINITE) {
// Calculate the fraction of the current iteration.
float fraction = (float) (mSeekFraction - Math.floor(mSeekFraction));
mSeekFraction = 1 - fraction;
} else {
mSeekFraction = 1 + mRepeatCount - mSeekFraction;
}
}
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
// Resets mLastFrameTime when start() is called, so that if the animation was running,
// calling start() would put the animation in the
// started-but-not-yet-reached-the-first-frame phase.
mLastFrameTime = 0;
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
if (mStartDelay == 0 || mSeekFraction >= 0) {
// If there's no start delay, init the animation and notify start listeners right away
// to be consistent with the previous behavior. Otherwise, postpone this until the first
// frame after the start delay.
startAnimation();
if (mSeekFraction == -1) {
// No seek, start at play time 0. Note that the reason we are not using fraction 0
// is because for animations with 0 duration, we want to be consistent with pre-N
// behavior: skip to the final value immediately.
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
}
接下来我们进入animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));这个方法
/**
* 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));
}
}
我们看到如果动画回调为0就进入getProvider().postFrameCallback这个方法,
@Override
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
/**
* Posts a frame callback to run on the next frame.
*
* The callback runs once then is automatically removed.
*
* The callback runs once then is automatically removed.
*
postCallbackDelayedInternal(CALLBACK_ANIMATION,
callback, FRAME_CALLBACK_TOKEN, delayMillis);
}
接着进入
private void postCallbackDelayedInternal(int callbackType,
Object action, Object token, long delayMillis) {
if (DEBUG_FRAMES) {
Log.d(TAG, “PostCallback: type=” + callbackType
+ “, action=” + action + “, token=” + token
+ “, delayMillis=” + delayMillis);
}
synchronized (mLock) {
final long now = SystemClock.uptimeMillis();
final long dueTime = now + delayMillis;
mCallbackQueues[callbackType].addCallbackLocked(dueTime, action, token);
//如果动画不延迟执行
if (dueTime <= now) {
scheduleFrameLocked(now);
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_CALLBACK, action);
msg.arg1 = callbackType;
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, dueTime);
}
}
}
我们加入动画不延迟执行,就进入scheduleFrameLocked这个方法
private void scheduleFrameLocked(long now) {
if (!mFrameScheduled) {
mFrameScheduled = true;
if (USE_VSYNC) {
if (DEBUG_FRAMES) {
Log.d(TAG, “Scheduling next frame on vsync.”);
}
// If running on the Looper thread, then schedule the vsync immediately,
// otherwise post a message to schedule the vsync from the UI thread
// as soon as possible.
//如果动画是执行在主线程
if (isRunningOnLooperThreadLocked()) {
scheduleVsyncLocked();
} else {
Message msg = mHandler.obtainMessage(MSG_DO_SCHEDULE_VSYNC);
msg.setAsynchronous(true);
mHandler.sendMessageAtFrontOfQueue(msg);
}
} else {
final long nextFrameTime = Math.max(
mLastFrameTimeNanos / TimeUtils.NANOS_PER_MS + sFrameDelay, now);
if (DEBUG_FRAMES) {
Log.d(TAG, "Scheduling next frame in " + (nextFrameTime - now) + " ms.");
}
Message msg = mHandler.obtainMessage(MSG_DO_FRAME);
msg.setAsynchronous(true);
mHandler.sendMessageAtTime(msg, nextFrameTime);
}
}
}
如果动画执行在主线程上就进入scheduleVsyncLocked()这个方法
private void scheduleVsyncLocked() {
mDisplayEventReceiver.scheduleVsync();
}
接着进入
/**
* Schedules a single vertical sync pulse to be delivered when the next
* display frame begins.
*/
public void scheduleVsync() {
if (mReceiverPtr == 0) {
Log.w(TAG, "Attempted to schedule a vertical sync pulse but the display event "
+ "receiver has already been disposed.");
} else {
nativeScheduleVsync(mReceiverPtr);
}
}
这个调用JNI方法,到此就研究完毕了,JNI暂时就不分析了。
另外ObjectAnimator的以of开头的静态方法,
比如
/**
* Constructs and returns an ObjectAnimator that animates between float values. A single
* value implies that that value is the one being animated to, in which case the start value
* will be derived from the property being animated and the target object when {@link #start()}
* is called for the first time. Two values imply starting and ending values. More than two
* values imply a starting value, values to animate through along the way, and an ending value
* (these values will be distributed evenly across the duration of the animation).
*
* @param target The object whose property is to be animated. This object should
* have a public method on it called setName()
, where name
is
* the value of the propertyName
parameter.
* @param propertyName The name of the property being animated.
* @param values A set of values that the animation will animate between over time.
* @return An ObjectAnimator object that is set up to animate between the given values.
*/
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
其中的propertyName是指view的属性名字,包括自定义属性也可以。