您的位置:首页 > 产品设计 > UI/UE

Android动画总结系列(5)——属性动画源码分析(Aniamtor/ValueAnimator)

2016-11-15 16:32 615 查看
一、整体结构
本篇文章主要总结属性动画相关的源码的分析,属性动画的使用见上一篇文章,属性动画的源码比较多,整篇文章会分作两篇小文章:
Animator类与ValueAnimator类:这是属性动画最核心的部分。
ObjectAnimator类与AnimatorSet类及其他:这是属性动画的应用部分。

1.1 属性动画整体结构





Animator是一个抽象类,内部基本都是空实现。
ValueAnimator是属性动画的核心元素,完成在指定时间内对指定属性从初始值到结束值不断变化的实现控制。
ObjectAnimator是ValueAnimator的应用,在ValueAnimator完成了属性值计算时,ObjectAnimator完成将当前属性值设置到对象的过程。
TimeAnimator没啥作用,可以略过。
AnimatorSet持有多个Animator对象,形成动画集合的效果(包括动画同时执行或先后执行等控制)。

1.2 属性动画流程





上图是属性动画的执行流程图,ValueAnimator内的AnimationHandler对象扮演的是帧刷新时间控制器的角色,在动画开始后它不断的定时执行runnable直到动画结束。在每次runnable执行时,就是一次新的刷新帧(属性值)的开始,此时它触发自己的doAnimationFrame方法,并在方法执行过程中触发ValueAnimator的doAnimationFrame开始新的属性值计算。
ValueAnimator通过归一化的frameTime来确定动画当前的位置,通过TimeInterpolator计算变化后的插值时间,形成动画变化率的改变。
计算了插值后的时间后,委托PropertyValuesHolder计算插值的属性值,PropertyValuesHolder委托KeyFrameSet插值,最终委托TypeEvaluator计算属性的插值。
插值计算完成后,通过TypeConverter转化属性值成为新的对象,最终存储新的属性值。在插值完成后,通知ObjectAnimator刷新。

二、抽象类Animator及其接口
Animator是动画的抽象类,其内部封装了动画应提供的基本操作(start/end/...),以及动画的事件的注册与对外通知。但到目前为止,此类还只作为属性动画的抽象,并不涉及属性动画相关的逻辑。

2.1 成员变量
//对外通知动画开始/结束/取消/重复执行事件的监听器列表
ArrayList<AnimatorListener> mListeners = null;

//对外通知动画暂停/恢复的监听器列表
ArrayList<AnimatorPauseListener> mPauseListeners = null ;

//动画当前是否处于暂停状态
boolean mPaused = false ;


[align=left]2.2 关键方法[/align]
//开始动画执行,如果动画有开始延迟(startDelay),则动画在延迟之后开始运行(running),动画初始值在第一帧开始设置,随后调用onAnimationStart
//调用此方法后,当前动画就在调用线程上运行,线程需要一个Looper,如果动画引起View变化,则应在UI线程调用start,或在UI线程处理onAnimationUpdate
public void start();

//取消动画运行,与end不同的是,cancel让动画直接结束,并使状态立刻复位,而end让动画执行到最后一帧,再使状态复位。调用cancel,外部会先收到onAnimationCancel,再收到onAnimationEnd,也就是收到两个回调。此方法应该在运行动画的线程上调用,否则操作并无作用(动画是依附在运行线程的ThreadLocal中的,以此来避开多线程操作)。
public void cancel();

//结束动画运行,动画会先执行到最后一帧,然后再调用onAnimationEnd,而cancel事件不会有执行到最后一帧的动作。此方法也只能在动画运行线程执行。
public void end();

//暂停一个正在运行的动画,只能在动画运行的线程上调用。如果动画还没start(isStarted=false)或者已经结束,此调用会被忽略,被暂停的动画可以通过resume()来恢复。
//置mPaused为true,并对外通知onAnimationPause事件。
public void pause();

//恢复一个暂停的动画,动画从暂停处继续运行。只能在动画运行的线程上调用。如果当前动画不是paused状态,则此调用忽略。
//置mPaused为false,对外通知onAnimationResume事件。
public void resume();

//动画当前是否处于暂停状态。
public boolean isPaused();

//获取动画的开始延迟,也就是动画从start到running的延迟时间,单位毫秒。
public abstract long getStartDelay();

//设置动画开始延迟
public abstract void setStartDelay(long startDelay);

//设置动画执行一次的耗时,单位ms
public abstract Animator setDuration(long duration);

//获取动画执行一次的耗时。
public abstract long getDuration();

//设置动画的时间插值器,也就是动画的变化速率,默认是AccelerateDecelerateInterpolator
public abstract void setInterpolator(TimeInterpolator value);

//获取动画的时间插值器
public TimeInterpolator getInterpolator();

//返回动画是否正在执行,正在执行状态是动画start并且过了startDelay的时间后,到动画结束之前的状态。
public abstract boolean isRunning();

//返回动画是否已经开始。从动画调用start后到动画结束之前都返回true,比isRunning涵盖的时间要广,多出的就是startDelay的时间。在startDelay内,isRunning返回false,isStarted返回true。
public boolean isStarted();

//添加一个AnimatorListener监听(监听start/repeat/end/cancel)
public void addListener(AnimatorListener listener);

//移除一个已经设置的AnimatorListener
public void removeListener(AnimatorListener listener);

//获取已经设置的AnimatorListener的集合。
public ArrayList<AnimatorListener> getListeners();

//添加一个暂停/恢复事件监听器,AnimatorPauseListener通知动画的暂停/恢复事件。
public void addPauseListener(AnimatorPauseListener listener);

//移除一个已经设置的AnimatorPauseListener
public void removePauseListener(AnimatorPauseListener listener);

//移除所有已经设置的监听器,包括AnimatorListener和AnimatorPauseListener
public void removeAllListeners();

//此方法用于配置动画的初始值,AnimatorSet会将此调用传给子元素,ObjectAnimator从其目标对象和PropertyValueHolder的属性中获取开始值,ValueAnimator没有足够信息获取开始值,所以会忽略此调用。
public void setupStartValues();

//此方法用于配置动画的结束值,其他同上。
public void setupEndValues();

//设置动画运行在哪个对象上。
public void setTarget(Object target);

//返回动画是否可以逆序运行
public boolean canReverse();
//让动画逆序执行
public void reverse();

//AnimatorListener接口监听动画开始(onAnimationStart)/重复(onAnimationRepeat)/结束(onAnimationEnd)/取消(onAnimationCancel)事件,接口不再展开描述
public static interface AnimatorListener;

//AnimatorPauseListener接口监听动画暂停(onAnimationPause)/恢复(onAnimationResume)事件,接口不再展开。
public static interface AnimatorPauseListener;


三、ValueAnimator接口及关键实现

3.1 构造器与静态工厂
ValueAnimator提供默认的空实现构造器,不过一般不常用,用ValueAnimator时通常都是使用其提供的静态工厂。
3.1.1 支持int类型属性值的静态工厂
如果传入的值只有一个,表示这个值是需要动画执行到的结束值。这种用法不常用,因为ValueAnimator不同于ObjectAnimator,它没有对象的当前状态,所以取不到一个开始值。建议传入两个以上的值。如果真的只传入了一个值,则初始值为0。假设传入值是100,则演变过程是0~100。
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}


3.1.2 支持颜色属性值的静态工厂
颜色值的表示是16进制int,类似0xFFFFFFFF的表示形式,与ofInt相同,传入一个值时,此值作为目标值,若传入值是0xFFFFFFFF,则演变过程从0x00000000~0xFFFFFFFF。
public static ValueAnimator ofArgb(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
anim.setEvaluator(ArgbEvaluator.getInstance());
return anim;
}


3.1.3 支持浮点数的静态工厂
与ofInt相同,传入一个值时,此值作为目标,假设为1.0f,则演变过程为从0.0f~1.0f
[align=left]
public static ValueAnimator ofFloat(float... values);
[/align]

3.1.4 支持多个PropertyValuesHolder的静态工厂
PropertyValuesHolder内持有属性的开始/结束值以及属性插值计算方法(TypeEvaluator),此工厂方法构建多个属性值执行动画组合在一起的ValueAnimator。
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values) {
ValueAnimator anim = new ValueAnimator();
anim.setValues(values);
return anim;
}


3.1.5 支持对象的静态工厂
返回一个从对象开始值到对象结束值插值的ValueAnimator,因为ValuaAnimator并不知道怎么对任意的Object做插值,此处需要提供一个TypeEvaluator来完成插值动作。如果values只传入了一个值,则初始值被设定为null,所以在自定义的TypeEvaluator的evaluate方法中收到的startValue值为null(注意做空判断)。
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
ValueAnimator anim = new ValueAnimator();
anim.setObjectValues(values);
anim.setEvaluator(evaluator);
return anim;
}


3.2 各接口及其作用
3.2.1 DurationScale的getter/setter
DurationScale是用来做什么的呢?
Android的调试模式里有一个动画执行速率的调整,我们可以选择减慢1倍/2倍/5倍之类的效果,来放慢观察本应快速完成的动画,这个缩放比例就是此处的durationScale,下面两个方法是给系统调用的,我们调用不了。
public static void setDurationScale(float durationScale) {
sDurationScale = durationScale;
}

public static float getDurationScale() {
return sDurationScale;
}


3.2.2 void setIntValues( int... values):设置ValueAnimator Int型属性值演变的开始到结束值,如果对象已经设置过不止一个PropertyValuesHolder,则此值会被设置到第一个PropertyValuesHolder内。
ValueAnimator.ofInt静态工厂是对此方法的封装,values建议传入2个及以上的值,原因前面提到了,此处不细说。

3.2.3 void setFloatValues( float... values):设置ValueAnimator Float型属性值演变的开始到结束值,如果对象已经设置过不止一个PropertyValuesHolder,则此值会被设置到第一个PropertyValuesHolder内。
ValueAnimator.ofFloat静态工厂是对此方法的封装。

3.2.4 void setObjectValues(Object... values):设置ValueAnimator对象型属性值演变的开始到结束值,如果对象设置过不止一个PropertyValuesHolder,则此值会被设置到第一个PropertyValuesHolder内。因为ValueAnimator并不能对Object插值,所以此方法应该与void setEvaluator(TypeEvaluator value)一起使用,告知ValueAnimator如何插值对象型属性值。
ValueAnimator.ofObject是对此方法的封装。

3.2.5 void setValues(PropertyValuesHolder... values):设置ValueAnimator同时演变的多个属性,每个属性都支持自己的开始值和结束值,类型有int/float/object三种,颜色(argb)本质是int,与普通的int插值的差别只是用了ArgbEvaluator计算插值而已。
ValueAnimator.ofPropertyValuesHolder是对此方法的封装。

3.2.6 PropertyValuesHolder[] getValues():返回ValueAnimator需要演变的一个或多个属性的内容。每一个PropertyValuesHolder内封装了待演变的属性,和属性的开始值/结束值。

3.2.7 初始化动画
void initAnimation() {
if (!mInitialized) {
int numValues = mValues. length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}

此函数由动画自己调用,触发时间为动画生成第一个动画帧之前。如果动画有一个startDelay,则在开始动画延迟结束后才会调用此方法。

3.2.8 ValueAnimator setDuration( long duration):设置动画执行时间,默认300ms;
long getDuration():获取动画的执行时间

3.2.9 设置/获取当前动画执行到的位置
float getAnimatedFraction()获取当前动画已经执行到的位置,返回归一化的位置0~1
setCurrentPlayTime设置动画当前的执行时间,此时间可以是0~动画总执行时长(duration * repeatcount)之间的任意值。如果动画没有start(),则动画停留在当前设置的时间所在的帧,不接着播放;如果动画已经开始执行了,则动画接着播放,也就是类似电影跳到某个位置播放的效果,可以是快进到某个位置执行动画也可以是回退到某个位置重播。
这个接口其实还有个非常好的用法,定义动画后不调用start()方法,在手指触摸屏幕时不断的计算合适的playTime,调用此方法,可以营造出复杂的手指跟随效果。
public void setCurrentPlayTime(long playTime) {
float fraction = mUnscaledDuration > 0 ? (float) playTime / mUnscaledDuration : 1;
setCurrentFraction(fraction);
}

//设置动画当前的执行位置(执行位置与执行时间线性一一对应)
//fraction值是基于动画时长的0~1取值,超出1的考虑动画循环执行情况取模
public void setCurrentFraction(float fraction) {
initAnimation();//此方法可能是第一帧,此处要考虑初始化动画
if (fraction < 0) {//fraction保证>=0
fraction = 0 ;
}
int iteration = (int) fraction; //当前执行动画的轮数
if (fraction == 1) {
//动画刚刚走完一轮,到最后一帧,此时动画的轮数应为1,表现为mCurrentIteration应等于0
iteration -= 1 ;
} else if (fraction > 1) {
//动画还没执行完所有轮数
if (iteration < (mRepeatCount + 1) || mRepeatCount == INFINITE ) {
if (mRepeatMode == REVERSE) {
//动画reverse时,奇数轮动画从结束帧向开始帧执行
mPlayingBackwards = (iteration % 2) != 0 ;
}
//去掉重复执行,动画真实的位置
fraction = fraction % 1f;
//在这种情况下,动画应该完全已经执行了iteration轮,此时正在iteration+1轮内执行,此处不用-1
} else {
//动画已到达最后一帧,轮数iteration要-1
fraction = 1;
iteration -= 1;
}
} else {
//还在第一轮执行,第一轮本应是顺序执行,但如果当前动画被调用了reverse,则此处是逆序执行
mPlayingBackwards = mReversing;
}
//mCurrentIteration动画执行轮数需要与mRepeatCount对比,mRepeatCount从0开始,所以mCurrentIteration也从0开始
mCurrentIteration = iteration;

//重新计算动画开始时间,也就是当前时间往前倒推,这样后面执行时动画开始时间就改变了,动画可以接着向下执行
long seekTime = (long) ( mDuration * fraction);
long currentTime = AnimationUtils.currentAnimationTimeMillis();
mStartTime = currentTime - seekTime;
mStartTimeCommitted = true; // do not allow start time to be compensated for jank

//当前动画不再执行状态,记录标志位
if (mPlayingState != RUNNING) {
mSeekFraction = fraction;
mPlayingState = SEEKED;
}
//如果动画正在逆序从结束帧到初始帧执行,则调整归一化的fraction
if (mPlayingBackwards) {
fraction = 1f - fraction;
}
//动画运行到当前时间指定的帧,见3.3.6
animateValue(fraction);
}


3.2.10 long getCurrentPlayTime():获取动画当前的执行时间

3.2.11 long getStartDelay():获取动画从start()调用后到真正执行第一帧还需要等待的时间
void setStartDelay( long startDelay):设置动画执行延迟时间

3.2.12 static long getFrameDelay():返回帧刷新延迟,就是两次插值帧之间的时间,返回的是Choreographer.getFrameDelay(),所有动画都共用同一个刷新延迟,因为所有动画共用同一个定时循环(Timing Loop)。此值默认是10ms,但具体值视当时的系统负载而定。
static void setFrameDelay( long frameDelay):设置帧刷新延迟

3.2.13 Object getAnimatedValue()/Object getAnimatedValue(String propertyName):返回ValueAnimator计算的最近一帧的指定属性的插值属性值;对于第一个接口,因为没传参数,如果设置了多个属性同时插值,则返回第一个属性的插值;此值设计用在AnimatorUpdateListener.onAnimationUpdate()内,ValueAnimator计算完新一帧的数据后就会调用此方法,我们应该在此方法的实现内调用getAnimatedValue()获取最新的属性值,并设置到对象上。

3.2.14 void setRepeatCount( int value):设置动画的重复执行次数,0不重复执行,1重复执行1次,总共执行2次,ValueAnimator.INFINITE动画无限循环。
int getRepeatCount():获取动画重复执行次数

3.2.15 void setRepeatMode( int value):动画执行到一轮结束时应该如何执行下一轮。两个值:RESTART从头开始下一轮执行,REVERSE从尾向前执行下一轮;默认是RESTART。
int getRepeatMode():获取当前设置的重复模式。

3.2.16 void addUpdateListener(AnimatorUpdateListener listener):添加了此监听器后,动画执行到新的一帧时,会收到回调。我们可以在此回调内设置对象的属性,或者刷新界面。
void removeAllUpdateListeners():移除所有的帧刷新监听器。
void removeUpdateListener(AnimatorUpdateListener listener):移除一个特定的帧刷新监听器。

3.2.17 void setInterpolator(TimeInterpolator value):设置动画的时间插值器,默认是加减速插值器,value为null则设置线性插值器。
TimeInterpolator getInterpolator():获取动画的时间插值器。

3.2.18 void setEvaluator(TypeEvaluator value):设置属性插值的计算器,TypeEvaluator告知ValueAnimator如何插值Object,我们也可以为int和float提供非默认的计算器。类似ArgbEvaluator,计算代表Color的int。

3.2.19 void start():开始动画,调用start(false)
//playBackwards值true表示动画逆序执行,false表示顺序执行
private void start(boolean playBackwards) {
if (Looper.myLooper() == null) {
//属性动画是支持非主线程调用的,但前提是线程有Looper
throw new AndroidRuntimeException("Animators may only be run on Looper threads");
}
//记录标志位
mReversing = playBackwards;
mPlayingBackwards = playBackwards;
if (playBackwards && mSeekFraction != - 1) {
//逆序执行且曾经有过seek动作(先setCurrentFraction再start)
if (mSeekFraction == 0 && mCurrentIteration == 0 ) {
// special case: reversing from seek-to-0 should act as if not seeked at all
//如果seek到动画初始位置,则新的seek位置还保持在0
mSeekFraction = 0;
} else if (mRepeatCount == INFINITE) {
//无限重复时新的seek位置,reverse模式下,fraction保持在0~1之间即可
mSeekFraction = 1 - (mSeekFraction % 1);
} else {
//有限循环时,新位置为总的Fraction(1 + mRepeatCount)减去当前已经执行到的位置(mCurrentIteration + mSeekFraction)
mSeekFraction = 1 + mRepeatCount - (mCurrentIteration + mSeekFraction);
}
//以上三个分支涵盖了如果seek到某个位置,而动画逆序执行时,动画应当执行到的位置
//假设动画逆序执行,则seek到0.8的位置,其实是从0.2执行到0.
mCurrentIteration = (int) mSeekFraction;
mSeekFraction = mSeekFraction % 1;
}
if (mCurrentIteration > 0 && mRepeatMode == REVERSE &&
(mCurrentIteration < (mRepeatCount + 1) || mRepeatCount == INFINITE)) {
// if we were seeked to some other iteration in a reversing animator,
// figure out the correct direction to start playing based on the iteration
//reverse模式下,如果当前动画已经执行过超过1轮,而且动画还需要继续执行,则重新计算动画方向
if (playBackwards) {
//逆序执行模式下,奇数轮执行方向是顺序的(mPlayingBackwards=false),偶数轮执行方向是逆序的
mPlayingBackwards = (mCurrentIteration % 2 ) == 0 ;
} else {
//顺序执行模式下,奇数轮执行的方向是逆序的,比如轮数0顺序执行,轮数1逆序执行(mPlayingBackwards=true )
mPlayingBackwards = (mCurrentIteration % 2 ) != 0 ;
}
}
int prevPlayingState = mPlayingState;
//STOPPED动画还没开始运行, RUNNING正在运行, SEEKED动画快进到了某个位置
mPlayingState = STOPPED;
mStarted = true;//动画已开始
mStartedDelay = false;//动画还没开始经历StartedDelay阶段
mPaused = false ;//动画不属于暂停状态
updateScaledDuration(); // in case the scale factor has changed since creation time
//在当前线程的ThreadLocal内获取/建立AnimationHandler对象
AnimationHandler animationHandler = getOrCreateAnimationHandler();
//在当前线程对应的AnimationHandler中增加当前动画的引用
animationHandler.mPendingAnimations .add(this);
if (mStartDelay == 0) {
// This sets the initial value of the animation, prior to actually starting it running
if (prevPlayingState != SEEKED) {
//动画没有startDelay时,外部也没有seek动作,这时动画立刻开始运行,从时间0开始
setCurrentPlayTime(0);
}
//动画此时状态还保持STOPPED,在动画开始第一帧时状态改为RUNNING
mPlayingState = STOPPED;
//没有开始延迟的动画在start后就处于running状态
mRunning = true;
//通知外部动画开始
notifyStartListeners();
}
//开始动画插值计算(AnimationHandler在指定时间触发属性值插值计算)
animationHandler.start();
}


3.2.20 取消动画执行
public void cancel() {
// Only cancel if the animation is actually running or has been started and is about
// to run
//仅当动画start后才能cancel
AnimationHandler handler = getOrCreateAnimationHandler();//获取AnimationHandler
if (mPlayingState != STOPPED
|| handler.mPendingAnimations .contains(this)
|| handler.mDelayedAnims .contains(this)) {
//动画当前动画正在运行
// Only notify listeners if the animator has actually started
if ((mStarted || mRunning) && mListeners != null) {
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
//当前动画在start之后,第一帧之前,通知外部动画开始,
notifyStartListeners();
}
//通知外部动画取消动作
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
for (AnimatorListener listener : tmpListeners) {
listener.onAnimationCancel(this );
}
}
endAnimation(handler);
}
}
protected void endAnimation(AnimationHandler handler) {
//从当前线程的AnimationHandler移除当前的动画
handler.mAnimations.remove(this);
handler.mPendingAnimations .remove(this);
handler.mDelayedAnims .remove(this);
//动画结束的标志位
mPlayingState = STOPPED;
mPaused = false ;
if ((mStarted || mRunning) && mListeners != null ) {
//动画已开始
if (!mRunning) {
// If it's not yet running, then start listeners weren't called. Call them now.
//考虑动画开始但未运行的情况
notifyStartListeners();
}
//通知动画结束,从代码可以看到,外部cancel的调用会引起onAnimationCancel和onAnimationEnd的回调
ArrayList<AnimatorListener> tmpListeners =
(ArrayList<AnimatorListener>) mListeners.clone();
int numListeners = tmpListeners.size();
for (int i = 0; i < numListeners; ++i) {
tmpListeners.get(i).onAnimationEnd(this );
}
}
//重置动画的各种标志位
mRunning = false;//动画是否已经运行
mStarted = false;//动画是否已经开始
mStartListenersCalled = false;//是否已经回调国onAnimationStart事件
mPlayingBackwards = false;//在当前执行轮内,动画是否逆序执行
mReversing = false;//动画是否在逆序执行,调用reverse后此值为true
mCurrentIteration = 0 ;//当前动画已经执行的轮数
}


3.2.21 结束动画执行
public void end() {
//获取当前线程的AnimationHandler
AnimationHandler handler = getOrCreateAnimationHandler();
if (!handler.mAnimations.contains( this) && !handler.mPendingAnimations .contains(this)) {
// Special case if the animation has not yet started; get it ready for ending
//如果动画还没schedule到handler内,则动画还没开始运行,此时开始执行动画,为后面的结束动画做准备
//标识一个带startDelay的动画是否开始经历延迟阶段,这里动画都快要结束了,这里就置false了
mStartedDelay = false;
startAnimation(handler);//动画还没开始,我们启动动画,随后再结束动画,见下面的代码
mStarted = true;
} else if (!mInitialized) {
initAnimation();//动画还没初始化,我们初始化它
}
//让动画执行到最后一帧,见3.3.6
animateValue(mPlayingBackwards ? 0f : 1f );
//结束动画,具体见上面的代码
endAnimation(handler);
}
private void startAnimation(AnimationHandler handler) {
initAnimation();//如果动画没初始化,初始化动画
handler.mAnimations .add(this);//把动画添加到AnimationHandler
if (mStartDelay > 0 && mListeners != null) {
// Listeners were already notified in start() if startDelay is 0; this is
// just for delayed animations
//startDelay为0则start时已经调用过onAnimationStart,只有>0才需要在此处调用
notifyStartListeners();
}
}


3.2.22 void pause():暂停动画执行;
void resume()恢复动画执行。
这两个动作很简单,其实就是置标志位(mPaused/mResumed)和对外通知事件(onAnimationPause/onAnimationResume)

3.2.23 boolean isRunning():返回动画是否正在运行,从动画第一帧开始算running状态,如果动画有动画开始延迟startDelay,在startDelay期间,此值返回false。
boolean isStarted():返回动画是否已经开始,从start()开始,此值就返回true

3.2.24 动画reverse:让动画从当前位置逆序运行
boolean canReverse():动画是否支持reverse操作
public void reverse() {
//动画方向逆转
mPlayingBackwards = !mPlayingBackwards;
if (mPlayingState == RUNNING) {
//动画正在运行
long currentTime = AnimationUtils.currentAnimationTimeMillis();
long currentPlayTime = currentTime - mStartTime;
long timeLeft = mDuration - currentPlayTime;
//因为方向逆转,动画开始事件mStartTime调整为另一个方向的开始时间,认为动画已经执行了timeLeft,剩余currPlayTime需要执行
mStartTime = currentTime - timeLeft;
mStartTimeCommitted = true; // do not allow start time to be compensated for jank
//修改reverse的标志位
mReversing = !mReversing;
} else if (mStarted) {
//动画start了但未运行,则当前处于startDelay内,reverse的最终状态就是当前的状态,所以直接结束动画
//注意此处所有的状态的复位了,mReversing = false
end();
} else {
//动画不在start,也没running,此时reverse直接从当前位置逆序执行动画
start(true);
}
}


3.2.25 ValueAnimator clone():ValueAnimator克隆出的是一个初始状态的新ValueAnimator,其内部持有所有的AnimatorUpdateListener及新的PropertyValueHolder集合

3.2.26 与线程相关的几个方法:
int getCurrentAnimationsCount():静态方法,获取当前线程内正在执行的属性动画个数。
void clearAllAnimations():静态方法,清除当前线程内正在执行或者将要执行的所有属性动画。
AnimationHandler getOrCreateAnimationHandler():静态方法,获取AnimationHandler,没有就创建一个handler

3.2.27 通知动画帧刷新接口
public static interface AnimatorUpdateListener {
/**动画开始进行新一帧的刷新时的回调*/
void onAnimationUpdate(ValueAnimator animation);
}


3.3 定时策略AnimationHandler
3.3.1 主要代码
protected static class AnimationHandler {
// 单个进程内所有活跃的属性动画的列表,每个线程只持有一个AnimationHandler(ThreadLocal机制)
protected final ArrayList<ValueAnimator> mAnimations = new ArrayList<ValueAnimator>();

// 避免mAnimations列表的ConcurrentModification,在doAnimationFrame()方法中使用
private final ArrayList<ValueAnimator> mTmpAnimations = new ArrayList<ValueAnimator>();

// 下一个动画帧时将要start的动画列表
/** @hide */
protected final ArrayList<ValueAnimator> mPendingAnimations = new ArrayList<ValueAnimator>();

/**
* 在处理动画开始/结束时避免冲突而使用的集合
*/
protected final ArrayList<ValueAnimator> mDelayedAnims = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mEndingAnims = new ArrayList<ValueAnimator>();
private final ArrayList<ValueAnimator> mReadyAnims = new ArrayList<ValueAnimator>();

//形成逐帧插值效果的定时器,单例
private final Choreographer mChoreographer;
//是否已经在Choreographer内规划了下一帧的动画插值事件
private boolean mAnimationScheduled;
//上一次插值的时间
private long mLastFrameTime;

private AnimationHandler() {
mChoreographer = Choreographer.getInstance();
}

//开始动画执行,本质就是在Choreographer内规划下一帧的动画插值事件
public void start() {
scheduleAnimation();
}

//开始一次新的帧插值计算及界面刷新
void doAnimationFrame(long frameTime) {
mLastFrameTime = frameTime;//记录最近的一次插值时间

// mPendingAnimations 内持有所有准备start的animation,我们需要将其移动到mAnimations和mDelayedAmiations列表内,
// 考虑到startAnimation可能会导致pendinglist重新不为空(比如一个动画start触发另一个动画的start),我们需要循环直到mPendingAnimations为空
while (mPendingAnimations.size() > 0) {
//克隆列表防止ConcurrentModifyException
ArrayList<ValueAnimator> pendingCopy =
(ArrayList<ValueAnimator>) mPendingAnimations.clone();
mPendingAnimations.clear();
int count = pendingCopy.size();
for (int i = 0; i < count; ++i) {
ValueAnimator anim = pendingCopy.get(i);
// 如果动画startDelay为0,则调用animator的startAnimation开始动画,在此方法中,animator会被add到mAnimations列表内
if (anim.mStartDelay == 0) {
anim.startAnimation(this);
} else {
//有延迟则放入延迟执行队列内
mDelayedAnims.add(anim);
}
}
}

// 现在,开始处理mDelayedAnims内的延迟动画,如果延迟时间已经到达,则将其添加到mReadyAnims
int numDelayedAnims = mDelayedAnims.size();
for (int i = 0; i < numDelayedAnims; ++i) {
ValueAnimator anim = mDelayedAnims.get(i);
if (anim.delayedAnimationFrame(frameTime)) {//见3.3.2
mReadyAnims.add(anim);
}
}
//对所有mReadyAnims内的动画,逐个start动画,并将其从mDelayedAnims内移除
int numReadyAnims = mReadyAnims.size();
if (numReadyAnims > 0) {
for (int i = 0; i < numReadyAnims; ++i) {
ValueAnimator anim = mReadyAnims.get(i);
anim.startAnimation(this);
anim.mRunning = true;
mDelayedAnims.remove(anim);
}
mReadyAnims.clear();
}

// 处理完成mPendingAnims和mDelayedAnims后,已经重新计算了所有当前活跃的动画,也就是mAnimations列表,
// 此时需要对每个活跃的动画开始帧插值与计算,animationFrame()返回true表示动画已经结束了
//个人感觉,这个内部类应该经手了好几个开发人员,此处mTmpAnimations可直接使用mAnimations.clone替代,上面那个mReadyAnims也没必要用成员变量,大家姑且看之
int numAnims = mAnimations.size();
for (int i = 0; i < numAnims; ++i) {
mTmpAnimations.add(mAnimations.get(i));
}
for (int i = 0; i < numAnims; ++i) {
ValueAnimator anim = mTmpAnimations.get(i);
//执行动画帧插值动作,刷新新的动画帧,doAnimationFrame返回true表示动画需要结束了,将其加入mEndingAnimas,见3.3.4
if (mAnimations.contains(anim) && anim.doAnimationFrame(frameTime)) {
mEndingAnims.add(anim);
}
}
mTmpAnimations.clear();
if (mEndingAnims.size() > 0) {
for (int i = 0; i < mEndingAnims.size(); ++i) {
//结束需要结束的动画运行
mEndingAnims.get(i).endAnimation(this);
}
mEndingAnims.clear();
}

// 对正在执行的动画规划一个开始时间调整;
//如果绘制时间很快,则此处补偿的时间的时间就越短;
//举例来说,如果动画Handler在时间为200ms时开始执行第一帧动画,在203ms执行此代码,在208ms处执行mCommit.run(),
//则补偿时间为8ms,其中前方代码计算耗时3ms,绘制耗时5ms
mChoreographer.postCallback(Choreographer.CALLBACK_COMMIT, mCommit, null);

// 如果还有动画未执行完成,则规划下一次回调
if (!mAnimations.isEmpty() || !mDelayedAnims.isEmpty()) {
scheduleAnimation();
}
}

void commitAnimationFrame(long frameTime) {
final long adjustment = frameTime - mLastFrameTime;
final int numAnims = mAnimations.size();
for (int i = 0; i < numAnims; ++i) {
//对正在执行的动画的开始时间执行一个开始时间到绘制完成时间之间差异的补偿;见3.3.3
mAnimations.get(i).commitAnimationFrame(adjustment);
}
}

private void scheduleAnimation() {
if (!mAnimationScheduled) {
//如果下一帧的动作mAnimate尚未规划,则在Choreographer内注册之
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}

// 由Choreographer定时器调用
final Runnable mAnimate = new Runnable() {
@Override
public void run() {
//当动画帧延迟时间到达时,开始执行新的一帧,由定时器触发
mAnimationScheduled = false;//本次规划已完成
doAnimationFrame(mChoreographer.getFrameTime());//进行动画插值计算,计算依据是当前时间
}
};

// 由Choreographer定时器调用
final Runnable mCommit = new Runnable() {
@Override
public void run() {
//对正在执行的动画规划一个开始时间微调
commitAnimationFrame(mChoreographer.getFrameTime());
}
};
}


3.3.2 delayedAnimationFrame(long currentTime):对设置了startDelay的动画进行处理,判断是否已经超出了startDelay范围从而可以执行了
/**
* 对当前正处于startDelay阶段动画的进行处理,返回值表示动画是否需要被唤醒并被放入待执行队列。
*
* @param currentTime 当前动画时间,用于判断当前动画是否已经超出了startDelay
* @return 返回true表示动画已经超出了startDelay,应被放入待执行队列。
*/
private boolean delayedAnimationFrame(long currentTime) {
if (!mStartedDelay) {
//尚未执行动画延迟,则开始进入执行动画延迟状态,记录开始进入动画延迟的开始时间
mStartedDelay = true;
mDelayStartTime = currentTime;
}
if (mPaused) {
if (mPauseTime < 0) {
mPauseTime = currentTime;//记录暂停时间
}
//动画被暂停了,则不能加入活跃动画列表
return false;
} else if (mResumed) {
mResumed = false;//消费了resume事件
if (mPauseTime > 0) {
// 重新计算动画延迟的开始时间,去掉暂停到恢复中间的时间
mDelayStartTime += (currentTime - mPauseTime);
}
}
//计算动画在startDelay阶段真正经历的时间
long deltaTime = currentTime - mDelayStartTime;
if (deltaTime > mStartDelay) {
// 动画已经超出了延迟时间,可以移入活跃队列真正执行了
mStartTime = mDelayStartTime + mStartDelay;
mStartTimeCommitted = true;
mPlayingState = RUNNING;
return true;
}
//动画还在startDelay时间段内
return false;
}


3.3.3 commitAnimationFrame(long adjustment):对动画开始时间进行补偿
/**对动画开始时间增加adjustment作为动画第一次执行与动画开始绘制之间的补偿。*/
void commitAnimationFrame(long adjustment) {
if (!mStartTimeCommitted) {
mStartTimeCommitted = true;
if (mPlayingState == RUNNING && adjustment > 0) {
mStartTime += adjustment;
}
}
}


3.3.4 doAnimationFrame(long frameTime):开始执行动画的某一帧插值与界面刷新
/**
* 处理动画的某一帧,可能需要调整startTime,返回true表示动画需要结束了
*/
final boolean doAnimationFrame(long frameTime) {
if (mPlayingState == STOPPED) {
//当前动画状态是STOPPED
mPlayingState = RUNNING;
if (mSeekFraction < 0) {
//没有调用seekTo
mStartTime = frameTime;
} else {
//如果调用了seekTo,则重新计算StartTime来应用seek的时间
long seekTime = (long) (mDuration * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // 支持动画开始与动画绘制的差距补偿
}
if (mPaused) {//动画已经pause了则不处理
if (mPauseTime < 0) {
mPauseTime = frameTime;
}
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// 重新计算startTime,去掉pause到resume的间隔
mStartTime += (frameTime - mPauseTime);
mStartTimeCommitted = false; // 支持动画开始与动画绘制的差距补偿
}
}
// frameTime可能位于startTime之前,这种情况很少见,只有往回seek才可能发生
final long currentTime = Math.max(frameTime, mStartTime);
return animationFrame(currentTime);//见3.3.5
}


3.3.5 animationFrame(long currentTime):由Handler触发计算动画属性的新插值。
/**根据当前时间计算新一帧的属性插值。currentTime参数是由Handler发出的时序脉冲,指出当前动画已经执行的时间。返回true表示动画需要结束了。*/
boolean animationFrame(long currentTime) {
boolean done = false;
switch (mPlayingState) {
case RUNNING:
case SEEKED:
//计算新的动画执行耗时(归一化,但可能大于1)
float fraction = mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;
if (mDuration == 0 && mRepeatCount != INFINITE) {
// Skip to the end
mCurrentIteration = mRepeatCount;
if (!mReversing) {
mPlayingBackwards = false;
}
}
if (fraction >= 1f) {
if (mCurrentIteration < mRepeatCount || mRepeatCount == INFINITE) {
//开始了新的动画重复过程
if (mListeners != null) {
int numListeners = mListeners.size();
for (int i = 0; i < numListeners; ++i) {
mListeners.get(i).onAnimationRepeat(this);
}
}
//重新设置动画的执行方向
if (mRepeatMode == REVERSE) {
mPlayingBackwards = !mPlayingBackwards;
}
//动画执行轮数+1
mCurrentIteration += (int) fraction;
//计算0~1之间的归一化耗时
fraction = fraction % 1f;
//更新startTime
mStartTime += mDuration;
// Note: We do not need to update the value of mStartTimeCommitted here
// since we just added a duration offset.
} else {
//已执行完成所有repeatCount轮
done = true;
fraction = Math.min(fraction, 1.0f);
}
}
//计算你想执行的fraction
if (mPlayingBackwards) {
fraction = 1f - fraction;
}
//根据归一化时间计算插值,见3.3.6
animateValue(fraction);
break;
}

return done;
}


3.3.6 animateValue(float fraction):真正的对动画进行属性插值,参数fraction介于0~1之间
/**
* 此方法在每次动画帧刷新时都会调用,通过将动画已执行的时间(归一化后的时间)转化为插值时间,再通过Evaluator计算属性插值。
* 在Animation帧刷新时和end()时方法都会被调用。
*
* @param fraction 动画已经执行的时间
*/
@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);
}
}
}


四、总结
本文分析了属性动画的抽象类Animator和属性动画最核心的类ValueAnimator的源码,在整个属性动画框架中,虽然我们用的最多的是ObjectAnimator,但ValueAnimator才是最基本最核心的基础,所有的属性动画效果与派生都是ValueAnimator的延展,比如ObjectAnimator就是在ValueAniamtor基础上增加了对象的属性读取/设置能力封装的应用类。
ValueAnimator的核心在于它管理了一个属性集合,然后在当前线程内存储一个对象AnimationHandler来不断的形成时序脉冲(通过Choreographer机制),在每次脉冲到达时,计算新的插值属性值,并通过AnimatorUpdateListener的onAnimationUpdate()对外通知属性值的变化,外部接收到属性值变化的通知后,做一些对应的操作来设置属性值或者刷新界面等,形成动画视觉效果。
整体来讲,ValueAnimator的封装设计还是非常不错的,思想也比较新颖,但是部分代码的可读性比较差,需要花比较长的时间才能搞懂作者想要干嘛,这也是我这篇文章缺席了好久的一个原因。当然,最主要原因还是懒癌发作了,哈哈哈。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息