当前位置: 移动技术网 > 移动技术>移动开发>Android > Android源码解析之属性动画详解

Android源码解析之属性动画详解

2019年07月24日  | 移动技术网移动技术  | 我要评论

前言

大家在日常开发中离不开动画,属性动画更为强大,我们不仅要知道如何使用,更要知道他的原理。这样,才能得心应手。那么,今天,就从最简单的来说,了解下属性动画的原理。

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();
 }
}

方法较长,逻辑如下:

  1. 从mpendinganimations中取出动画,根据事先选择startanimation还是加入到mdelayedanims列表。
  2. 如果mdelayedanims列表中的动画准备好了,就加入到mreadyanims列表中
  3. 从manimations列表中取出要执行的动画,加入到mtmpanimations列表
  4. 通过doanimationframe方法执行动画帧
  5. 继续执行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);
 }
}

这里主要干了两件事,

  1. 调用父类的animatevalue方法
  2. 通过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());
 }
 }
}

恩,到这里就能看到修改属性值得痕迹了,有以下四种情况

  1. mintproperty不为null
  2. mproperty不为null
  3. mjnisetter不为null
  4. msetter不为null

首先,我们通过string propertyname, int… values参数构造的对象,mintproperty为null,并且mproperty也为null。那其他两个是怎么来的呢?似乎漏了什么?

还节的,在doanimationframe中,直接调用startanimation么?没错,就是这里。

startanimation

在这个方法中调用了initanimation方法。还是根据动态分派规则,这里调用objectanimator的initanimation方法。在这里调用propertyvaluesholder的setupsetterandgetter方法,在这里对msetter等进行了初始化,这里就不多说了,大家自己看代码吧。

好了,以上就是关于android中属性动画对的全部内容,希望本文的内容对各位android开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对移动技术网的支持。

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

相关文章:

验证码:
移动技术网