前言
属性动画是Android API 11加入的新特性。属性动画可以作用于任意对象属性(具有get/set方法的属性),不仅仅可以作用于View对象。从这个特性来看,属性动画的应用场景就会变得多种多样。
属性动画应用非常简单,通过ObjectAnimator、ValueAnimator、AnimatorSet就可以实现动画或者动画组合。根据属性动画的应用,我们带着问题去分析原理。我们知道使用的动画会在一定的时间内执行完毕,那么属性动画是如何更新值的?带着这个问题我们在看源码。
属性动画原理
val animator: ValueAnimator = ValueAnimator.ofInt(0, 100);
animator.start()
上面的代码是一个非常简单的属性动画的实现,代码的作用是在300ms(属性动画默认时间)将值从0增加到100。
private void start(boolean playBackwards) {
//...
mStarted = true;
mPaused = false;
mRunning = false;
mAnimationEndRequested = false;
mLastFrameTime = -1;
mFirstFrameTime = -1;
mStartTime = -1;
addAnimationCallback(0);
//...
if (mStartDelay == 0 || mSeekFraction >= 0 || mReversing) {
startAnimation();
if (mSeekFraction == -1) {
setCurrentPlayTime(0);
} else {
setCurrentFraction(mSeekFraction);
}
}
//...
}
我们从start()方法入手,可以看到会调用start(boolean playBackwards)方法。在这个方法里,可以看到有个startAnimation()方法,继续分析。
private void startAnimation() {
mAnimationEndRequested = false;
initAnimation();
mRunning = true;
if (mSeekFraction >= 0) {
mOverallFraction = mSeekFraction;
} else {
mOverallFraction = 0f;
}
if (mListeners != null) {
notifyStartListeners();
}
}
startAnimation()方法中逻辑很简单,先看一个initAnimation()方法,这个方法做了一些初始化工作。
void initAnimation() {
if (!mInitialized) {
int numValues = mValues.length;
for (int i = 0; i < numValues; ++i) {
mValues[i].init();
}
mInitialized = true;
}
}
可以看到这里初始化了mValues[i].init()。这个mValues变量是PropertyValuesHolder数组。它持有了属性动画对应的属性,包括属性setter、getter方法等信息,主要作用是更新属性的值。
接下去继续分析notifyStartListeners()方法,这里是回调AnimatorListener的onAnimationStart()方法。
到这里可以发现,startAnimation()里面没有执行动画的逻辑,只是做了一些初始化工作和接口通知。回过头在来看start()方法,可以看到还有一个addAnimationCallback(0)方法。
private void addAnimationCallback(long delay) {
if (!mSelfPulse) {
return;
}
getAnimationHandler().addAnimationFrameCallback(this, delay);
}
这里代码将会在AnimationHandler类中执行。接着跟代码。
public void addAnimationFrameCallback(final AnimationFrameCallback callback, long delay) {
if (mAnimationCallbacks.size() == 0) {
getProvider().postFrameCallback(mFrameCallback);
}
//...
}
public void postFrameCallback(Choreographer.FrameCallback callback) {
mChoreographer.postFrameCallback(callback);
}
这里可以看到一个熟悉的角色mChoreographer,这个变量就是Choreographer。也就是Android中提供屏幕刷新与Android页面更新的重要角色。大家在学习屏幕刷新机制时都会看到Choreographer的身影。
这里Choreographer将回调注册到了自己的工作队列中(具体内部实现大家可以看屏幕刷新相关的文章)。那么接下来的工作流程就是在屏幕刷新时会通过Choreographer来回调动画接口。
private final Choreographer.FrameCallback mFrameCallback = new Choreographer.FrameCallback() {
@Override
public void doFrame(long frameTimeNanos) {
// 1
doAnimationFrame(getProvider().getFrameTime());
// 2
if (mAnimationCallbacks.size() > 0) {
getProvider().postFrameCallback(this);
}
}
};
这个接口就是注册到Choreographer的回调。这里先看第二步的逻辑,如果mAnimationCallbacks.size() >0,就继续注册接口。这也很好理解,这个说明还有动画需要执行,所以继续注册回调到Choreographer中。
接下来看第一步的逻辑。跟代码进去,中间可能省略部分方法,具体细节大家可以自行阅读源码。
这一步将回调进ValueAnimator中调用了doAnimationFrame()方法。
public final boolean doAnimationFrame(long frameTime) {
//...
final long currentTime = Math.max(frameTime, mStartTime);
boolean finished = animateBasedOnTime(currentTime);
if (finished) {
endAnimation();
}
return finished;
}
这里方法就处理了每一帧的事情,接下来的过程在animateBasedOnTime()方法中实现。
boolean animateBasedOnTime(long currentTime) {
// ...
mOverallFraction = clampFraction(fraction);
float currentIterationFraction = getCurrentIterationFraction(
mOverallFraction, mReversing);
animateValue(currentIterationFraction);
}
这个方法最终的逻辑在animateValue()方法中。
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);
}
}
}
这个方法可以看到先计算的动画执行结果的值。这里是通过PropertyValuesHolder中计算的。
void calculateValue(float fraction) {
Object value = mKeyframes.getValue(fraction);
mAnimatedValue = mConverter == null ? value : mConverter.convert(value);
}
这里可以看到是通过Keyframes计算的,计算的过程也依赖了插值器的影响。Keyframes是一个接口,继承它的结果又分为IntKeyframes和FloatKeyframes,这里就相当于支持了Int和Float类型的计算。具体实现可以看IntKeyframeSet和FloatKeyframeSet.
总结
经过上面的分析,属性动画的大致工作原理也就比较清晰了。
- 在调用start()方法或,属性动画会执行一些初始化工作,并通知动画流程开始;
- 然后通过AnimationHandler将动画回调注册进Choreographer的工作队列中,一次监听屏幕刷新,直至动画结束(这里是每帧处理一次,当回调列表数量大于0时就继续注册监听)。