二者都可以实现大部分动画效果,也可以通过组合实现各种动画效果,并且支持xml和java两种生成方式
二者的实现原理和效果差异性还是比较大的,下面来看看这两种动画的使用和实现原理
View动画主要使用的是Animation这个类,其子类TranslateAniamtion,AlphaAnimation等实现不同的动画效果,比如平移,透明度,缩放,旋转等
AnimationSet类可以管理多个Animation,做组合动画
//旋转动画(以自身的中心为原点,从0度转到360度)
RotateAnimation rotateAnimation = new RotateAnimation(0, 360, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f);
//透明度动画(从完全不可见到完全可见)
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
//平移动画(x轴方向,从自身原点平移500像素)
TranslateAnimation translateAnimation = new TranslateAnimation(Animation.RELATIVE_TO_SELF, 0, Animation.ABSOLUTE, 500, Animation.RELATIVE_TO_SELF, 0, Animation.RELATIVE_TO_SELF, 0);
//AnimationSet集合控制多个动画(无法控制顺序,而且执行效果和add顺序还有关系...)
AnimationSet animationSet = new AnimationSet(true);
//统一设置所有Animation的duration为1000
animationSet.setDuration(1000);
//动画结束后保持位置(并不影响实际measure和layout)
animationSet.setFillAfter(true);
//顺序添加Animation
animationSet.addAnimation(rotateAnimation);
animationSet.addAnimation(alphaAnimation);
animationSet.addAnimation(translateAnimation);
//view开启动画
view.startAnimation(animationSet);
由代码可知,我们可以创建不同的Animation对象,设置不同的参数:fromValue,toValue,type(绝对值,相对于自身,相对于parent等)
当有多个动画效果时,可以创建多个Animation对象,然后使用一个AnimationSet对象,add这些Animation,也可以设置一些公共参数
最后,通过view.startAnimation(Animation)执行view的动画
//在res/anim文件夹下定义
<set xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000">
<translate
android:fromXDelta="0"
android:toXDelta="500" />
<alpha
android:fromAlpha="0"
android:toAlpha="1" />
set>
//代码调用
Animation animation = AnimationUtils.loadAnimation(this, R.anim.translate_in);
view.startAnimation(animation);
我们也可以通过声明xml文件来定义动画,就对应的AnimationSet对象,就对应的TranslateAnimation对象,其他的等都类似
在代码里,我们可以使用AnimationUtils提供的方法将xml解析成Animation对象再使用
Animation对象保存了需要变化的参数,比如fromValue,toValue等等
Transformation对象维护一个matrix和alpha值,来记录不同动画的变化结果
在ViewGroup中,为每个child调用draw()方法时,会在canvas上应用当前动画效果的transformation,然后进行绘制
startAnimation后,会调用view的invalidate方法进行重绘,每次绘制完后,如果动画并没有完成,还会继续调用invalidate直至动画完成
由(3)知,动画只是改变了绘制时的状态,而不是改变了view的measure和layout的位置和大小等属性
i.Animation类用于存储和应用其动画值,当绘制时,会调用到Animation对象的getTransformation方法,并传递一个Transformation对象,该方法将动画当前的参数应用到Transformation对象上
//默认的实现
public boolean getTransformation(long currentTime, Transformation outTransformation) {
...
//根据当前时间计算动画行进比例
if (duration != 0) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
} else {
// time is a step-change with a zero duration
normalizedTime = currentTime < mStartTime ? 0.0f : 1.0f;
}
//动画是否结束
final boolean expired = normalizedTime >= 1.0f || isCanceled();
mMore = !expired;
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
...
//应用动画效果到Transformation对象上
applyTransformation(interpolatedTime, outTransformation);
}
...
//返回值代表动画是否已经完成
return mMore;
}
//AnimationSet的实现
public boolean getTransformation(long currentTime, Transformation t) {
...
//遍历children,应用每一个动画效果
for (int i = count - 1; i >= 0; --i) {
final Animation a = animations.get(i);
temp.clear();
more = a.getTransformation(currentTime, temp, getScaleFactor()) || more;
t.compose(temp);
started = started || a.hasStarted();
ended = a.hasEnded() && ended;
}
...
//是否所有动画均已完成
return more;
}
一般动画就是调用自己的applyTransformation方法,应用动画参数;AnimationSet类要调用每个animation的applyTransformation方法,应用所有的动画效果
ii.Animation类应用动画效果调用applyTransformation方法
//TranslateAnimation
protected void applyTransformation(float interpolatedTime, Transformation t) {
float dx = mFromXDelta;
float dy = mFromYDelta;
if (mFromXDelta != mToXDelta) {
dx = mFromXDelta + ((mToXDelta - mFromXDelta) * interpolatedTime);//根据比例计算当前偏移值
}
if (mFromYDelta != mToYDelta) {
dy = mFromYDelta + ((mToYDelta - mFromYDelta) * interpolatedTime);
}
t.getMatrix().setTranslate(dx, dy);//直接将偏移应用到Transformation的matrix上
}
//AlphaAnimation
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));//计算当前alpha值,应用到Transformation中
}
由代码可以看到,Animation类就是将自己的当前动画值,应用到Transformation的matrix和alpha上,用于绘制时改变绘制状态
i.startAnimation()
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);//保存Animation对象
invalidateParentCaches();
invalidate(true);//重绘
}
开启动画,其实实质就是记录Animation对象,调用invalidate进行重绘,重绘时应用Animation
ii.draw()
//ViewGroup为每个child调用draw方法是根据Animation来设置动画效果
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
...
Transformation transformToApply = null;
boolean concatMatrix = false;
final boolean scalingRequired = mAttachInfo != null && mAttachInfo.mScalingRequired;
final Animation a = getAnimation();
if (a != null) {//有动画
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);//应用动画
...
}
...
float alpha = drawingWithRenderNode ? 1 : (getAlpha() * getTransitionAlpha());
if (transformToApply != null
|| alpha < 1
|| !hasIdentityMatrix()
|| (mPrivateFlags3 & PFLAG3_VIEW_IS_ANIMATING_ALPHA) != 0) {
if (transformToApply != null || !childHasIdentityMatrix) {
...
if (transformToApply != null) {
canvas.concat(transformToApply.getMatrix());//将Transformation的matrix应用到canvas上(concat拼接)
}
...
}
...
}
...
return more;
}
//获取变换后的Transformation对象
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
Transformation invalidationTransform;
final int flags = parent.mGroupFlags;
final boolean initialized = a.isInitialized();
if (!initialized) {//没有初始化
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());//使用child的size和parent的size初始化Animation(因为可以有相对自己、parent等类型)
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);//初始化绘制region,初始化时child的区域
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();//listener
}
final Transformation t = parent.getChildTransformation();
boolean more = a.getTransformation(drawingTime, t, 1f);//应用Animation的效果到Transformation上
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
...
} else {
invalidationTransform = t;
}
if (more) {//如果动画没有执行完
if (!a.willChangeBounds()) {//动画效果不会导致绘制区域大于原区域时,继续invalidate即可
...
parent.invalidate(mLeft, mTop, mRight, mBottom);
} else {//否则要计算新的region,进行invalidate
...
final RectF region = parent.mInvalidateRegion;
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);//根据Transformation获取新的region
...
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));//invalidate新的region
}
}
return more;
}
由代码可知,view动画,就是在ViewGroup调用draw方法绘制child时,将child的Animation的参数,应用到Transformation上,然后将Transformation的效果应用到canvas上进行绘制
当动画没有完毕时,通过不断调用invalidate重绘来重复这一过程
view动画实质是在draw方法里完成,并不会影响view的measure和layout的实际大小和位置
Android还提供了一直布局动画,其是用在ViewGroup上的,可以给这个ViewGroup的每个child指定统一的动画,实现一定的效果,最常见的比如在ListView的items的出现效果
//在res/anim文件夹下定义
//指定每个child的动画在上一个child动画执行了多长时间后开始执行
//在ViewGroup中声明
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layoutAnimation="@anim/layout_translate_in"
android:orientation="vertical"/>
//从xml加载出LayoutAnimationController对象
LayoutAnimationController controller = AnimationUtils.loadLayoutAnimation(this, R.anim.layout_translate_in);
//设置到ViewGroup中
container.setLayoutAnimation(controller);
//开启LayoutAnimation
container.startLayoutAnimation();
ViewGroup保存LayoutAnimationController对象,无论是从属性中解析出来的还是通过setter设置的
在ViewGroup的dispatchDraw方法绘制children时,为每个child设置controller里指定的Animation对象进行执行,controller里控制执行顺序、delay等流程
由(2)可知,布局动画实质也是在绘制时,为每个child应用了统一的view动画而已
ViewGroup只在第一次绘制时会启动布局动画,之后会取消掉相应flag,如果想要多次执行,可以手动调用startLayoutAnimation()
//ViewGroup解析
case R.styleable.ViewGroup_layoutAnimation:
int id = a.getResourceId(attr, -1);
if (id > 0) {
setLayoutAnimation(AnimationUtils.loadLayoutAnimation(mContext, id));//解析,设置
}
//代码设置
public void setLayoutAnimation(LayoutAnimationController controller) {
mLayoutAnimationController = controller;
if (mLayoutAnimationController != null) {
mGroupFlags |= FLAG_RUN_ANIMATION;//加入标识位
}
}
//手动执行动画
public void startLayoutAnimation() {
if (mLayoutAnimationController != null) {
mGroupFlags |= FLAG_RUN_ANIMATION;//加入标识位
requestLayout();//重新布局绘制
}
}
通过AnimationUtils.loadLayoutAnimation()解析xml生成LayoutAnimationController对象
通过setLayoutAnimation设置到ViewGroup中,并加入FLAG_RUN_ANIMATION标识位,后续绘制时使用
手动启动动画时,requestLayout导致重新布局绘制
//dispatchDraw()
if ((flags & FLAG_RUN_ANIMATION) != 0 && canAnimate()) {//需要有FLAG_RUN_ANIMATION标识并且设置了controller对象才会进行动画
final boolean buildCache = !isHardwareAccelerated();
for (int i = 0; i < childrenCount; i++) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE) {
final LayoutParams params = child.getLayoutParams();
attachLayoutAnimationParameters(child, params, i, childrenCount);将view的参数(index,count等)设置params里,用于计算每个view的动画参数
bindLayoutAnimation(child);//将Animation绑定到每个view上
}
}
...
controller.start();//开启动画
mGroupFlags &= ~FLAG_RUN_ANIMATION;//取消掉标识位,之后不会再启动动画(除非手动启动)
mGroupFlags &= ~FLAG_ANIMATION_DONE;
...
}
private void bindLayoutAnimation(View child) {
Animation a = mLayoutAnimationController.getAnimationForView(child);//根据params拿到布局动画里设置的Animation对象
child.setAnimation(a);//设置给view
}
在ViewGroup调用dispatchDraw绘制每个child时,会根据是否有FLAG_RUN_ANIMATION和设置的controller对象来启动动画
启动动画时,其实就是把指定的Animation对象设置给每个child,其实本质就是给每个view设置view动画
在设置布局动画时会加入FLAG_RUN_ANIMATION标识,只在ViewGroup第一次绘制时开启动画,之后会取消掉标识,后续再想启动布局动画,可以手动startLayoutAnimation()
上面说到的view动画是基于view的,且在绘制时改变view的展示效果
android提供的属性动画其实是一个变化过程,而与view无依赖关系,我们可以通过一个变化过程来完成自己想要的任何变化效果,包括了view,非常好使用
//1.值动画
ValueAnimator animator = ValueAnimator.ofInt(0, 100);
animator.setInterpolator(new OvershootInterpolator());//加速器
animator.setRepeatCount(ValueAnimator.INFINITE);//重复次数
animator.setRepeatMode(ValueAnimator.REVERSE);//重复模式
animator.setStartDelay(1000);//开始延迟
animator.setDuration(1000);//duration
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {//更新监听
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int value = (int) animation.getAnimatedValue();//拿到当前值使用
ViewGroup.LayoutParams params = view.getLayoutParams();
params.width = params.height = value;
view.setLayoutParams(params);
}
});
animator.start();//开启动画
//2.对象动画(属性)
//或用"translationX"代替,确保有setTranslationX(float)方法
ObjectAnimator animator = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0, 500);
animator.setAutoCancel(true);//API 18以上,当有其他处理该target的相应property的animator时,自动cancel掉这个
animator.start();
//3.组合动画
//(1).通过多个PropertyValuesHolder组合(同时执行)
PropertyValuesHolder tranXHolder = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0, 500);
PropertyValuesHolder rotationXHolder = PropertyValuesHolder.ofFloat(View.ROTATION_X, 0, 360);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(view, tranXHolder, rotationXHolder);
animator.start();
//(2).通过AnimatorSet(控制顺序)
ObjectAnimator transAnim = ObjectAnimator.ofFloat(view, View.TRANSLATION_X, 0, 500);
ObjectAnimator rotationAnim = ObjectAnimator.ofFloat(view, View.ROTATION, 0, 360);
ObjectAnimator alphaAnim = ObjectAnimator.ofFloat(view, View.ALPHA, 1, 0);
animatorSet = new AnimatorSet();
//同步执行
//animatorSet.playTogether(transAnim, rotationAnim, alphaAnim);
//顺序执行
//animatorSet.playSequentially(transAnim, rotationAnim, alphaAnim);
//流程控制(在alpha执行完成后,trans和rotation一起执行)
//animatorSet.play(transAnim).with(rotationAnim).after(alphaAnim);
//一个链式调用以play为基点展开,假如如果需要多个after或before,可以使用多个链式调用(同一个animator对象会当作一个)
animatorSet.play(transAnim).after(rotationAnim);
animatorSet.play(rotationAnim).after(alphaAnim);
//AnimatorSet的duration会覆盖每个Animator的duration
animatorSet.setDuration(1000);
animatorSet.start();
//4.View提供的简易动画(实质使用的也是属性动画ValueAnimator)
//x轴平移到500、旋转到360度,同时执行
view.animate().translationX(500).rotation(360).setDuration(1000).start();
ValueAnimator:一个值变化动画,模拟了值的变化过程,我们可以拿到任意时刻的变化值去完成我们自己的效果
ObjectAnimator:对象属性变化动画,可以设置一个target和property,即把值的变化过程,通过property对象应用到target上,比如view的动画操作,就变的易如反掌
AnimatorSet:管理多个Animator的管理类,可以控制多个动画的执行顺序等各种方面
PropertyValuesHolder:是Animator类真正构建动画信息的类,所以我们可以向一个Animator类里添加多个PropertyValuesHolder对象,来实现多个动画同时执行的效果
ViewPropertyAnimator:View类提供的一个管理View动画的管理类,内部会封装view的一些基本动画操作,如平移、缩放等,我们可以通过简易api创建简单的动画效果执行,执行时本质用的也是ValueAnimator类实现
//在res/animator文件夹下定义
<animator
android:valueFrom="0"
android:valueTo="100" />
set>
//代码调用
Animator animator = AnimatorInflater.loadAnimator(this, R.animator.alpha_animator);
通过工具类AnimatorInflater.loadAnimator方法可以解析生成Animator对象
通过创建(有静态工厂方法)Animator类来设置动画的各种参数,包括duration、repeat、interpolator等,还有值变化参数
所有的值变化参数会交由PropertyValuesHolder的静态工厂方法,构建出相应的PropertyValuesHolder对象,该类是真正用于管理、计算、更新值变化的类
PropertyValuesHolder内部,会将传入的值变化参数,封装成一个个相应的KeyFrame对象,维护在一个相应的KeyFrameSet对象中,保存下来,获取当前值时使用
KeyFrameSet在获取当前值参数时,会使用维护的KeyFrame列表,计算得到当前的值
AnimationHandler类为线程单例类,其内部使用的是一个线程单例类Choreographer,用来绑定当前线程looper,发送消息,执行回调的,回调的callback就是AnimationFrameCallback类,Animator实现了该接口
在动画开始(start())时,会使用AnimationHandler发送小心,callback为Animator自己,每次回调时,根据当前时间比例fraction,计算当前值,然后继续发送消息,直到动画完成
这里我们先以创建一个简单的ValueAnimator为例,逐步分析下它的创建、时间变化、值更新过程:
ValueAnimator animator = ValueAnimator.ofInt(0,100);
//静态工厂方法创建
public static ValueAnimator ofInt(int... values) {
ValueAnimator anim = new ValueAnimator();
anim.setIntValues(values);
return anim;
}
//将这些值变化,生成Holder对象保存
public void setIntValues(int... values) {
...
setValues(PropertyValuesHolder.ofInt("", values));
mInitialized = false;//flag
}
//Holder中静态工厂方法生成相应的Holder对象
public static PropertyValuesHolder ofInt(String propertyName, int... values) {
return new IntPropertyValuesHolder(propertyName, values);
}
//构建Holder时,将values构建成List对象,保存到KeyFrameSet对象中
public IntPropertyValuesHolder(String propertyName, int... values) {
super(propertyName);//保存property相关信息
setIntValues(values);//设置值变化参数
}
public void setIntValues(int... values) {
mValueType = int.class;
mKeyframes = KeyFrameSet.ofInt(values);//生成相应的KeyFrameSet对象
}
//静态工厂方法生成KeyFrameSet对象
public static KeyframeSet ofInt(int... values) {
int numKeyframes = values.length;
IntKeyframe keyframes[] = new IntKeyframe[Math.max(numKeyframes,2)];//将每个阶段的值变化保存为KeyFrame对象
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]);//第一帧fraction为0
for (int i = 1; i < numKeyframes; ++i) {
keyframes[i] =
(IntKeyframe) Keyframe.ofInt((float) i / (numKeyframes - 1), values[i]);//每一帧fraction为index/(count-1),按比例分
}
}
return new IntKeyframeSet(keyframes);//将这些值变化帧保存到KeyFrameSet中
}
//静态工厂方法生成KeyFrame对象
public static Keyframe ofInt(float fraction, int value) {
return new IntKeyframe(fraction, value);
}
IntKeyframe(float fraction, int value) {
mFraction = fraction;//保存fraction
mValue = value;//保存value
...
}
//start()启动动画
private void start(boolean playBackwards) {
...
//发起回调,进行下一次动画变化
AnimationHandler animationHandler = AnimationHandler.getInstance();
//添加Animator为callback,进行下次动画变化
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
if (mStartDelay == 0 || mSeekFraction >= 0) {
startAnimation();//开启动画
if (mSeekFraction == -1) {
setCurrentPlayTime(0);//执行第一帧
} else {
setCurrentFraction(mSeekFraction);
}
}
}
//添加回调,发起消息
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);//使用Choreographer,通过handler发起message,回调到FrameCallback
}
if (!mAnimationCallbacks.contains(callback)) {
mAnimationCallbacks.add(callback);//添加callback
}
...
}
//FrameCallback处理
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
doAnimationFrame(getProvider().getFrameTime());//执行当前动画变化,进行更新
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);//继续发起message,回调到自己,直到所有动画执行完毕,mAnimationCallbacks就为空集合了,就不用发起message了
}
}
};
值更新
//1.开始动画
private void start(boolean playBackwards) {
...
//加入回调,绘制每一帧
AnimationHandler animationHandler = AnimationHandler.getInstance();
animationHandler.addAnimationFrameCallback(this, (long) (mStartDelay * sDurationScale));
//立即开始,绘制第一帧或指定帧
if (mStartDelay == 0 || mSeekFraction >= 0) {
startAnimation();//开始动画,初始化一些属性
if (mSeekFraction == -1) {
setCurrentPlayTime(0);//绘制第一帧,时间相当于0(刚开始)
} else {
setCurrentFraction(mSeekFraction);
}
}
}
public void setCurrentPlayTime(long playTime) {
float fraction = mDuration > 0 ? (float) playTime / mDuration : 1;//根据duration计算指定时间的fraction
setCurrentFraction(fraction);//调用该方法可以从任一时间点执行
}
public void setCurrentFraction(float fraction) {
...
//校准fraction
final float currentIterationFraction = getCurrentIterationFraction(fraction);
//调用该方法进行统一的更新步骤
animateValue(currentIterationFraction);
}
//2.callback回调doAnimationFrame()方法,绘制每一帧
public final void doAnimationFrame(long frameTime) {
AnimationHandler handler = AnimationHandler.getInstance();
if (mLastFrameTime == 0) {
//首次绘制(有开始延迟)
handler.addOneShotCommitCallback(this);
if (mStartDelay > 0) {
startAnimation();
}
...
}
mLastFrameTime = frameTime;
//处理暂停、重启等状态
if (mPaused) {
mPauseTime = frameTime;
handler.removeCallback(this);
return;
} else if (mResumed) {
mResumed = false;
...
handler.addOneShotCommitCallback(this);
}
//根据当前时间,调用animateBasedOnTime()方法计算fraction
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
//处理结束动画
if (finished) {
endAnimation();
}
}
boolean animateBasedOnTime(long currentTime) {
boolean done = false;
if (mRunning) {
//根据duration、repeat,计算当前时间的fraction
final long scaledDuration = getScaledDuration();
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
final float lastFraction = mOverallFraction;
final boolean newIteration = (int) fraction > (int) lastFraction;
final boolean lastIterationFinished = (fraction >= mRepeatCount + 1) &&
(mRepeatCount != INFINITE);
...
mOverallFraction = clampFraction(fraction);
//校准fraction,最终也调用到animateValue()方法统一处理fraction变化
float currentIterationFraction = getCurrentIterationFraction(mOverallFraction);
animateValue(currentIterationFraction);
}
return done;
}
由代码可知,无论是动画刚开始第一帧,还是callback回调绘制每一帧,都会根据当前时间和动画起始时间以及repeat、duration等信息,算出当前的时间比例fraction,然后统一调用animateValue(fraction)进行动画更新
在callback回调到doAnimationFrame()方法绘制每一帧时,会实时处理动画的各种状态,比如pause,resume,end等
开始动画时,会进行一些属性的初始化,即调用initAnimation方法,这个方法会初始化PropertyValuesHolder等重要属性,在具体更新动画前,我们要看一下,究竟需要初始化哪些属性:
initAnimation
//1.ValueAnimator
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();//调用所有PropertyValuesHolder的init方法初始化
}
mInitialized = true;
}
}
//2.ObjectAnimator
void initAnimation() {
if (!mInitialized) {
final Object target = getTarget();
if (target != null) {
final int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setupSetterAndGetter(target);//调用所有PropertyValuesHolder的该方法,进行属性setter/getter方法的反射初始化
}
}
super.initAnimation();//super
}
}
//3.PropertyValuesHolder的init方法
void init() {
//初始化TypeEvaluator
if (mEvaluator == null) {
mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
(mValueType == Float.class) ? sFloatEvaluator :
null;
}
//将Evaluator传递到KeyFrameSet中使用
if (mEvaluator != null) {
mKeyframes.setEvaluator(mEvaluator);
}
}
//4.PropertyValuesHolder初始化setter/getter
//FloatPropertyValuesHolder.setupSetter
void setupSetter(Class targetClass) {
//传递进来的就是Property对象
if (mProperty != null) {
return;
}
// Check new static hashmap for setter method
synchronized (sJNISetterPropertyMap) {
//get in cache
...
if (!wasInMap) {
//根据指定的propertyName生成反射setter名:setXxx
String methodName = getMethodName("set", mPropertyName);
try {
//反射查找target类中的该方法Method对象
mJniSetter = nGetFloatMethod(targetClass, methodName);
}
//cache
...
}
}
...
}
由代码可知,ValueAnimator初始化主要是初始化PropertyValuesHolder,主要是初始化TypeEvaluator,用于计算值变化
ObjectAnimator除了会处理上述属性,还会处理property相关的内容,正如前面所说,ObjectAnimator可以指定Property对象或者propertyName,来更新target,当使用Property对象时不作处理,而当使用propertyName时,就需要根据这个name,反射将相应的Method找到,更新时用其来更新target
说完了初始化,下面就来看看,当拿到fraction后,如何应用到动画上的:
update
//1.ValueAnimator
void animateValue(float fraction) {
fraction = mInterpolator.getInterpolation(fraction);//Interpolator变速器计算当前速率下的fraction
mCurrentFraction = fraction;
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].calculateValue(fraction);//使用fraction,更新每个PropertyValuesHolder的当前状态值
}
//listener->update
if (mUpdateListeners != null) {
int numListeners = mUpdateListeners.size();
for (int i = 0; i < numListeners; ++i) {
mUpdateListeners.get(i).onAnimationUpdate(this);
}
}
}
//2.ObjectAnimator
void animateValue(float fraction) {
...
super.animateValue(fraction);//super
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].setAnimatedValue(target);//更新完每个PropertyValuesHolder的状态值后,将每个状态应用到target上
}
}
//3.应用到target上
//FloatPropertyValueHolder
void setAnimatedValue(Object target) {
//直接使用指定的property对象进行应用
if (mFloatProperty != null) {
mFloatProperty.setValue(target, mFloatAnimatedValue);
return;
}
...
//反射调用Method,应用到target上
if (mSetter != null) {
try {
mTmpValueArray[0] = mFloatAnimatedValue;
mSetter.invoke(target, mTmpValueArray);
} catch (InvocationTargetException e) {
Log.e("PropertyValuesHolder", e.toString());
} catch (IllegalAccessException e) {
Log.e("PropertyValuesHolder", e.toString());
}
}
}
//4.Property
//View.TRANSLATION_X
public static final Property<View, Float> TRANSLATION_X = new FloatProperty<View>("translationX") {
@Override
public void setValue(View object, float value) {
object.setTranslationX(value);//直接调用view的setTranslationX方法进行移动
}
...
};
由代码可知,更新值时,Animator会首先用我们指定的Interpolation变速器,计算当前速率下fraction的真实值
然后使用当前fraction去更新所有PropertyValuesHolder,即所有动画过程的当前值(calculateValue)
最后通知Listener进行更新,比如ValueAnimator中,我们通过listener的update方法来拿到当前真实值,来进行处理
而对于ObjectAnimator来说,我们给其设置的Property对象,或者通过propertyName反射出来的setter方法,在计算完所有PropertyValuesHolder值后,就可以直接应用到target上了,不需要我们自己处理
下面就来看看,PropertyValuesHolder是如何根据fraction来更新状态值的:
calculateValue
//1.calculateValue
//FloatPropertyValuesHolder
void calculateValue(float fraction) {
//直接使用的是Holder维护的KeyFrameSet获取最新值,并保存下来
mFloatAnimatedValue = mFloatKeyframes.getFloatValue(fraction);
}
//2.KeyFrameSet.getValue
//FloatKetFrameSet
public float getFloatValue(float fraction) {
//只有两帧的情况、fraction大于1的情况等
//普通情况
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);//前一帧
for (int i = 1; i < mNumKeyframes; ++i) {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);//后一帧
if (fraction < nextKeyframe.getFraction()) {//找到当前fraction位于之间的两个帧
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());//根据当前fraction,计算在两帧之间的fraction
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
// Apply interpolator on the proportional duration.
if (interpolator != null) {
intervalFraction = interpolator.getInterpolation(intervalFraction);
}
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();//用TypeEvaluator,计算preValue至nextValue之间,比例为fraction的值
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
由代码可知,PropertyValuesHolder计算值,其实就是通过一开始创建的帧集合(KeyFrameSet)去计算当前值
KeyFrameSet使用其维护的List,首先通过当前fraction,找到所在区间的前后两个KeyFrame,然后计算fraction在其内部的intervalFraction
通过TypeEvaluator,得到preValue和nextValue之间,比例为intervalFraction的值,就是当前的状态值
至此,一次动画值的更新就完成了,每次callback回调改变一次,连起来的绘制效果,就是动画
以上就是android提供的View动画和属性动画的使用与原理,简单做个对比:
使用上,两者提供的基本功能都差不多,也都可以通过java代码和xml两种形式定义和生成
在动画集合上来说,AnimationSet和AnimatorSet都可以管理多个动画,但是AnimatorSet支持顺序控制等方式,可以更加灵活的组合动画
View动画是与View强行绑定的,只能用于View;而属性动画是一个动画过程的抽象,既可以方便的完成View动画的效果,也可以自己定义任何的动画效果
View动画是基于draw过程的,即动画效果是在View的draw过程时候完成的,只改变了绘制区域,而并没有改变View的真实位置和大小以及相应区域,容易造成一定的混乱;而属性动画改变的是View真实的位置和大小
View动画支持了布局动画,属性动画没有,可以通过自己的逻辑控制来实现
综上所述,其实属性动画几乎可以支持全部情况下的动画,可以替代View动画,也是现在用的最多的。