热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

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

一、整体结构 本篇文章主要总结属性动画相关的源码的分析,属性动画的使用见上一篇文章,属性动画的源码比较多,整篇文章会分作两篇小文章: Anima
一、整体结构

本篇文章主要总结属性动画相关的源码的分析,属性动画的使用见上一篇文章,属性动画的源码比较多,整篇文章会分作两篇小文章:

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 mListeners = null;//对外通知动画暂停/恢复的监听器列表ArrayList mPauseListeners = null ;//动画当前是否处于暂停状态boolean mPaused = false ;


2.2 关键方法

//开始动画执行,如果动画有开始延迟(startDelay),则动画在延迟之后开始运行(running),动画初始值在第一帧开始设置,随后调用onAnimationStart//调用此方法后,当前动画就在调用线程上运行,线程需要一个Looper,如果动画引起View变化,则应在UI线程调用start,或在UI线程处理onAnimationUpdatepublic 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);//设置动画执行一次的耗时,单位mspublic abstract Animator setDuration(long duration);//获取动画执行一次的耗时。public abstract long getDuration();//设置动画的时间插值器,也就是动画的变化速率,默认是AccelerateDecelerateInterpolatorpublic 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);//移除一个已经设置的AnimatorListenerpublic void removeListener(AnimatorListener listener);//获取已经设置的AnimatorListener的集合。public ArrayList getListeners();//添加一个暂停/恢复事件监听器,AnimatorPauseListener通知动画的暂停/恢复事件。public void addPauseListener(AnimatorPauseListener listener);//移除一个已经设置的AnimatorPauseListenerpublic void removePauseListener(AnimatorPauseListener listener);//移除所有已经设置的监听器,包括AnimatorListener和AnimatorPauseListenerpublic 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

public static ValueAnimator ofFloat(float... values);





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 }


此函数由动画自己调用,触发时间为动画生成第一个动画帧之前。如果动画有一个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();//此方法可能是第一帧&#xff0c;此处要考虑初始化动画if (fraction <0) {//fraction保证>&#61;0fraction &#61; 0 ;}int iteration &#61; (int) fraction; //当前执行动画的轮数if (fraction &#61;&#61; 1) {//动画刚刚走完一轮&#xff0c;到最后一帧&#xff0c;此时动画的轮数应为1&#xff0c;表现为mCurrentIteration应等于0iteration -&#61; 1 ;} else if (fraction > 1) {//动画还没执行完所有轮数if (iteration <(mRepeatCount &#43; 1) || mRepeatCount &#61;&#61; INFINITE ) {if (mRepeatMode &#61;&#61; REVERSE) {//动画reverse时&#xff0c;奇数轮动画从结束帧向开始帧执行mPlayingBackwards &#61; (iteration % 2) !&#61; 0 ;}//去掉重复执行&#xff0c;动画真实的位置fraction &#61; fraction % 1f;//在这种情况下&#xff0c;动画应该完全已经执行了iteration轮&#xff0c;此时正在iteration&#43;1轮内执行&#xff0c;此处不用-1} else {//动画已到达最后一帧&#xff0c;轮数iteration要-1fraction &#61; 1;iteration -&#61; 1;}} else {//还在第一轮执行&#xff0c;第一轮本应是顺序执行&#xff0c;但如果当前动画被调用了reverse&#xff0c;则此处是逆序执行mPlayingBackwards &#61; mReversing;}//mCurrentIteration动画执行轮数需要与mRepeatCount对比&#xff0c;mRepeatCount从0开始&#xff0c;所以mCurrentIteration也从0开始mCurrentIteration &#61; iteration;//重新计算动画开始时间&#xff0c;也就是当前时间往前倒推&#xff0c;这样后面执行时动画开始时间就改变了&#xff0c;动画可以接着向下执行long seekTime &#61; (long) ( mDuration * fraction);long currentTime &#61; AnimationUtils.currentAnimationTimeMillis();mStartTime &#61; currentTime - seekTime;mStartTimeCommitted &#61; true; // do not allow start time to be compensated for jank//当前动画不再执行状态&#xff0c;记录标志位if (mPlayingState !&#61; RUNNING) {mSeekFraction &#61; fraction;mPlayingState &#61; SEEKED;}//如果动画正在逆序从结束帧到初始帧执行&#xff0c;则调整归一化的fractionif (mPlayingBackwards) {fraction &#61; 1f - fraction;}//动画运行到当前时间指定的帧&#xff0c;见3.3.6animateValue(fraction);
}



3.2.10 long getCurrentPlayTime()&#xff1a;获取动画当前的执行时间




3.2.11 long getStartDelay()&#xff1a;获取动画从start()调用后到真正执行第一帧还需要等待的时间

void setStartDelay( long startDelay)&#xff1a;设置动画执行延迟时间




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

static void setFrameDelay( long frameDelay)&#xff1a;设置帧刷新延迟




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




3.2.14 void setRepeatCount( int value)&#xff1a;设置动画的重复执行次数&#xff0c;0不重复执行&#xff0c;1重复执行1次&#xff0c;总共执行2次&#xff0c;ValueAnimator.INFINITE动画无限循环。

int getRepeatCount()&#xff1a;获取动画重复执行次数




3.2.15 void setRepeatMode( int value)&#xff1a;动画执行到一轮结束时应该如何执行下一轮。两个值&#xff1a;RESTART从头开始下一轮执行&#xff0c;REVERSE从尾向前执行下一轮&#xff1b;默认是RESTART。

int getRepeatMode()&#xff1a;获取当前设置的重复模式。




3.2.16 void addUpdateListener(AnimatorUpdateListener listener)&#xff1a;添加了此监听器后&#xff0c;动画执行到新的一帧时&#xff0c;会收到回调。我们可以在此回调内设置对象的属性&#xff0c;或者刷新界面。

void removeAllUpdateListeners()&#xff1a;移除所有的帧刷新监听器。

void removeUpdateListener(AnimatorUpdateListener listener)&#xff1a;移除一个特定的帧刷新监听器。




3.2.17 void setInterpolator(TimeInterpolator value)&#xff1a;设置动画的时间插值器&#xff0c;默认是加减速插值器&#xff0c;value为null则设置线性插值器。

TimeInterpolator getInterpolator()&#xff1a;获取动画的时间插值器。




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




3.2.19 void start()&#xff1a;开始动画&#xff0c;调用start(false)

//playBackwards值true表示动画逆序执行&#xff0c;false表示顺序执行
private void start(boolean playBackwards) {if (Looper.myLooper() &#61;&#61; null) {//属性动画是支持非主线程调用的&#xff0c;但前提是线程有Looperthrow new AndroidRuntimeException("Animators may only be run on Looper threads");}//记录标志位mReversing &#61; playBackwards;mPlayingBackwards &#61; playBackwards;if (playBackwards && mSeekFraction !&#61; - 1) {//逆序执行且曾经有过seek动作(先setCurrentFraction再start)if (mSeekFraction &#61;&#61; 0 && mCurrentIteration &#61;&#61; 0 ) {// special case: reversing from seek-to-0 should act as if not seeked at all//如果seek到动画初始位置&#xff0c;则新的seek位置还保持在0mSeekFraction &#61; 0;} else if (mRepeatCount &#61;&#61; INFINITE) {//无限重复时新的seek位置&#xff0c;reverse模式下&#xff0c;fraction保持在0&#xff5e;1之间即可mSeekFraction &#61; 1 - (mSeekFraction % 1);} else {//有限循环时&#xff0c;新位置为总的Fraction&#xff08;1 &#43; mRepeatCount&#xff09;减去当前已经执行到的位置&#xff08;mCurrentIteration &#43; mSeekFraction&#xff09;mSeekFraction &#61; 1 &#43; mRepeatCount - (mCurrentIteration &#43; mSeekFraction);}//以上三个分支涵盖了如果seek到某个位置&#xff0c;而动画逆序执行时&#xff0c;动画应当执行到的位置//假设动画逆序执行&#xff0c;则seek到0.8的位置&#xff0c;其实是从0.2执行到0.mCurrentIteration &#61; (int) mSeekFraction;mSeekFraction &#61; mSeekFraction % 1;}if (mCurrentIteration > 0 && mRepeatMode &#61;&#61; REVERSE &&(mCurrentIteration <(mRepeatCount &#43; 1) || mRepeatCount &#61;&#61; 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模式下&#xff0c;如果当前动画已经执行过超过1轮&#xff0c;而且动画还需要继续执行&#xff0c;则重新计算动画方向if (playBackwards) {//逆序执行模式下&#xff0c;奇数轮执行方向是顺序的(mPlayingBackwards&#61;false)&#xff0c;偶数轮执行方向是逆序的mPlayingBackwards &#61; (mCurrentIteration % 2 ) &#61;&#61; 0 ;} else {//顺序执行模式下&#xff0c;奇数轮执行的方向是逆序的&#xff0c;比如轮数0顺序执行&#xff0c;轮数1逆序执行(mPlayingBackwards&#61;true )mPlayingBackwards &#61; (mCurrentIteration % 2 ) !&#61; 0 ;}}int prevPlayingState &#61; mPlayingState;//STOPPED动画还没开始运行, RUNNING正在运行, SEEKED动画快进到了某个位置mPlayingState &#61; STOPPED;mStarted &#61; true;//动画已开始mStartedDelay &#61; false;//动画还没开始经历StartedDelay阶段mPaused &#61; false ;//动画不属于暂停状态updateScaledDuration(); // in case the scale factor has changed since creation time//在当前线程的ThreadLocal内获取/建立AnimationHandler对象AnimationHandler animationHandler &#61; getOrCreateAnimationHandler();//在当前线程对应的AnimationHandler中增加当前动画的引用animationHandler.mPendingAnimations .add(this);if (mStartDelay &#61;&#61; 0) {// This sets the initial value of the animation, prior to actually starting it runningif (prevPlayingState !&#61; SEEKED) {//动画没有startDelay时&#xff0c;外部也没有seek动作&#xff0c;这时动画立刻开始运行&#xff0c;从时间0开始setCurrentPlayTime(0);}//动画此时状态还保持STOPPED&#xff0c;在动画开始第一帧时状态改为RUNNINGmPlayingState &#61; STOPPED;//没有开始延迟的动画在start后就处于running状态mRunning &#61; true;//通知外部动画开始notifyStartListeners();}//开始动画插值计算&#xff08;AnimationHandler在指定时间触发属性值插值计算&#xff09;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后才能cancelAnimationHandler handler &#61; getOrCreateAnimationHandler();//获取AnimationHandlerif (mPlayingState !&#61; STOPPED|| handler.mPendingAnimations .contains(this)|| handler.mDelayedAnims .contains(this)) {//动画当前动画正在运行// Only notify listeners if the animator has actually startedif ((mStarted || mRunning) && mListeners !&#61; null) {if (!mRunning) {// If it&#39;s not yet running, then start listeners weren&#39;t called. Call them now.//当前动画在start之后&#xff0c;第一帧之前&#xff0c;通知外部动画开始&#xff0c;notifyStartListeners();}//通知外部动画取消动作ArrayList tmpListeners &#61;(ArrayList) 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 &#61; STOPPED;mPaused &#61; false ;if ((mStarted || mRunning) && mListeners !&#61; null ) {//动画已开始if (!mRunning) {// If it&#39;s not yet running, then start listeners weren&#39;t called. Call them now.//考虑动画开始但未运行的情况notifyStartListeners();}//通知动画结束&#xff0c;从代码可以看到&#xff0c;外部cancel的调用会引起onAnimationCancel和onAnimationEnd的回调ArrayList tmpListeners &#61;(ArrayList) mListeners.clone();int numListeners &#61; tmpListeners.size();for (int i &#61; 0; i }



3.2.21 结束动画执行

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



3.2.22 void pause()&#xff1a;暂停动画执行&#xff1b;

void resume()恢复动画执行。

这两个动作很简单&#xff0c;其实就是置标志位&#xff08;mPaused/mResumed&#xff09;和对外通知事件&#xff08;onAnimationPause/onAnimationResume&#xff09;




3.2.23 boolean isRunning()&#xff1a;返回动画是否正在运行&#xff0c;从动画第一帧开始算running状态&#xff0c;如果动画有动画开始延迟startDelay&#xff0c;在startDelay期间&#xff0c;此值返回false。

boolean isStarted()&#xff1a;返回动画是否已经开始&#xff0c;从start()开始&#xff0c;此值就返回true




3.2.24 动画reverse&#xff1a;让动画从当前位置逆序运行

boolean canReverse()&#xff1a;动画是否支持reverse操作

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



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




3.2.26 与线程相关的几个方法&#xff1a;

int getCurrentAnimationsCount()&#xff1a;静态方法&#xff0c;获取当前线程内正在执行的属性动画个数。

void clearAllAnimations()&#xff1a;静态方法&#xff0c;清除当前线程内正在执行或者将要执行的所有属性动画。

AnimationHandler getOrCreateAnimationHandler()&#xff1a;静态方法&#xff0c;获取AnimationHandler&#xff0c;没有就创建一个handler




3.2.27 通知动画帧刷新接口

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



3.3 定时策略AnimationHandler

3.3.1 主要代码

protected static class AnimationHandler {// 单个进程内所有活跃的属性动画的列表&#xff0c;每个线程只持有一个AnimationHandler(ThreadLocal机制)protected final ArrayList mAnimations &#61; new ArrayList();// 避免mAnimations列表的ConcurrentModification&#xff0c;在doAnimationFrame()方法中使用private final ArrayList mTmpAnimations &#61; new ArrayList();// 下一个动画帧时将要start的动画列表/** &#64;hide */protected final ArrayList mPendingAnimations &#61; new ArrayList();/*** 在处理动画开始/结束时避免冲突而使用的集合*/protected final ArrayList mDelayedAnims &#61; new ArrayList();private final ArrayList mEndingAnims &#61; new ArrayList();private final ArrayList mReadyAnims &#61; new ArrayList();//形成逐帧插值效果的定时器&#xff0c;单例private final Choreographer mChoreographer;//是否已经在Choreographer内规划了下一帧的动画插值事件private boolean mAnimationScheduled;//上一次插值的时间private long mLastFrameTime;private AnimationHandler() {mChoreographer &#61; Choreographer.getInstance();}//开始动画执行&#xff0c;本质就是在Choreographer内规划下一帧的动画插值事件public void start() {scheduleAnimation();}//开始一次新的帧插值计算及界面刷新void doAnimationFrame(long frameTime) {mLastFrameTime &#61; frameTime;//记录最近的一次插值时间// mPendingAnimations 内持有所有准备start的animation&#xff0c;我们需要将其移动到mAnimations和mDelayedAmiations列表内&#xff0c;// 考虑到startAnimation可能会导致pendinglist重新不为空&#xff08;比如一个动画start触发另一个动画的start&#xff09;&#xff0c;我们需要循环直到mPendingAnimations为空while (mPendingAnimations.size() > 0) {//克隆列表防止ConcurrentModifyExceptionArrayList pendingCopy &#61;(ArrayList) mPendingAnimations.clone();mPendingAnimations.clear();int count &#61; pendingCopy.size();for (int i &#61; 0; i 0) {for (int i &#61; 0; i 0) {for (int i &#61; 0; i }


3.3.2 delayedAnimationFrame(long currentTime)&#xff1a;对设置了startDelay的动画进行处理&#xff0c;判断是否已经超出了startDelay范围从而可以执行了

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



3.3.3 commitAnimationFrame(long adjustment)&#xff1a;对动画开始时间进行补偿

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



3.3.4 doAnimationFrame(long frameTime)&#xff1a;开始执行动画的某一帧插值与界面刷新

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





3.3.5 animationFrame(long currentTime)&#xff1a;由Handler触发计算动画属性的新插值。

/**根据当前时间计算新一帧的属性插值。currentTime参数是由Handler发出的时序脉冲&#xff0c;指出当前动画已经执行的时间。返回true表示动画需要结束了。*/
boolean animationFrame(long currentTime) {boolean done &#61; false;switch (mPlayingState) {case RUNNING:case SEEKED://计算新的动画执行耗时&#xff08;归一化&#xff0c;但可能大于1&#xff09;float fraction &#61; mDuration > 0 ? (float)(currentTime - mStartTime) / mDuration : 1f;if (mDuration &#61;&#61; 0 && mRepeatCount !&#61; INFINITE) {// Skip to the endmCurrentIteration &#61; mRepeatCount;if (!mReversing) {mPlayingBackwards &#61; false;}}if (fraction >&#61; 1f) {if (mCurrentIteration }



3.3.6 animateValue(float fraction)&#xff1a;真正的对动画进行属性插值&#xff0c;参数fraction介于0&#xff5e;1之间

/*** 此方法在每次动画帧刷新时都会调用&#xff0c;通过将动画已执行的时间&#xff08;归一化后的时间&#xff09;转化为插值时间&#xff0c;再通过Evaluator计算属性插值。* 在Animation帧刷新时和end()时方法都会被调用。** &#64;param fraction 动画已经执行的时间*/
&#64;CallSuper
void animateValue(float fraction) {//计算速率变化插值后的动画时间fraction &#61; mInterpolator.getInterpolation(fraction);//保存当前时间mCurrentFraction &#61; fraction;int numValues &#61; mValues.length;for (int i &#61; 0; i }





四、总结

本文分析了属性动画的抽象类Animator和属性动画最核心的类ValueAnimator的源码&#xff0c;在整个属性动画框架中&#xff0c;虽然我们用的最多的是ObjectAnimator&#xff0c;但ValueAnimator才是最基本最核心的基础&#xff0c;所有的属性动画效果与派生都是ValueAnimator的延展&#xff0c;比如ObjectAnimator就是在ValueAniamtor基础上增加了对象的属性读取/设置能力封装的应用类。

ValueAnimator的核心在于它管理了一个属性集合&#xff0c;然后在当前线程内存储一个对象AnimationHandler来不断的形成时序脉冲&#xff08;通过Choreographer机制&#xff09;&#xff0c;在每次脉冲到达时&#xff0c;计算新的插值属性值&#xff0c;并通过AnimatorUpdateListener的onAnimationUpdate()对外通知属性值的变化&#xff0c;外部接收到属性值变化的通知后&#xff0c;做一些对应的操作来设置属性值或者刷新界面等&#xff0c;形成动画视觉效果。

整体来讲&#xff0c;ValueAnimator的封装设计还是非常不错的&#xff0c;思想也比较新颖&#xff0c;但是部分代码的可读性比较差&#xff0c;需要花比较长的时间才能搞懂作者想要干嘛&#xff0c;这也是我这篇文章缺席了好久的一个原因。当然&#xff0c;最主要原因还是懒癌发作了&#xff0c;哈哈哈。

推荐阅读
  • 本文详细介绍了 com.apollographql.apollo.api.internal.Optional 类中的 orNull() 方法,并提供了多个实际代码示例,帮助开发者更好地理解和使用该方法。 ... [详细]
  • 深入解析 Lifecycle 的实现原理
    本文将详细介绍 Android Jetpack 中 Lifecycle 组件的实现原理,帮助开发者更好地理解和使用 Lifecycle,避免常见的内存泄漏问题。 ... [详细]
  • 本文详细介绍了 PHP 中对象的生命周期、内存管理和魔术方法的使用,包括对象的自动销毁、析构函数的作用以及各种魔术方法的具体应用场景。 ... [详细]
  • 包含phppdoerrorcode的词条 ... [详细]
  • Android 自定义 RecycleView 左滑上下分层示例代码
    为了满足项目需求,需要在多个场景中实现左滑删除功能,并且后续可能在列表项中增加其他功能。虽然网络上有很多左滑删除的示例,但大多数封装不够完善。因此,我们尝试自己封装一个更加灵活和通用的解决方案。 ... [详细]
  • 如果应用程序经常播放密集、急促而又短暂的音效(如游戏音效)那么使用MediaPlayer显得有些不太适合了。因为MediaPlayer存在如下缺点:1)延时时间较长,且资源占用率高 ... [详细]
  • 本文详细介绍了Java反射机制的基本概念、获取Class对象的方法、反射的主要功能及其在实际开发中的应用。通过具体示例,帮助读者更好地理解和使用Java反射。 ... [详细]
  • JUC(三):深入解析AQS
    本文详细介绍了Java并发工具包中的核心类AQS(AbstractQueuedSynchronizer),包括其基本概念、数据结构、源码分析及核心方法的实现。 ... [详细]
  • 本文将带你快速了解 SpringMVC 框架的基本使用方法,通过实现一个简单的 Controller 并在浏览器中访问,展示 SpringMVC 的强大与简便。 ... [详细]
  • DAO(Data Access Object)模式是一种用于抽象和封装所有对数据库或其他持久化机制访问的方法,它通过提供一个统一的接口来隐藏底层数据访问的复杂性。 ... [详细]
  • 在多线程并发环境中,普通变量的操作往往是线程不安全的。本文通过一个简单的例子,展示了如何使用 AtomicInteger 类及其核心的 CAS 无锁算法来保证线程安全。 ... [详细]
  • 重要知识点有:函数参数默许值、盈余参数、扩大运算符、new.target属性、块级函数、箭头函数以及尾挪用优化《深切明白ES6》笔记目次函数的默许参数在ES5中,我们给函数传参数, ... [详细]
  • 原文网址:https:www.cnblogs.comysoceanp7476379.html目录1、AOP什么?2、需求3、解决办法1:使用静态代理4 ... [详细]
  • Flowable 流程图路径与节点展示:已执行节点高亮红色标记,增强可视化效果
    在Flowable流程图中,通常仅显示当前节点,而路径则需自行获取。特别是在多次驳回的情况下,节点可能会出现混乱。本文重点探讨了如何准确地展示流程图效果,包括已结束的流程和正在执行的流程。具体实现方法包括生成带有高亮红色标记的图片,以增强可视化效果,确保用户能够清晰地了解每个节点的状态。 ... [详细]
  • 深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案
    深入剖析Java中SimpleDateFormat在多线程环境下的潜在风险与解决方案 ... [详细]
author-avatar
百度地震姜常宏
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有