欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2022-04-28 13:21:40
前言 本文为android动画系列的最后一篇文章,通过对的分析,能够让大家更深刻地理解属性动画的工作原理,这有助于我们更好地使用属性动画。但是,由于动画的底层实现已经深入到jni层,并且涉及到显示子...

前言

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