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

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

程序员文章站 2023-12-17 18:09:34
前言 大家在日常开发中离不开动画,属性动画更为强大,我们不仅要知道如何使用,更要知道他的原理。这样,才能得心应手。那么,今天,就从最简单的来说,了解下属性动画的原理。...

前言

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

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开发者们能带来一定的帮助,如果有疑问大家可以留言交流,谢谢大家对的支持。

上一篇:

下一篇: