ValueAnimator 源码深入分析
Posted 薛瑄
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了ValueAnimator 源码深入分析相关的知识,希望对你有一定的参考价值。
前言
android 的View动画, Roate Scale Translate Alpha 等,可以组合使用实现一些动画效果,但是这些动画却有一个致命的弱点,它们只是改变了 View 显示的大小,而没有改变 View 的响应区域。详情可查看这篇文章View 的 translationX、 translationY , X、Y 和 Left、Top,Right、Bottom
这时以 ObjectAnimator、ValueAnimator 为代表的属性动画也就应运而生了,就是更改View的属性例如,宽,高,透明度等等。来实现动画效果的。那么他是如何实现的呢?又是如何保证动画从头开始执行呢?
一、 如何使用
先来看个简单的示例
private void performAnimate(final View target, final int start, final int end)
//针对width属性
PropertyValuesHolder propertyValuesHolder1 = PropertyValuesHolder.ofInt("width", 1, 1, 5);
//使用PropertyValuesHolder 对象创建ValueAnimator
ValueAnimator valueAnimator = ValueAnimator.ofPropertyValuesHolder(propertyValuesHolder1);
//设置每一帧的回调接口
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener()
@Override
public void onAnimationUpdate(ValueAnimator animator)
//获取到属性width 的值,设置给view
target.getLayoutParams().width = (int) animator.getAnimatedValue("width");
target.requestLayout();
);
valueAnimator.setDuration(5000) //设置持续时间
.setInterpolator(new LinearInterpolator()) //设置插值器
.start();
大家应该都很熟悉这段代码了,就不多说,直接开始分析源码
二、源码分析
2.1、PropertyValuesHolder
public static PropertyValuesHolder ofInt(String propertyName, int... values)
return new IntPropertyValuesHolder(propertyName, values);
static class IntPropertyValuesHolder extends PropertyValuesHolder
public IntPropertyValuesHolder(String propertyName, int... values)
super(propertyName);
setIntValues(values);
@Override
public void setIntValues(int... values)
//调用基类的setIntValues函数
super.setIntValues(values);
mIntKeyframes = (Keyframes.IntKeyframes) mKeyframes;
调用PropertyValuesHolder的setIntValues
public void setIntValues(int... values)
mValueType = int.class;
mKeyframes = KeyframeSet.ofInt(values);
public static KeyframeSet ofInt(int... values)
int numKeyframes = values.length;
//根据参数长度,
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1)
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f);
keyframes[1] = (IntKeyframe) Keyframe.ofInt(1f, values[0]);
else
//按照参数的索引,计算动画的进行的比例,动画在该比例下的属性值是索引对应的值
keyframes[0] = (IntKeyframe) Keyframe.ofInt(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i)
keyframes[i] =
(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);
return new IntKeyframeSet(keyframes);
创建了IntKeyframeSet的数组,分了两种情况,
-
numKeyframes == 1时只会设置keyframes[0]和keyframes[1]分别代表的是动画的起始位置和结束位置;
-
numKeyframes > 1时,则将动画均分成numKeyframes - 1份,也就是有numKeyframes - 1 个 IntKeyframe,每个保存着当前动画进度 和值的信息。最后构建了一个IntKeyframeSet对象
2.2 、 ValueAnimator.ofInt
public static ValueAnimator ofInt(int... values)
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
public void setIntValues(int... values)
if (values == null || values.length == 0)
return;
//传入的参数values,会设置到mValues,如果mValues为没有任何数据,就需要为它设置数据
if (mValues == null || mValues.length == 0)
setValues(PropertyValuesHolder.ofInt("", values));
else
PropertyValuesHolder valuesHolder = mValues[0];
valuesHolder.setIntValues(values);
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
2.3 ValueAnimator#start
一切都设置好后,开始执行动画
private void start(boolean playBackwards)
if (Looper.myLooper() == null)
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
//是否倒序播放
mReversing = playBackwards;
mSelfPulse = !mSuppressSelfPulseRequested;
//如果是倒序播放,并且指定了开始位置(从头到尾,在这个过程的百分比),那么需要重新计算开始的位置,倒序是从尾到头,需要转换mSeekFraction
// 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 = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing)
// 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);
private void addAnimationCallback(long delay)
if (!mSelfPulse)
return;
//获取当前线程的AnimationHandler对象(该对象使用ThreadLocal来描述的)
//这里传入的回调接口是this,因为ValueAnimator 实现了AnimationFrameCallback 接口,后面会分析到
getAnimationHandler().addAnimationFrameCallback(this, delay);
2.4、AnimationHandler# addAnimationFrameCallback
//callback 就是ValueAnimator ,它实现的AnimationFrameCallback 接口
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay)
//第一次调用的时候,还没有添加任何回调,所以mAnimationCallbacks 的大小为0
if (mAnimationCallbacks.size() == 0)
//getProvider 函数获取一个AnimationFrameCallbackProvider 类型的对象
getProvider().postFrameCallback(mFrameCallback);
//把当前回调添加到mAnimationCallbacks,不能重复添加
if (!mAnimationCallbacks.contains(callback))
mAnimationCallbacks.add(callback);
if (delay > 0)
mDelayedCallbackStartTime.put(callback, (SystemClock.uptimeMillis() + delay));
private AnimationFrameCallbackProvider getProvider()
if (mProvider == null)
mProvider = new MyFrameCallbackProvider();
return mProvider;
2.5、AnimationHandler# MyFrameCallbackProvider
private class MyFrameCallbackProvider implements AnimationFrameCallbackProvider
//Choreographer 的实例,也是使用ThreadLocal 描述的
final Choreographer mChoreographer = Choreographer.getInstance();
@Override
public void postFrameCallback(Choreographer.FrameCallback callback)
//这里最终调用了Choreographer的函数,
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);
可以看到 最终的操作,都是交给了Choreographer 处理的,不熟悉 Choreographer的同学,可以查看Choreographer 源码分析,简单来说Choreographer 会根据VSync信号来回调传递进去的接口。可以看到上面postFrameCallback,postCallback
都指定了回调接口
2.6、AnimationHandler# mFrameCallback
那这个回调接口,到底是什么呢?它都执行了什么呢
// 看到它的类型,知道它是被Choreographer 回调的
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback()
@Override
public void doFrame(long frameTimeNanos)
//执行动画的回调,对该时刻的对应的那帧动画 进行处理
doAnimationFrame(getProvider().getFrameTime());
//注意此时的mAnimationCallbacks 是大于0的,是在2.4小节的代码中被添加进去的
if (mAnimationCallbacks.size() > 0)
//再次调用mChoreographer.postFrameCallback,回调是当前对象,所以收到下一个VSync信号 ,还会执行这个回调doFrame
//不断循环,直到动画结束,可以猜想到,动画结束,mAnimationCallbacks的大小肯定是0
getProvider().postFrameCallback(this);
;
private void doAnimationFrame(long frameTime)
//获取当前时间
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++)
//依次取出,回调接口
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null)
continue;
//是否到了 执行callback的时间,(主要针对延迟执行的回调)
if (isCallbackDue(callback, currentTime))
//执行回调接口
callback.doAnimationFrame(frameTime);
//下面这段代码,现在已经没有用了,但是理解它解决的问题,还是有意义的
//现在该问题是如何解决的呢?我初步分析 是在2.7中设置startTime
if (mCommitCallbacks.contains(callback))
getProvider().postCommitCallback(new Runnable()
@Override
public void run()
commitAnimationFrame(callback, getProvider().getFrameTime());
);
cleanUpList();
下面我们先来简单介绍一下postCommitCallback
是在解决什么问题。之后再回来分析callback.doAnimationFrame(frameTime);
画是使用插值器,根据时间的流逝来计算比例。在动画开始执行,如果第一帧的渲染时间过长,会导致不是从第一帧开始播放,为了避免这个问题,需要在绘制完后,重新设置动画开始时间。
commitAnimationFrame 最终执行到,下面这个回调(接口AnimationFrameCallback的)
ValueAnimator.java
public void commitAnimationFrame(long frameTime)
if (!mStartTimeCommitted)
mStartTimeCommitted = true;
long adjustment = frameTime - mLastFrameTime;
if (adjustment > 0)
//调整动画的开始时间
mStartTime += adjustment;
2.7、ValueAnimator#doAnimationFrame
public final boolean doAnimationFrame(long frameTime)
if (mStartTime < 0)
//mStartTime 小于0 表示这是第一次进入,因为初始值是-1,
//也就是第一帧动画,为了防止在绘制过程太长,动画还没真正开始执行,这里重新设置动画开始时间,保证动画是从第一帧执行的
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
//根据是否倒序播放,设置开始时间
mStartTime = mReversing ? frameTime : frameTime + (long) (mStartDelay * sDurationScale);
// Handle pause/resume
if (mPaused)
//动画暂停了,就记录暂停的时间,待下次重新执行时,需要把暂停时流逝的时间减去
mPauseTime = frameTime;
//这里删除动画的回调,也就说2.6小节,不会继续执行getProvider().postFrameCallback(this)
removeAnimationCallback();
return false;
else if (mResumed)
mResumed = false;
if (mPauseTime > 0)
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
if (!mRunning)
// If not running, that means the animation is in the start delay phase of a forward
// running animation. In the case of reversing, we want to run start delay in the end.
if (mStartTime > frameTime && mSeekFraction == -1)
// This is when no seek fraction is set during start delay. If developers change the
// seek fraction during the delay, animation will start from the seeked position
// right away.
return false;
else
// If mRunning is not set by now, that means non-zero start delay,
// no seeking, not reversing. At this point, start delay has passed.
mRunning = true;
startAnimation();
//mLastFrameTime 初始值是-1,
if (mLastFrameTime < 0)
if (mSeekFraction >= 0)
//根据初始播放的百分比,计算动画开始时间
long seekTime = (long) (getScaledDuration() * mSeekFraction);
//把开始时间往前移动,从而达到直接在指定位置开始播放动画
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
//允许修正开始时间(2.6小节的 commitAnimationFrame 函数会用到这个字段)
mStartTimeCommitted = false; // allow start time to be compensated for jank
mLastFrameTime = frameTime;
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished)
endAnimation();
return finished;
2.8、 ValueAnimator#animateBasedOnTime
boolean animateBasedOnTime(long currentTime)
boolean done = false;
if (mRunning)
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
if (scaledDuration == ValueAnimator源码解析-基于Android API30
Android属性动画ValueAnimator源码简单分析