Android属性动画源码分析

属性动画实现主要有以下两个类: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.


*
* @param playBackwards Whether the ValueAnimator should start playing in reverse.
*/
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.AnimationFrameCallback这个接口
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);
        }
    }
}

接下来会进入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.


*
* @param fraction The elapsed fraction of the animation.
*/
@CallSuper
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);
}
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
这个方法的主要作用是计算动画每一帧的值并且调用动画更新接口的onAnimationUpdate方法,实现动画更新

我们重新进到

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.
*


*
* @param callback The frame callback to run during the next frame.
*
* @see #postFrameCallbackDelayed
* @see #removeFrameCallback
*/
public void postFrameCallback(FrameCallback callback) {
postFrameCallbackDelayed(callback, 0);
}
接着进入
/**
* Posts a frame callback to run on the next frame after the specified delay.
*


* The callback runs once then is automatically removed.
*


*
* @param callback The frame callback to run during the next frame.
* @param delayMillis The delay time in milliseconds.
*
* @see #postFrameCallback
* @see #removeFrameCallback
*/
public void postFrameCallbackDelayed(FrameCallback callback, long delayMillis) {
if (callback == null) {
throw new IllegalArgumentException(“callback must not be null”);
}
    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 <code>setName()</code>, where <code>name</code> is
 * the value of the <code>propertyName</code> 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的属性名字,包括自定义属性也可以。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值