Android源码解析之属性动画详解
前言
大家在日常开发中离不开动画,属性动画更为强大,我们不仅要知道如何使用,更要知道他的原理。这样,才能得心应手。那么,今天,就从最简单的来说,了解下属性动画的原理。
objectanimator .ofint(mview,"width",100,500) .setduration(1000) .start();
objectanimator#ofint
以这个为例,代码如下。
public static objectanimator ofint(object target, string propertyname, int... values) { objectanimator anim = new objectanimator(target, propertyname); anim.setintvalues(values); return anim; }
在这个方法中,首先会new一个objectanimator对象,然后通过setintvalues方法将值设置进去,然后返回。在objectanimator的构造方法中,会通过settarget方法设置当前动画的对象,通过setpropertyname设置当前的属性名。我们重点说下setintvalues方法。
public void setintvalues(int... values) { if (mvalues == null || mvalues.length == 0) { // no values yet - this animator is being constructed piecemeal. init the values with // whatever the current propertyname is if (mproperty != null) { setvalues(propertyvaluesholder.ofint(mproperty, values)); } else { setvalues(propertyvaluesholder.ofint(mpropertyname, values)); } } else { super.setintvalues(values); } }
首先会判断,mvalues是不是null,我们这里是null,并且mproperty也是null,所以会调用setvalues(propertyvaluesholder.ofint(mpropertyname, values));
方法。先看propertyvaluesholder.ofint
方法,propertyvaluesholder这个类是holds属性和值的,在这个方法会构造一个intpropertyvaluesholder对象并返回。
public static propertyvaluesholder ofint(string propertyname, int... values) { return new intpropertyvaluesholder(propertyname, values); }
intpropertyvaluesholder的构造方法如下:
public intpropertyvaluesholder(string propertyname, int... values) { super(propertyname); setintvalues(values); }
在这里,首先会调用他的分类的构造方法,然后调用setintvalues方法,在他父类的构造方法中,只是设置了下propertyname。setintvalues内容如下:
public void setintvalues(int... values) { super.setintvalues(values); mintkeyframes = (keyframes.intkeyframes) mkeyframes; }
在父类的setintvalues方法中,初始化了mvaluetype为int.class,mkeyframes为keyframeset.ofint(values)。其中keyframeset为关键帧集合。然后将mkeyframes赋值给mintkeyframes。
keyframeset
这个类是记录关键帧的。我们看下他的ofint方法。
public static keyframeset ofint(int... values) { int numkeyframes = values.length; intkeyframe keyframes[] = new intkeyframe[math.max(numkeyframes,2)]; if (numkeyframes == 1) { keyframes[0] = (intkeyframe) keyframe.ofint(0f); keyframes[1] = (intkeyframe) keyframe.ofint(1f, values[0]); } else { keyframes[0] = (intkeyframe) keyframe.ofint(0f, values[0]); for (int i = 1; i < numkeyframes; ++i) { keyframes[i] = (intkeyframe) keyframe.ofint((float) i / (numkeyframes - 1), values[i]); } } return new intkeyframeset(keyframes); }
在这里呢?根据传入的values来计算关键帧,最后返回intkeyframeset。
回到objectanimator里面,这里的setvalues用的是父类valueanimator的
valueanimator#setvalues
public void setvalues(propertyvaluesholder... values) { int numvalues = values.length; mvalues = values; mvaluesmap = new hashmap<string, propertyvaluesholder>(numvalues); for (int i = 0; i < numvalues; ++i) { propertyvaluesholder valuesholder = values[i]; mvaluesmap.put(valuesholder.getpropertyname(), valuesholder); } // new property/values/target should cause re-initialization prior to starting minitialized = false; }
这里的操作就简单了,就是把propertyvaluesholder放入到mvaluesmap中。
objectanimator#start
这个方法就是动画开始的地方。
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(log_tag, "anim target, duration: " + gettarget() + ", " + getduration()); for (int i = 0; i < mvalues.length; ++i) { propertyvaluesholder pvh = mvalues[i]; log.d(log_tag, " values[" + i + "]: " + pvh.getpropertyname() + ", " + pvh.mkeyframes.getvalue(0) + ", " + pvh.mkeyframes.getvalue(1)); } } super.start(); }
首先呢,会获取animationhandler对象,如果不为空的话,就会判断是manimations、mpendinganimations、mdelayedanims中的动画,并且取消。最后调用父类的start方法。
valueanimator#start
private void start(boolean playbackwards) { if (looper.mylooper() == null) { throw new androidruntimeexception("animators may only be run on looper threads"); } mreversing = playbackwards; mplayingbackwards = playbackwards; if (playbackwards && mseekfraction != -1) { if (mseekfraction == 0 && mcurrentiteration == 0) { // special case: reversing from seek-to-0 should act as if not seeked at all mseekfraction = 0; } else if (mrepeatcount == infinite) { mseekfraction = 1 - (mseekfraction % 1); } else { mseekfraction = 1 + mrepeatcount - (mcurrentiteration + mseekfraction); } 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 if (playbackwards) { mplayingbackwards = (mcurrentiteration % 2) == 0; } else { mplayingbackwards = (mcurrentiteration % 2) != 0; } } int prevplayingstate = mplayingstate; mplayingstate = stopped; mstarted = true; mstarteddelay = false; mpaused = false; updatescaledduration(); // in case the scale factor has changed since creation time animationhandler animationhandler = getorcreateanimationhandler(); animationhandler.mpendinganimations.add(this); if (mstartdelay == 0) { // this sets the initial value of the animation, prior to actually starting it running if (prevplayingstate != seeked) { setcurrentplaytime(0); } mplayingstate = stopped; mrunning = true; notifystartlisteners(); } animationhandler.start(); }
- 先初始化一些值
- updatescaledduration 缩放时间,默认为1.0f
- 获取或者创建animationhandler,将动画加入到mpendinganimations列表中,
- 如果没延迟,通知监听器
- animationhandler.start
在animationhandler.start
中,会调用scheduleanimation方法,在这个种,会用mchoreographerpost一个callback,最终会执行manimate的run方法。mchoreographerpost涉及到vsync,这里不多介绍。
manimate#run
doanimationframe(mchoreographer.getframetime());
在这里会用过doanimationframe设置动画帧,我们看下这个方法的代码。
void doanimationframe(long frametime) { mlastframetime = frametime; // mpendinganimations holds any animations that have requested to be started // we're going to clear mpendinganimations, but starting animation may // cause more to be added to the pending list (for example, if one animation // starting triggers another starting). so we loop until mpendinganimations // is empty. while (mpendinganimations.size() > 0) { 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); // if the animation has a startdelay, place it on the delayed list if (anim.mstartdelay == 0) { anim.startanimation(this); } else { mdelayedanims.add(anim); } } } // next, process animations currently sitting on the delayed queue, adding // them to the active animations if they are ready int numdelayedanims = mdelayedanims.size(); for (int i = 0; i < numdelayedanims; ++i) { valueanimator anim = mdelayedanims.get(i); if (anim.delayedanimationframe(frametime)) { mreadyanims.add(anim); } } 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(); } // now process all active animations. the return value from animationframe() // tells the handler whether it should now be ended 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); 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(); } // schedule final commit for the frame. mchoreographer.postcallback(choreographer.callback_commit, mcommit, null); // if there are still active or delayed animations, schedule a future call to // onanimate to process the next frame of the animations. if (!manimations.isempty() || !mdelayedanims.isempty()) { scheduleanimation(); } }
方法较长,逻辑如下:
- 从mpendinganimations中取出动画,根据事先选择startanimation还是加入到mdelayedanims列表。
- 如果mdelayedanims列表中的动画准备好了,就加入到mreadyanims列表中
- 从manimations列表中取出要执行的动画,加入到mtmpanimations列表
- 通过doanimationframe方法执行动画帧
- 继续执行scheduleanimation
从上面我们能看出,执行动画的关键是doanimationframe方法。在这个方法中,会调用animationframe方法。
valueaniator#animationframe
boolean animationframe(long currenttime) { boolean done = false; switch (mplayingstate) { case running: case seeked: 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) { // time to repeat if (mlisteners != null) { int numlisteners = mlisteners.size(); for (int i = 0; i < numlisteners; ++i) { mlisteners.get(i).onanimationrepeat(this); } } if (mrepeatmode == reverse) { mplayingbackwards = !mplayingbackwards; } mcurrentiteration += (int) fraction; fraction = fraction % 1f; mstarttime += mduration; // note: we do not need to update the value of mstarttimecommitted here // since we just added a duration offset. } else { done = true; fraction = math.min(fraction, 1.0f); } } if (mplayingbackwards) { fraction = 1f - fraction; } animatevalue(fraction); break; } return done; }
- 计算fraction
- 调用animatevalue方法
根据虚拟机执行引擎动态分派原则,这里会调用objectanimator的animatevalue方法。
objectanimator#animatevalue
void animatevalue(float fraction) { final object target = gettarget(); if (mtarget != null && target == null) { // we lost the target reference, cancel and clean up. cancel(); return; } super.animatevalue(fraction); int numvalues = mvalues.length; for (int i = 0; i < numvalues; ++i) { mvalues[i].setanimatedvalue(target); } }
这里主要干了两件事,
- 调用父类的animatevalue方法
- 通过setanimatedvalue设置属性
其父类的方法如下:
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); } } }
在这个方法中,会通过interpolator得到出当前的fraction,并通过calculatevalue来计算当前应该的值,这里会调用intpropertyvaluesholder的calculatevalue
void calculatevalue(float fraction) { mintanimatedvalue = mintkeyframes.getintvalue(fraction); }
我们知道,mintkeyframes对应的是intkeyframeset。在这个类的getintvalue中,会通过typeevaluator来计算当前对应的值。不多说了。
最后,回到animatevalue。计算了值之后,会调用setanimatedvalue来设置值。我们看看他的实现。
intpropertyvaluesholder#setanimatedvalue
void setanimatedvalue(object target) { if (mintproperty != null) { mintproperty.setvalue(target, mintanimatedvalue); return; } if (mproperty != null) { mproperty.set(target, mintanimatedvalue); return; } if (mjnisetter != 0) { ncallintmethod(target, mjnisetter, mintanimatedvalue); return; } if (msetter != null) { try { mtmpvaluearray[0] = mintanimatedvalue; msetter.invoke(target, mtmpvaluearray); } catch (invocationtargetexception e) { log.e("propertyvaluesholder", e.tostring()); } catch (illegalaccessexception e) { log.e("propertyvaluesholder", e.tostring()); } } }
恩,到这里就能看到修改属性值得痕迹了,有以下四种情况
- mintproperty不为null
- mproperty不为null
- mjnisetter不为null
- msetter不为null
首先,我们通过string propertyname, int… values参数构造的对象,mintproperty为null,并且mproperty也为null。那其他两个是怎么来的呢?似乎漏了什么?
还节的,在doanimationframe中,直接调用startanimation么?没错,就是这里。
startanimation
在这个方法中调用了initanimation方法。还是根据动态分派规则,这里调用objectanimator的initanimation方法。在这里调用propertyvaluesholder的setupsetterandgetter方法,在这里对msetter等进行了初始化,这里就不多说了,大家自己看代码吧。
好了,以上就是关于android中属性动画对的全部内容,希望本文的内容对各位android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。
推荐阅读
-
Android源码解析之属性动画详解
-
Android动画之逐帧动画(Frame Animation)实例详解
-
Android动画之补间动画(Tween Animation)实例详解
-
Android动画之渐变动画(Tween Animation)详解 (渐变、缩放、位移、旋转)
-
图文详解Android属性动画
-
Android源码解析之属性动画详解
-
详解Android之解析XML文件三种方式(DOM,PULL,SAX)
-
详解Android开发数据持久化之文件存储(附源码)
-
Android 动画之AlphaAnimation应用详解
-
Android 动画之TranslateAnimation应用详解