android属性动画学习与整理
对于android手机上的动画实现主要有三种,一种是帧动画,一种是View动画,以及3.0以上提供的属性动画,所有View动画可以实现的动画都可以使用属性动画来实现,最简单的如渐变,平移,缩小和放大。
这里对于属性动画做个学习的总结与整理,以前总是学习过后直接使用,过一段时间后又忘记了如何去使用一个属性动画来设计自己的动画效果,因此产生了这篇文章。
本篇文章分如下几个部分:
- ObjectAnimator介绍
- ValueAnimator介绍
- TypeEvaluator(估值器)和TimeInterpolator(插值器)的学习
- 关于源码的浅析
<h3>1.ObjectAnimator介绍</h3>
ObjectAnimator类是我们在设计动画的时候经常需要使用到的类,它可以直接对任意对象的任意属性进行动画操作,ObjectAnimator提供静态的动画实现方法,具体的实现方法有如下几种:
下述的静态方法中,传入的参数一是目标View,参数二是进行改变的属性,参数三是变化的过程区间
请对照例子查看。
//颜色渐变动画
public static ObjectAnimator ofArgb(Object target, String propertyName, int... values)
//int类型值动画
public static ObjectAnimator ofInt(Object target, String propertyName, int... values)
//float类型值动画
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
//Object类型值动画
public static ObjectAnimator ofObject(Object target, String propertyName, TypeEvaluator evaluator, Object... values)
....
上述列举了几个基本的方法来使用ObjectAnimator类,下面举两个例子整理一下大概实现代码的思路:
1.translationX
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
float s=imageView.getTranslationX();
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "translationX",s,-500f,s+100 );
objectAnimator.setDuration(4000).start();
}});
效果图
translationX.gif上述实现的是一个基本是平移动画,可以看出实现的效果跟View动画如出一辙
alpha
btn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
objectAnimator.setDuration(5000).start();
}});
效果图
alpha.gif
ObjectAnimator提供了不少方法设计动画的执行过程,下面举出常用的一些方法:
//设置动画执行时长
setDuration(4000);
//设置动画监听
objectAnimator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
}
@Override
public void onAnimationEnd(Animator animation) {
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}});
//设置动画循环播放的次数,默认为0,-1表示无限循环。
setRepeatCount();
//循环播放的模式,循环模式包括RESTART和REVERSE两种,分别表示连续重新播放和倒序播放的意思
setRepeatMode();
上述的两个例子只是针对于单个动画的效果,如果想要组合起各个动画,这也是各个复杂动画产生的过程,那该如何使用呢?答案是AnimatorSet类的使用,AnimatorSet可以把动画集合起来,既可以依次播放,也可以一起播放,可以定义各个动画的播放顺序,使用例子如下图所示:
yo.gif其代码如下所示:
float x=view.getTranslationX();
ObjectAnimator objectAnimator2 = ObjectAnimator.ofFloat(imageView, "translationX",x,500,x );
ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(imageView, "alpha", 1f, 0f, 1f);
AnimatorSet animatorSet=new AnimatorSet();
animatorSet.playTogether(objectAnimator,objectAnimator2);
animatorSet.setDuration(5000);
animatorSet.start();
关于AnimatorSet的使用,有如下主要几个常用的方法:
after(Animator anim) 将现有动画插入到传入的动画之后执行
after(long delay) 将现有动画延迟指定毫秒后执行
before(Animator anim) 将现有动画插入到传入的动画之前执行
with(Animator anim) 将现有动画和传入的动画同时执行
playTogether(Animator... items)将所有传入动画同时执行
playSequentially(Animator... items)所有动画依次执行
通过组合不同的动画,我们就可以写出不同的复杂的动画效果。
一个问题
为何在ObjectAnimator在第二个参数中传入一个String值,目标View就会执行相对应的动画呢?如我们传入一个alpha参数,为什么就行了呢?
引用郭霖大神的解释:其实ObjectAnimator内部的工作机制并不是直接对我们传入的属性名进行操作的,而是会去寻找这个属性名对应的get和set方法,因此alpha属性所对应的get和set方法应该就是:
public void setAlpha(float value);
public float getAlpha();
那么textview对象中是否有这两个方法呢?确实有,并且这两个方法是由View对象提供的,也就是说不仅TextView可以使用这个属性来进行淡入淡出动画操作,任何继承自View的对象都可以的。
既然alpha是这个样子,相信大家一定已经明白了,前面我们所用的所有属性都是这个工作原理,那么View当中一定也存在着setRotation()、getRotation()、setTranslationX()、getTranslationX()、setScaleY()、getScaleY()这些方法。
那假设我们不传入一个规定的值,我们随便传入一个字符串,如"aaaaa",他的效果会怎样的?一般情况下动画没有响应,而严重的情况则可能造成程序的crash,因为属性动画是要求动画对象提供get和set的方法,属性动画根据外界传递的属性的厨师值和最终值,以动画的效果多次的去调用set方法,每次传递给set的方法,随着时间的冲突,所传递的值最终越来越接近最终值,因此我们队object做动画,如果想要让动画生效,要满足两个条件:
-
object提供setAbc方法,如果动画时没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的值(不满足此条,程序会直接Crash)。
-
object的setAbc对属性abc所做的改变必须通过某种方法映射出来,如带来UI的改变之类的(不满足此条,动画无效果但不会Crash)。
ValueAnimator
如果你大概略过一遍关于ObjectAnimator的源码,你会发现其实ObjectAnimator是继承自ValueAnimator的,也就是说其实属性动画的实现最核心的类应该是ValueAnimator,而ObjectAnimator只是对其的一个封装继承而已,下面就先来了解一下关于ValueAnimator把。
下面代码重新的实现了透明度的变化从1f->0f->1f的过程,跟ObjectAnimator第一个示例的效果一样,这里就不贴gif图片了:
public void onClick(View v) {
valueAnimator=ValueAnimator.ofFloat(1F,0F,1F);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
float result= (float) animation.getAnimatedValue();
imageView.setAlpha(result); }
});
valueAnimator.setDuration(4000);
valueAnimator.start();
}
});
ValueAnimator并不负责持有需要做动画的View对象的引用,而只是执行了计算的过程,但是我们可以在addUpdateListener监听事件中进行设置View对象的变化过程,从而实现动画的过程。所有使用ObjectAnimator实现的效果,我们都可以通过ValueAnimator来实现这个过程,这里只是提供了一种ValueAnimator的使用情况,在实际开发中,我们还是使用ObjectAnimator偏多。
关于ValueAnimator的常用方法,只要ObjectAnimator拥有的方法,ValueAnimator都有,这里就不一一列举说明,有兴趣的朋友可以详细查看一下源码。
<h4>TimeInterpolator(插值器)</h4>
TimeInterpolator中文译为时间插值器,他的作用是根据时间流逝的百分比来计算出当前属性改变的百分比,系统中有预置的三种插值器:
- LinearInterpolator(线性插值器),匀速的动画
- DecelerateInterpolator(减速插值器),动画越来越慢
- AccelerateDecelerateInterpolator(加速减速插值器),两头慢中间快
<h4>TypeEvaluator(估值器)</h4>
TypeEvaluator作用是根据当前属性改变的百分比来计算改变后的属性值,其是要跟TimeInterpolator配对使用的。android内置的估值器有:
- IntEvaluator Int类型估值器,返回int类型的属性改变
- FloatEvaluator Float类型估值器,返回Float类型属性改变
- ArgbEvaluator 颜色类型估值器
TypeEvaluator源码:
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}
evaluate()方法当中传入了三个参数,第一个参数fraction非常重要,这个参数用于表示动画的完成度的,我们应该根据它来计算当前动画的值应该是多少,第二第三个参数分别表示动画的初始值和结束值。
下面以匀速动画实现过程来研究估值器和插值器在其中所扮演的角色,关于匀速动画实现的插值器源码和估值器源码如下所示:
时间插值器
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() { }
public LinearInterpolator(Context context, AttributeSet attrs) { }
public float getInterpolation(float input) { return input; }
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
类型估值器
public class IntEvaluator implements TypeEvaluator<Integer> {
public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
int startInt = startValue;
return (int)(startInt + fraction * (endValue - startInt));
}
比如一个移动的线性动画需要在1s中完成400dp的移动过程,那么在0.4s时候,时间插值器的值是多少呢,主要是看其getInterpolation()方法,我们可以看到,过了0.4s,流逝的时间比即是0.4/1=0.4,所以属性改变百分比的值为0.4,然后交付给估值器(这里假设使用Int类型估值器)进行对当前对象属性的改变操作。估值器获取到当前属性改变的百分比0.4后,在IntEvaluator中会将第一个参数赋值为0.4,计算得到结果后在进行属性的改变,当前过程会一直重复,知道动画完成为止。
Android系统默认提供的插值器和估值器满足大部分使用场景,但是在某些情况下,我们可能需要重写估值器或者插值器来达到使用动画的目的,下面以重写一个估值器为例:
定义一个动画,使得View围绕某一点做圆周运动,如图所示:
yo.gif当然,直接通过重写View的onDraw()实现起来更加简单点,这里只是做个例子,在例子中,系统内置的估值器并不能满足我们的需求,所以这里进行重新定义,代码如下:
自定义的估值器:
public class CircleTypeInterpolator implements TypeEvaluator {
private float radius;
public CircleTypeInterpolator(float radius){
this.radius=radius;
}
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Point point=new Point();
Point startPoint= (Point) startValue;
//旋转的角度
float angle=fraction*360;
float longY=(float) ((radius*Math.sin(Math.toRadians(angle))));
point.setY(startPoint.getY()+longY );
float longX= (float) (radius-radius*Math.cos(Math.toRadians(angle))); point.setX(startPoint.getX()+longX);
return point;
}
}
这里主要涉及到关于计算的问题,计算的思路是通过起始点通过三角计算来得到新的点的坐标。
自定义View:
public class CirlceView extends View {
private Paint paint;
private Point point;
Point startPoint;
public static final float RADIUS=30f;
public CirlceView(Context context) {
super(context);
init();
}
public CirlceView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
private void init() {
paint=new Paint(Paint.ANTI_ALIAS_FLAG);
paint.setColor(Color.BLUE);
}
@Override
protected void onDraw(Canvas canvas) {
if (point==null){
point=new Point(getMeasuredWidth()/2,getMeasuredHeight()/2);
canvas.drawCircle(point.x,point.y,RADIUS,paint); startAnim();
}else {
canvas.drawCircle(point.getX(),point.getY(),RADIUS,paint);
}
}
private void startAnim() {
try {
startPoint = (Point) point.clone();
} catch (CloneNotSupportedException e) {
e.printStackTrace();
}
ValueAnimator animator=ValueAnimator.ofObject(new CircleTypeInterpolator(100f),startPoint,startPoint);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
Point result= (Point) animation.getAnimatedValue();
point.setX(result.getX());
point.setY(result.getY());
invalidate();
}
});
animator.setDuration(2000);
animator.setRepeatCount(-1);
animator.start();
}
}
自定义对象:
public class Point implements Cloneable {
public float x;
public float y;
public Point(float x, float y) {
this.x = x; this.y = y;
}
public Point() { }
public float getX() {
return x;
}
public void setX(float x) {
this.x = x;
}
public float getY() {
return y;
}
public void setY(float y) {
this.y = y;
}
@Override
protected Object clone() throws CloneNotSupportedException {
Point point=null;
point= (Point) super.clone();
return point;
}
}
<h3>源码浅析</h3>
上面整理了属性的动画的使用的具体的一些方法以及例子,这里再对于属性动画进行源码方面的研究,不求有多精通深入,希望能对动画源码有一个大体的认识,方便日后研究。
属性动画工作原理
属性动画要求动画作用的对象提供该属性的set方法,苏弧形动画会根据你传递的该属性的初始值和最终值,以动画的效果多次调用set方法。每次传递个set方法的值东不一样,随着时间的推移,所传递的值越来越接近最终值。如果动画没有传递初始值,那么还要提供get方法,因为系统要去获取属性的初始值。
从ObjectAnimator的为起点进行分析,选择ofFloat(...)进行代码研究着手点:
public static ObjectAnimator ofFloat(Object target, String propertyName, float... values) {
ObjectAnimator anim = new ObjectAnimator(target, propertyName);
anim.setFloatValues(values);
return anim;
}
private ObjectAnimator(Object target, String propertyName) {
setTarget(target);
setPropertyName(propertyName);
}
//这里设置目标target,并且做了判断,如果当前的target已经启动了动画,则会将动画取消。
public void setTarget(@Nullable Object target) {
final Object oldTarget = getTarget();
if (oldTarget != target) {
if (isStarted()) {
cancel();
}
mTarget = target == null ? null : new WeakReference<Object>(target);
// New target should cause re-initialization prior to starting
mInitialized = false;
}
}
//设置属性名
public void setPropertyName(@NonNull String propertyName) {
// mValues could be null if this is being constructed piecemeal. Just record the
// propertyName to be used later when setValues() is called if so.
if (mValues != null) {
PropertyValuesHolder valuesHolder = mValues[0];
String oldName = valuesHolder.getPropertyName();
valuesHolder.setPropertyName(propertyName);
mValuesMap.remove(oldName);
mValuesMap.put(propertyName, valuesHolder);
}
mPropertyName = propertyName;
// New property/values/target should cause re-initialization prior to starting
mInitialized = false;
}
这里构建了一个ObjectAnimator对象,在ObjectAnimator对象中传入了目标target以及属性名propertyName,然后在调用setFloatValues方法:
@Override
public void setFloatValues(float... 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.ofFloat(mProperty, values));
} else {
setValues(PropertyValuesHolder.ofFloat(mPropertyName, values));
}
} else {
super.setFloatValues(values);
}
}
这里ObjectAnimator通过设置浮点值来调用父类ValueAnimator的setVaules函数,先查看传入的值:
public static PropertyValuesHolder ofFloat(String propertyName, float... values) {
return new FloatPropertyValuesHolder(propertyName, values);
}
public FloatPropertyValuesHolder(String propertyName, float... values) {
super(propertyName);
setFloatValues(values);
}
@Override
public void setFloatValues(float... values) {
super.setFloatValues(values);
mFloatKeyframes = (Keyframes.FloatKeyframes) mKeyframes;
}
//对应 super.setFloatValues(values)初始化的东东
public void setFloatValues(float... values) {
mValueType = float.class;
mKeyframes = KeyframeSet.ofFloat(values);
}
这里返回了一个FloatPropertyValuesHolder对象,FloatPropertyValuesHolder调用了setFloatValues()方法,在其中设置了mFloatKeyframes这个变量,mFloatKeyframes通过调用KeyframeSet.ofFloat(values)方法获得,这里查看一下KeyframeSet.ofFloat(values):
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f);
keyframes[1] = (FloatKeyframe) Keyframe.ofFloat(1f, values[0]);
if (Float.isNaN(values[0])) {
badValue = true;
}
} else {
keyframes[0] = (FloatKeyframe) Keyframe.ofFloat(0f, values[0]);
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(FloatKeyframe) Keyframe.ofFloat((float) i / (numKeyframes - 1), values[i]);
if (Float.isNaN(values[i])) {
badValue = true;
}
}
}
if (badValue) {
Log.w("Animator", "Bad value (NaN) in float animator");
}
return new FloatKeyframeSet(keyframes);
}
这里的keyframe成为关键帧,而KeyframeSet则是存储Keyframe的集合,代码中根据values的长度来构建一个FloatKeyframe keyframes[]数组,然后通过Keyframe.ofFloat()方法去构造一个keyframe对象:
FloatKeyframe(float fraction, float value) {
mFraction = fraction;
mValue = value;
mValueType = float.class;
mHasValue = true;
}
FloatKeyframe(float fraction) {
mFraction = fraction;
mValueType = float.class;
}
在初始化完成后返回keyframes数组,将其传入到FloatKeyframeSet中进行构建,查看FloatKeyframeSet的构建方法:
public KeyframeSet(Keyframe... keyframes) {
mNumKeyframes = keyframes.length;
// immutable list
mKeyframes = Arrays.asList(keyframes);
mFirstKeyframe = keyframes[0];
mLastKeyframe = keyframes[mNumKeyframes - 1];
mInterpolator = mLastKeyframe.getInterpolator();
}
FloatKeyframeSet最终只是调用了父类的初始化方法,在父类的初始化方法中存储了所有的关键帧,开始帧,结束帧以及时间插值器。到这里,PropertyValuesHolder.ofFloat()初始化完成,重新回溯到ObjectAnimator.setFloatValue()中,调用其setValues(PropertyValuesHolder.ofFloat(mPropertyName, values))方法:
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;
}
首先记录了mValues,这里的values是PropertyValuesHolder[]类型的,然后通过一个mValueMap记录:key为属性的名称,值为PropertyValuesHolder 。
总结一下初始化的过程(摘自鸿洋csdn):ofFloat就是记录了target,propName,values(是将我们传入的int型values,辗转转化成了PropertyValuesHolder),以及一个mValueMap,这个map的key是propName,value是PropertyValuesHolder,在PropertyValuesHolder内部又存储了proprName, valueType , keyframeSet等等。
这里关于ObjectAnimator.ofFloat()方法的初始化就到此完成.
接下来着手研究一下核心的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();
}
}
}
}
.....
super.start();
}
//是否存在相同的属性和包名
private boolean hasSameTargetAndProperties(@Nullable Animator anim) {
if (anim instanceof ObjectAnimator) {
PropertyValuesHolder[] theirValues = ((ObjectAnimator) anim).getValues();
if (((ObjectAnimator) anim).getTarget() == getTarget() &&
mValues.length == theirValues.length) {
for (int i = 0; i < mValues.length; ++i) {
PropertyValuesHolder pvhMine = mValues[i];
PropertyValuesHolder pvhTheirs = theirValues[i];
if (pvhMine.getPropertyName() == null ||
!pvhMine.getPropertyName().equals(pvhTheirs.getPropertyName())) {
return false;
}
}
return true;
}
}
return false;
}
在start()方法中通过AnimationHandler来获得到当前/准备/删除的动画,通过hasSameTargetAndProperties()来判断如果当前/准备/删除动画中有和当前动画相同的,那么就取消掉相同的动画。
然后调用父类的start()方法,由于ObjectAnimator的父类是ValueAnimator,所以我们找到源码:
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();
}
Animators需要在拥有Looper的线程中执行,如果要更新UI,则必须在UI线程中使用。
在animationHandler(这是一个Runnable)中把当前动画加入到准备动画当中,并且初始化一些动画的状态变量,最后调用animationHandler.start()方法启动动画,start()源码:
public void start() {
scheduleAnimation();
}
private void scheduleAnimation() {
if (!mAnimationScheduled) {
mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, mAnimate, null);
mAnimationScheduled = true;
}
}
最终会调用到Choreographer类,这是与底层交互的类,animationHandler是Runnable的子类,而 mChoreographer.postCallback(Choreographer.CALLBACK_ANIMATION, this, null);类似与handler发送消息,最终执行这个Runnable的run方法。所以只要查看run方法即可:
public void run() {
mAnimationScheduled = false;
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();
}
}
在while循环当中,首先将所有准备动画取出,然后清空准备动画的List,接着通过是否延迟播放来判断立即执行还是延迟执行。将animationHandler的mAnimations集合中的每个anim,加入到mTmpAnimations中,依次调用mTmpAnimations中的anim,anim.doAnimationFrame(frameTime),doAnimationFrame(frameTime)上面已经分析过了,如果返回true,即doAnimationFrame的done为true,则将该动画加入到结束动画集合。
循环调用mEndingAnims, mEndingAnims.get(i).endAnimation(this);内部,会将动画移除mAnimations,回调动画监听接口onAnimationEnd;以及重置各种标志变量。如果mAnimations不为null,则再次调用scheduleAnimation(),这里有调用到了scheduleAnimation,上述分析这是一个与底层交互的入口,因此循环调用使得view的属性变化,直到动画执行完成为止。
总结
引用鸿洋大大的吧(博客链接):
ofInt中实例化了一个ObjectAnimator对象,然后设置了target,propName,values(PropertyValuesHolder) ;然后分别在setInterpolator,setDuration设置了Interpolator
和duration。其中setEvaluator是给PropertyValuesHolder,以及keyframeSet设置估值算法。
PropertyValueHolder实际上是IntPropertyValueHolder类型对象,包含propName,valueType,keyframeSet .
keyframeset中存了Keyframe集合,keyframe中存储了(fraction , valuetype , value , hasValue)。
上述其实都是设置各种值什么的。真正核心要看start~
start()中:
首先,步骤1:更新动画各种状态,然后初步计算fraction为(currentTime - mStartTime) / mDuration;然后将这个fraction交给我们的插值器计算后得到新的fraction,再将新的fraction交给我们的估值算法,估值算法根据开始、结束、fraction得到当前属性(动画作用的属性)应该的值,最大调用反射进行设置;
当然了:start中还会根据动画的状态,如果没有结束,不断的调用scheduleAnimation();该方法内部利用mChoreographer不断的去重复我们的上述步骤1。
参考文章: