Android源码—属性动画的工作原理
前言
本文为android动画系列的最后一篇文章,通过对的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画。但是,由于动画的底层实现已经深入到jni层,并且涉及到显示子,因此,深入地分析动画的底层实现不仅比较困难而且意义不大,因此,本文的分析到jni层为止。
android动画系列:
android动画简介
android动画进阶—使用开源动画库nineoldandroids
android属性动画深入分析:让你成为动画牛人
android源码分析—属性动画的工作原理
属性动画的原理
属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该熟悉的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去拿属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多,下面看源码分析。
源码分析
首先我们要找一个入口,就从objectanimator.ofint(mbutton, “width”, 500).setduration(5000).start()开始吧,其他动画都是类似的。
看objectanimator的start方法
[java] view plain copy
print?
@override
publicvoidstart(){
//seeifanyofthecurrentactive/pendinganimatorsneedtobecanceled
animationhandlerhandler=sanimationhandler.get();
if(handler!=null){
intnumanims=handler.manimations.size();
for(inti=numanims-1;i>=0;i–){
if(handler.manimations.get(i)instanceofobjectanimator){
objectanimatoranim=(objectanimator)handler.manimations.get(i);
if(anim.mautocancel&&hassametargetandproperties(anim)){
anim.cancel();
}
}
}
numanims=handler.mpendinganimations.size();
for(inti=numanims-1;i>=0;i–){
if(handler.mpendinganimations.get(i)instanceofobjectanimator){
objectanimatoranim=(objectanimator)handler.mpendinganimations.get(i);
if(anim.mautocancel&&hassametargetandproperties(anim)){
anim.cancel();
}
}
}
numanims=handler.mdelayedanims.size();
for(inti=numanims-1;i>=0;i–){
if(handler.mdelayedanims.get(i)instanceofobjectanimator){
objectanimatoranim=(objectanimator)handler.mdelayedanims.get(i);
if(anim.mautocancel&&hassametargetandproperties(anim)){
anim.cancel();
}
}
}
}
if(dbg){
log.d(”objectanimator”,“animtarget,duration:”+mtarget+“,”+getduration());
for(inti=0;ipropertyvaluesholderpvh=mvalues[i];
arraylistkeyframes=pvh.mkeyframeset.mkeyframes;
log.d(”objectanimator”,“values[“+i+“]:”+
pvh.getpropertyname()+”,”+keyframes.get(0).getvalue()+“,”+
keyframes.get(pvh.mkeyframeset.mnumkeyframes-1).getvalue());
}
}
super.start();
}
@override
public void start() {
// see if any of the current active/pending animators need to be canceled
animationhandler handler = sanimationhandler.get();
if (handler != null) {
int numanims = handler.manimations.size();
for (int i = numanims - 1; i >= 0; i--) {
if (handler.manimations.get(i) instanceof objectanimator) {
objectanimator anim = (objectanimator) handler.manimations.get(i);
if (anim.mautocancel && hassametargetandproperties(anim)) {
anim.cancel();
}
}
}
numanims = handler.mpendinganimations.size();
for (int i = numanims - 1; i >= 0; i--) {
if (handler.mpendinganimations.get(i) instanceof objectanimator) {
objectanimator anim = (objectanimator) handler.mpendinganimations.get(i);
if (anim.mautocancel && hassametargetandproperties(anim)) {
anim.cancel();
}
}
}
numanims = handler.mdelayedanims.size();
for (int i = numanims - 1; i >= 0; i--) {
if (handler.mdelayedanims.get(i) instanceof objectanimator) {
objectanimator anim = (objectanimator) handler.mdelayedanims.get(i);
if (anim.mautocancel && hassametargetandproperties(anim)) {
anim.cancel();
}
}
}
}
if (dbg) {
log.d("objectanimator", "anim target, duration: " + mtarget + ", " + getduration());
for (int i = 0; i < mvalues.length; ++i) {
propertyvaluesholder pvh = mvalues[i];
arraylist keyframes = pvh.mkeyframeset.mkeyframes;
log.d("objectanimator", " values[" + i + "]: " +
pvh.getpropertyname() + ", " + keyframes.get(0).getvalue() + ", " +
keyframes.get(pvh.mkeyframeset.mnumkeyframes - 1).getvalue());
}
}
super.start();
}
说明:上面的代码别看那么长,其实做的事情很简单,首先会判断一下,如果当前动画、等待的动画(pending)和延迟的动画(delay)中有和当前动画相同的动画,那么就把相同的动画给取消掉,接下来那一段是log,再接着就调用了父类的super.start()方法,因为objectanimator继承了valueanimator,所以接下来我们看一下valueanimator的start方法
[java] view plain copy
print?
privatevoidstart(booleanplaybackwards){
if(looper.mylooper()==null){
thrownewandroidruntimeexception(“animatorsmayonlyberunonlooperthreads”);
}
mplayingbackwards=playbackwards;
mcurrentiteration=0;
mplayingstate=stopped;
mstarted=true;
mstarteddelay=false;
mpaused=false;
animationhandleranimationhandler=getorcreateanimationhandler();
animationhandler.mpendinganimations.add(this);
if(mstartdelay==0){
//thissetstheinitialvalueoftheanimation,priortoactuallystartingitrunning
setcurrentplaytime(0);
mplayingstate=stopped;
mrunning=true;
notifystartlisteners();
}
animationhandler.start();
}
private void start(boolean playbackwards) {
if (looper.mylooper() == null) {
throw new androidruntimeexception("animators may only be run on looper threads");
}
mplayingbackwards = playbackwards;
mcurrentiteration = 0;
mplayingstate = stopped;
mstarted = true;
mstarteddelay = false;
mpaused = false;
animationhandler animationhandler = getorcreateanimationhandler();
animationhandler.mpendinganimations.add(this);
if (mstartdelay == 0) {
// this sets the initial value of the animation, prior to actually starting it running
setcurrentplaytime(0);
mplayingstate = stopped;
mrunning = true;
notifystartlisteners();
}
animationhandler.start();
}
说明:上述代码最终会调用animationhandler的start方法,这个animationhandler并不是handler,它是个runnable。看下它的代码,通过代码我们发现,很快就调到了jni层,不过jni层最终还是要调回来的。它的run方法会被调用,这个runnable涉及到和底层的交互,我们就忽略这部分,直接看重点:valueanimator中的doanimationframe方法
[java] view plain copy
print?
finalbooleandoanimationframe(longframetime){
if(mplayingstate==stopped){
mplayingstate=running;
if(mseektime<0){
mstarttime=frametime;
}else{
mstarttime=frametime-mseektime;
//nowthatwe’replaying,resettheseektime
mseektime=-1;
}
}
if(mpaused){
if(mpausetime<0){
mpausetime=frametime;
}
returnfalse;
}elseif(mresumed){
mresumed=false;
if(mpausetime>0){
//offsetbythedurationthattheanimationwaused
mstarttime+=(frametime-mpausetime);
}
}
//theframetimemightbebeforethestarttimeduringthefirstframeof
//ananimation.the“currenttime”mustalwaysbeonorafterthestart
//timetoavoidanimatingframesatnegativetimeintervals.inpractice,this
//isveryrareandonlyhappenswhenseekingbackwards.
finallongcurrenttime=math.max(frametime,mstarttime);
returnanimationframe(currenttime);
}
final boolean doanimationframe(long frametime) {
if (mplayingstate == stopped) {
mplayingstate = running;
if (mseektime < 0) {
mstarttime = frametime;
} else {
mstarttime = frametime - mseektime;
// now that we're playing, reset the seek time
mseektime = -1;
}
}
if (mpaused) {
if (mpausetime < 0) {
mpausetime = frametime;
}
return false;
} else if (mresumed) {
mresumed = false;
if (mpausetime > 0) {
// offset by the duration that the animation was paused
mstarttime += (frametime - mpausetime);
}
}
// 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);
return animationframe(currenttime);
}
注意到上述代码末尾调用了animationframe方法,而animationframe内部调用了animatevalue,下面看animatevalue的代码
[java] view plain copy
print?
voidanimatevalue(floatfraction){
fraction=minterpolator.getinterpolation(fraction);
mcurrentfraction=fraction;
intnumvalues=mvalues.length;
for(inti=0;imvalues[i].calculatevalue(fraction);
}
if(mupdatelisteners!=null){
intnumlisteners=mupdatelisteners.size();
for(inti=0;imupdatelisteners.get(i).onanimationupdate(this);
}
}
}
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);
}
}
}
上述代码中的calculatevalue方法就是计算每帧动画所对应的属性的值,下面着重看一下到底是在哪里调用属性的get和set方法的,毕竟这个才是我们最关心的。
get方法:在初始化的时候,如果属性的初始值没有提供,则get方法将会被调用。
[java] view plain copy
print?
privatevoidsetupvalue(objecttarget,keyframekf){
if(mproperty!=null){
kf.setvalue(mproperty.get(target));
}
try{
if(mgetter==null){
classtargetclass=target.getclass();
setupgetter(targetclass);
if(mgetter==null){
//alreadyloggedtheerror-justreturntoavoidnpe
return;
}
}
kf.setvalue(mgetter.invoke(target));
}catch(invocationtargetexceptione){
log.e(”propertyvaluesholder”,e.tostring());
}catch(illegalaccessexceptione){
log.e(”propertyvaluesholder”,e.tostring());
}
}
private void setupvalue(object target, keyframe kf) {
if (mproperty != null) {
kf.setvalue(mproperty.get(target));
}
try {
if (mgetter == null) {
class targetclass = target.getclass();
setupgetter(targetclass);
if (mgetter == null) {
// already logged the error - just return to avoid npe
return;
}
}
kf.setvalue(mgetter.invoke(target));
} catch (invocationtargetexception e) {
log.e("propertyvaluesholder", e.tostring());
} catch (illegalaccessexception e) {
log.e("propertyvaluesholder", e.tostring());
}
}
set方法:当动画的下一帧到来的时候,propertyvaluesholder中的setanimatedvalue方法会将新的属性值设置给对象,调用其set方法
[java] view plain copy
print?
voidsetanimatedvalue(objecttarget){
if(mproperty!=null){
mproperty.set(target,getanimatedvalue());
}
if(msetter!=null){
try{
mtmpvaluearray[0]=getanimatedvalue();
msetter.invoke(target,mtmpvaluearray);
}catch(invocationtargetexceptione){
log.e(”propertyvaluesholder”,e.tostring());
}catch(illegalaccessexceptione){
log.e(”propertyvaluesholder”,e.tostring());
}
}
}
void setanimatedvalue(object target) {
if (mproperty != null) {
mproperty.set(target, getanimatedvalue());
}
if (msetter != null) {
try {
mtmpvaluearray[0] = getanimatedvalue();
msetter.invoke(target, mtmpvaluearray);
} catch (invocationtargetexception e) {
log.e("propertyvaluesholder", e.tostring());
} catch (illegalaccessexception e) {
log.e("propertyvaluesholder", e.tostring());
}
}
}
总结
我觉得这篇源码分析写的逻辑有点混乱,希望不要给大家带来误导。从源码上来说,属性动画的源码逻辑层次有点跳跃,不过没关系,大家只要了解属性动画的工作原理就好,源码的作用在于让我们发现其工作原理的确如此。到此为止,android动画系列已经全部完成,十分感谢大家,希望能给大家带来一点帮助!
推荐阅读
-
【前端语言学习】学习minipack源码,了解打包工具的工作原理-个人文章-SegmentFault思否
-
Android源码—属性动画的工作原理
-
深入了解Android的View工作原理
-
Android高级进阶——View的工作原理Draw过程
-
Android中新引进的Google Authenticator验证系统工作原理浅析
-
Android 属性动画原理与DataBinding
-
Android消息通信机制Handler详解,Handler,Looper,MessageQueue,源码解析,讲解这几个类怎么配合工作的
-
Android消息机制之ThreadLocal的工作原理
-
Android的消息机制之ThreadLocal的工作原理
-
从源码的角度分析Android中的Handler机制的工作原理