一、引言
从一个bug说起,有一天晚上,我想写个demo验证下动画的效果,写了几行非常基础的代码,点击run运行,纳尼,说好的动画呢?怎么一动不动,哪里写错了嘛,于是我修改了几个参数,再次运行,发现还是不生效,如此简单的三行代码,怎么会有问题?怎么也想不通,想的都快怀疑人生了。第二天去公司,让同事帮忙看下是什么原因,发现是我手动无脑关闭了手机动画。事后我想为什么自己没找到原因?一是对属性动画原理不熟悉,导致怀疑代码问题,二是对生活中的事情不主动思考。所以写这篇文章目的是理解属性动画的原理,同时向源码学习。
二、基本使用
1.平移动画
setTranslationX表示在x轴方向移动。
setTranslationY表示在Y轴方向移动。
setTranslationZ设置阴影
ObjectAnimator mFlashAnimator = ObjectAnimator.ofFloat(mLoadingLayout, "translationY", 600);
mFlashAnimator.setDuration(3000);
mFlashAnimator.start();
2.伸缩动画
setScaleX(float scaleX):x轴方向伸缩
setScaleY(float scaleY):y轴方向伸缩
ObjectAnimator animator = ObjectAnimator.ofFloat(rLayout, "scaleX", 0f, 1.5f, 2f, 1.5f, 0f, 0.5f, 0.2f, 1f);
animator.setDuration(2000);
animator.start();
3.透明度动画
setAlpha(float alpha)透明度动画
ObjectAnimator animator = ObjectAnimator.ofFloat(rLayout, "alpha", 0, 0.5f, 1.0f);
animator.setDuration(2000);
animator.start();
4.旋转动画
setRotationX(float rotationX):x方向旋转,参数是旋转度数
setRotationY(float rotationY): y轴方向旋转
setRotation(float rotation):z方向旋转
ObjectAnimator animator = ObjectAnimator.ofFloat(rLayout, "rotation", 0, 180, 0, -180, 0);
animator.setDuration(3000);
animator.start();
只要有set方法的都可以用来做属性动画,比如setX(),setBackgroundResource(),setBackgroundColor()方法等。
三、源码分析
1.创建动画对象
以ObjectAnimator.ofFloat()创建对象为例,通过静态方法创建对象,将所需的数据一次性传入,提升的内聚性,易用性。传入参数使用object类型,我觉得是提高扩展性,因为后面设置属性时用的反射,无需关注特定类型。setTarget为null,则使用weakReference弱引用持有,防止内存泄漏。setValues()->PropertyValuesHolder->KeyframeSet->Keyframe,相应创建这些对象。
public static KeyframeSet ofFloat(float... values) {
boolean badValue = false;
int numKeyframes = values.length;
FloatKeyframe keyframes[] = new FloatKeyframe[Math.max(numKeyframes,2)];
if (numKeyframes == 1) {
//只有一个值,默认是0和100%
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) {
//将values转成百分比的值,比如有5个值,转化为0,25%,50%,75%,100%
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);
}
2.计算每一帧动画要执行的距离
AnimationHandler使用ThreadLocal持有,保住同个线程只有一个对象,autoCancelBasedOn()表示之前有相同的属性动画,且可以自动取消,则取消之前的动画。重点研究addAnimationCallback(0);此方法是将动画的回调间接注册到编舞者对象Choreographer中,编舞者对象是Android绘制的最重要的参与者,控制每一帧绘制的时机,正常情况下是1s中绘制60帧,平均16ms绘制一帧数据。
private void doAnimationFrame(long frameTime) {
long currentTime = SystemClock.uptimeMillis();
final int size = mAnimationCallbacks.size();
for (int i = 0; i < size; i++) {
final AnimationFrameCallback callback = mAnimationCallbacks.get(i);
if (callback == null) {
continue;
}
if (isCallbackDue(callback, currentTime)) {
callback.doAnimationFrame(frameTime);
if (mCommitCallbacks.contains(callback)) {
getProvider().postCommitCallback(new Runnable() {
@Override
public void run() {
commitAnimationFrame(callback, getProvider().getFrameTime());
}
});
}
}
}
cleanUpList();
}
外部的回调保存在arraylist集合中,for循环依次集合中的callback,执行doAnimationFrame()方法,将时间赋值给mStartTime,这个方法用了boolean值作为方法返回值,可以用来处理回调。
public final boolean doAnimationFrame(long frameTime) {
if (mStartTime < 0) {
// First frame. If there is start delay, start delay count down will happen *after* this
// frame.
//第一次回调,将第一帧的时间赋值给mStartTime
mStartTime = mReversing
? frameTime
: frameTime + (long) (mStartDelay * resolveDurationScale());
}
// Handle pause/resume
if (mPaused) {
mPauseTime = frameTime;
removeAnimationCallback();
return false;
} else if (mResumed) {
mResumed = false;
if (mPauseTime > 0) {
// Offset by the duration that the animation was paused
mStartTime += (frameTime - mPauseTime);
}
}
if (!mRunning) {
// If not running, that means the animation is in the start delay phase of a forward
// running animation. In the case of reversing, we want to run start delay in the end.
if (mStartTime > frameTime && mSeekFraction == -1) {
// This is when no seek fraction is set during start delay. If developers change the
// seek fraction during the delay, animation will start from the seeked position
// right away.
return false;
} else {
// If mRunning is not set by now, that means non-zero start delay,
// no seeking, not reversing. At this point, start delay has passed.
mRunning = true;
startAnimation();
}
}
if (mLastFrameTime < 0) {
if (mSeekFraction >= 0) {
long seekTime = (long) (getScaledDuration() * mSeekFraction);
mStartTime = frameTime - seekTime;
mSeekFraction = -1;
}
mStartTimeCommitted = false; // allow start time to be compensated for jank
}
mLastFrameTime = frameTime;
// The frame time might be before the start time during the first frame of
// an animation. The "current time" must always be on or after the start
// time to avoid animating frames at negative time intervals. In practice, this
// is very rare and only happens when seeking backwards.
//计算当前刷新时间
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
animateBaseOnTime方法算出时间的百分比,用来计算移动的距离。关键代码
//将每次时间和第一次相比,再除以mDuration,算出百分比
final float fraction = scaledDuration > 0 ?
(float)(currentTime - mStartTime) / scaledDuration : 1f;
计算移动距离,会走插值器逻辑,然后调用FloatKeyFrameSet的getFloatValue方法,
@Override
public float getFloatValue(float fraction) {
if (fraction <= 0f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
} else if (fraction >= 1f) {
final FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 2);
final FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(mNumKeyframes - 1);
float prevValue = prevKeyframe.getFloatValue();
float nextValue = nextKeyframe.getFloatValue();
float prevFraction = prevKeyframe.getFraction();
float nextFraction = nextKeyframe.getFraction();
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
if (interpolator != null) {
fraction = interpolator.getInterpolation(fraction);
}
float intervalFraction = (fraction - prevFraction) / (nextFraction - prevFraction);
return mEvaluator == null ?
prevValue + intervalFraction * (nextValue - prevValue) :
((Number)mEvaluator.evaluate(intervalFraction, prevValue, nextValue)).
floatValue();
}
FloatKeyframe prevKeyframe = (FloatKeyframe) mKeyframes.get(0);
for (int i = 1; i < mNumKeyframes; ++i) {
FloatKeyframe nextKeyframe = (FloatKeyframe) mKeyframes.get(i);
if (fraction < nextKeyframe.getFraction()) {
final TimeInterpolator interpolator = nextKeyframe.getInterpolator();
float intervalFraction = (fraction - prevKeyframe.getFraction()) /
(nextKeyframe.getFraction() - prevKeyframe.getFraction());
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();
}
prevKeyframe = nextKeyframe;
}
// shouldn't get here
return ((Number)mKeyframes.get(mNumKeyframes - 1).getValue()).floatValue();
}
这里以ObjectAnimator.ofFloat(rLayout, "translationX", 100,200, 500)的值举例说明,在初始化时values数组会将每个value封装成FloatKeyFrame对象,保存在FloatKeyFrameSet数组里,keyFrame对象的值分别为(0,100),(0.5,200),(1,500)。在第一次刷新时间回调时,假设currentFraction为0.000453,会走fraction=0的if判断,此次的value计算prevValue + intervalFraction * (nextValue - prevValue),下次会走fraction不等于0的判断,算出这次走的值。保存到PropertyValuesHolder中。
3.反射修改属性值。
在调用start方法时,会initAnimation方法,这个方法会先走子类的方法,再走父类的方法,接着会调用setupSetterAndGetter(),接着会创建set和get的metho对象实例,采用拼接字符串的形式。然后调用setAnimatedValue(),最后调用mSetter.invoke(target, mTmpValueArray)修改控件属性的值。方法执行顺序:start()->initAnimation()->setupSetterAndGetter()->getPropertyFunction()->getMethodName()->setAnimatedValue()。
四、总结
对一个view的某个属性设置不同的时间节点,应该达到的值,注册动画回调到Choreographer对象中,每隔16ms回调一次,执行相应的距离,在规定的时间内执行完成,完成动画。本篇文章主要分析属性动画执行的原理,还有一些问题,还有待研究,如:
1.动画只能在主线程执行吗?
2.多个动画同时执行会造成卡顿嘛?
3.动画卡顿的本质原因是什么?
5.values设置多个值的意义在哪?
6.插值器和估值器的区别?
7.自定义属性动画如何实现?
这些问题的答案留在下次揭晓,敬请期待!