当前位置: 移动技术网 > 移动技术>移动开发>Android > Android源码—属性动画的工作原理

Android源码—属性动画的工作原理

2018年12月11日  | 移动技术网移动技术  | 我要评论

前言

本文为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动画系列已经全部完成,十分感谢大家,希望能给大家带来一点帮助!

如对本文有疑问, 点击进行留言回复!!

相关文章:

验证码:
移动技术网