补间动画TweenAnimation
Animation 动画的扩展性很高,系统只是简单的为我们封装了几个基本的动画:平移、旋转、透明度、缩放等等。它们都继承自 Animation 类,然后实现了 applyTransformation() 方法,在这个方法里通过 Transformation 和 Matrix 实现各种各样炫酷的动画。
- TranslateAnimation 位移动画
- ScaleAnimation 缩放动画
- RotateAnimation 旋转动画
- AlphaAnimation 透明度动画
示例
Button mButton = findViewById(R.id.button1);
Animation translateAnimation = new TranslateAnimation(0,500,0,500);
translateAnimation.setDuration(3000);
mButton.startAnimation(translateAnimation);
源码解析
startAnimation初始化操作
- 初始化操作,绑定view和animation
- view.invalidate() -> viewGroup.invalidateChild() -> ViewRootImpl.invalidateChildInParent ->ViewRootImpl.scheduleTraversals()
ViewRootImpl是decorView的parent
当调用了 View.startAniamtion() 之后,动画并没有马上就被执行,这个方法只是做了一些变量初始化操作,接着将 View 和 Animation 绑定起来,然后调用重绘请求操作,内部层层寻找 mParent,最终走到 ViewRootImpl 的 scheduleTraversals 里发起一个遍历 View 树的请求,这个请求会在最近的一个屏幕刷新信号到来的时候被执行,调用 performTraversals 从根布局 DecorView 开始遍历 View 树,进行测量、布局、绘制。
//View.java
//启动动画
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME); // -1
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
//绑定View和Animation
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
}
protected void invalidateParentCaches() {
if (mParent instanceof View) {
((View) mParent).mPrivateFlags |= PFLAG_INVALIDATED;
}
}
view绘制时应用动画效果
View绘制流程
view.draw()
- 绘制背景
- 绘制自己
- dispatchDraw() ,如果有子 View,将绘制事件通知给子 View。ViewGroup 重写了 dispatchDraw(),调用了 drawChild(),而 drawChild() 调用了子 View 的 draw(Canvas, ViewGroup, long),而这个方法又会去调用到 draw(Canvas) 方法,所以这样就达到了遍历的效果。
当动画如果还没执行完,就会再调用 invalidate() 方法,层层通知到 ViewRootImpl 再次发起一次遍历请求,当下一帧屏幕刷新信号来的时候,再通过 performTraversals() 遍历 View 树绘制时,该 View 的 draw 收到通知被调用时,会再次去调用 applyLegacyAnimation() 方法去执行动画相关操作,包括调用 getTransformation() 计算动画进度,调用 applyTransformation() 应用动画。
//View.java
public void draw(Canvas canvas) {
// Step 4, draw the children
dispatchDraw(canvas); //ViewGroup实现了dispatchDraw
}
//ViewGroup实现了dispatchDraw又调用child.draw
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
}
//省略。。。
return more;
}
private boolean applyLegacyAnimation(ViewGroup parent, long drawingTime,
Animation a, boolean scalingRequired) {
final Transformation t = parent.getChildTransformation();
//计算动画执行进度并应用动画效果
boolean more = a.getTransformation(drawingTime, t, 1f);
//如果需要更多动画效果,则继续注册监听屏幕刷新信号,重绘view
if (more) {
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
return more;
}
//ViewGroup.java
protected void dispatchDraw(Canvas canvas) {
boolean more = false;
final long drawingTime = getDrawingTime();
for (int i = 0; i < childrenCount; i++) {
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE || child.getAnimation() != null) {
more |= drawChild(canvas, child, drawingTime);
}
}
}
//Animation.java
public boolean getTransformation(long currentTime, Transformation outTransformation) {
if (mStartTime == -1) { //记录动画第一帧的时间
mStartTime = currentTime;
}
final long startOffset = getStartOffset(); //第一帧动画的偏移量
final long duration = mDuration; //动画时长
float normalizedTime;
if (duration != 0) { //计算进度 0-1之前
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; //是否需要执行更多动画效果
//校正,确保在0.0-1.0之间
if (!mFillEnabled) normalizedTime = Math.max(Math.min(normalizedTime, 1.0f), 0.0f);
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
mStarted = true;
//根据插值器计算实际的动画进度
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//应用动画效果
applyTransformation(interpolatedTime, outTransformation);
}
//省略,计算动画是否执行完成,more为true,表示need more
return mMore;
}
补间动画只影响显示效果,不会改变view 属性
以TranslateAnimation为例,applyTransformation里,只是对matrix进行改变,没有改变view的属性。
也就是是说绘制画布进行了平移、缩放、旋转等操作,但view属性没有改变。
public class TranslateAnimation extends Animation {
protected float mFromXValue = 0.0f;
protected float mToXValue = 0.0f;
protected float mFromYValue = 0.0f;
protected float mToYValue = 0.0f;
public TranslateAnimation(float fromXDelta, float toXDelta, float fromYDelta, float toYDelta) {
mFromXValue = fromXDelta;
mToXValue = toXDelta;
mFromYValue = fromYDelta;
mToYValue = toYDelta;
}
@Override
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);
}
}
总结
-
首先,当调用了 View.startAnimation() 时动画并没有马上就执行,而是通过 invalidate() 层层通知到 ViewRootImpl 发起一次遍历 View 树的请求,而这次请求会等到接收到最近一帧到了的信号时才去发起遍历 View 树绘制操作。
-
从 DecorView 开始遍历,绘制流程在遍历时会调用到 View 的 draw() 方法,当该方法被调用时,如果 View 有绑定动画,那么会去调用applyLegacyAnimation(),这个方法是专门用来处理动画相关逻辑的。
-
在 applyLegacyAnimation() 这个方法里,如果动画还没有执行过初始化,先调用动画的初始化方法 initialized(),同时调用 onAnimationStart() 通知动画开始了,然后调用 getTransformation() 来根据当前时间计算动画进度,紧接着调用 applyTransformation() 并传入动画进度来应用动画。
-
getTransformation() 这个方法有返回值,如果动画还没结束会返回 true,动画已经结束或者被取消了返回 false。所以 applyLegacyAnimation() 会根据 getTransformation() 的返回值来决定是否通知 ViewRootImpl 再发起一次遍历请求,返回值是 true 表示动画没结束,那么就去通知 ViewRootImpl 再次发起一次遍历请求。然后当下一帧到来时,再从 DecorView 开始遍历 View 树绘制,重复上面的步骤,这样直到动画结束。
-
有一点需要注意,动画是在每一帧的绘制流程里被执行,所以动画并不是单独执行的,也就是说,如果这一帧里有一些 View 需要重绘,那么这些工作同样是在这一帧里的这次遍历 View 树的过程中完成的。每一帧只会发起一次 perfromTraversals() 操作。