最近项目上要实现一个动画,不过这个动画做起来比较难,一是要跟随手势,另外涉及到多个view,不能通过简单的属性动画实现,目前思路应该可以用Scene动画实现,但是又要做到跟手这就比较麻烦了。不过还好这个需求不急,趁这段时间把动画的整个框架过一遍,挖一挖动画的实现代码,说不定写完这个系列,我那个问题自然而然就解决了。
安卓动画主要分三种,补间动画、帧动画和属性动画,补间动画直接作用于view,也就是view和animation是紧密结合的,在view的绘制流程中就会判断当前view是否有animation,然后再做相应的操作,正因为和view结合的如此紧密,因此这种动画只能作用于view;帧动画就是整个动画期间不断刷新图片,这种动画需要多个图片,易导致内存占用过大的问题;属性动画是这三种动画中功能最强大的,这个动画其实相当于一个定时器,每隔一段时间就去处理我们监听的事件,由于这个间隔很短,如果在处理事件期间我们不断设置view的属性,那么看起来就像动画了,好了三种动画的基本情况就是如此,这一篇先从补间动画开始将。
系统为我们定义好了4种补间动画,translate
、scale
、rotate
、alpha
.四种动画都有各自对应的类,它们的基类为Animation
,如果我们想要定义自己的补间动画,只需要继承这个类,并且重写其applyTransformation
方法,我们来看看系统为我们定义的补间动画这个方法的实现方式:
//TranslateAnimation
@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);
}
//ScaleAnimation
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float sx = 1.0f;
float sy = 1.0f;
float scale = getScaleFactor();
if (mFromX != 1.0f || mToX != 1.0f) {
sx = mFromX + ((mToX - mFromX) * interpolatedTime);
}
if (mFromY != 1.0f || mToY != 1.0f) {
sy = mFromY + ((mToY - mFromY) * interpolatedTime);
}
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
看到上面的实现方式都是对Matrix进行操作,其实平移、旋转、缩放都是可以转换成矩阵运算的,这个矩阵位于Transformation这个类中。
关于上面动画的使用就不具体说了,还是比较简单的,还是探究一下实现原理比较好玩,第一点需要注意的是补间动画实际上不改变view的属性,效果都是画出来的,view自始至终的位置和其他属性根本没有变化,那么以这个为出发点,看安卓是如何实现的。
由ViewGroup.dispatchDraw方法切入,在这个方法中会依次遍历子view,通知子view去绘制自己,最终调用到
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime)
这个方法,这里view还没有绘制自己,view绘制自身内容是在onDraw
方法中,在这个方法中发现了和动画相关的信息:
//获取当前view的animation信息,view本身有个animation变量
final Animation a = getAnimation();
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
transformToApply = parent.getChildTransformation();
}
applyLegacyAnimation
这个方法中调用了具体动画的处理:
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) {
//第一次进入,需要对动画进行初始化,确保动画animation知道view是布局在何处的
a.initialize(mRight - mLeft, mBottom - mTop, parent.getWidth(), parent.getHeight());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
//回调动画开始接口
onAnimationStart();
}
final Transformation t = parent.getChildTransformation();
//跟进去可以看到主要是根据时间决定动画进行到什么程度,然后调用anmation类中的applyTransformation方法,计算当前的matrix,再存进transformation中
boolean more = a.getTransformation(drawingTime, t, 1f);
if (scalingRequired && mAttachInfo.mApplicationScale != 1f) {
if (parent.mInvalidationTransformation == null) {
parent.mInvalidationTransformation = new Transformation();
}
invalidationTransform = parent.mInvalidationTransformation;
a.getTransformation(drawingTime, invalidationTransform, 1f);
} else {
invalidationTransform = t;
}
//动画未进行完,那么more为true,这时就需要刷新界面了,保证动画继续进行
if (more) {
//根据当前动画确定是否改变了位置,根据改变的位置去重新刷新,不过好像一般的view只能全部刷新,surfaceview可以局部刷新,一般来说动画都会改变位置
if (!a.willChangeBounds()) {
if ((flags & (ViewGroup.FLAG_OPTIMIZE_INVALIDATE | ViewGroup.FLAG_ANIMATION_DONE)) ==
ViewGroup.FLAG_OPTIMIZE_INVALIDATE) {
parent.mGroupFlags |= ViewGroup.FLAG_INVALIDATE_REQUIRED;
} else if ((flags & ViewGroup.FLAG_INVALIDATE_REQUIRED) == 0) {
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
parent.invalidate(mLeft, mTop, mRight, mBottom);
}
} else {
if (parent.mInvalidateRegion == null) {
parent.mInvalidateRegion = new RectF();
}
final RectF region = parent.mInvalidateRegion;
//计算改变的矩形,这也是根据动画中的matrix来计算的
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
// The child need to draw an animation, potentially offscreen, so
// make sure we do not cancel invalidate requests
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
//最终调用invalidate方法,使得动画能在界面上动起来
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
写到这里我们理顺了view是如何调用animation中applyTransformation
方法的,但是动画是在哪里绘制的,这个还没找到具体代码的位置。