示例

补间动画的使用很简单,如下面代码,让图片旋转360度:

1
2
3
animation = new RotateAnimation(0,360);
animation.setDuration(3000);
iv.startAnimation(animation);

那么补间动画说怎么执行的,插值器又是怎么用上的能?

动画的启动 View

从动画启动开始吧,看View的startAnimation方法:

/frameworks/base/core/java/android/view/View.java:

1
2
3
4
5
6
7
8
9
10
11
12
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
if (animation != null) {
......
}
}

这里面没几行代码,首先把动画设置给内部的一个变量,然后调用invalidate(true)方法。

这个方法会引起View的draw()方法的执行,并且是整个View的重绘。

/frameworks/base/core/java/android/view/View.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
public Animation getAnimation() {
return mCurrentAnimation;
}
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
Transformation transformToApply = null;
boolean concatMatrix = false;
//拿到动画
final Animation a = getAnimation();
//动画不为空,说明有动画要执行
if (a != null) {
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
concatMatrix = a.willChangeTransformationMatrix();
if (concatMatrix) {
mPrivateFlags3 |= PFLAG3_VIEW_IS_ANIMATING_TRANSFORM;
}
//获取Transformation,里面包换透明度和矩阵,这个Transformation的值在上面已经设置好了
transformToApply = parent.getChildTransformation();
} else {
}
//根据Transformation进行绘制。
return more;
}

调用本类的applyLegacyAnimation方法。

/frameworks/base/core/java/android/view/View.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
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());
a.initializeInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop);
if (mAttachInfo != null) a.setListenerHandler(mAttachInfo.mHandler);
onAnimationStart();
}
//注意这里也是通过这个方法拿到一个Transformation。
final Transformation t = parent.getChildTransformation();
//获取动画的变化值,并返回是否还有下一帧。
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;
}
if (more) {
//如果有下一帧,就继续刷新绘制
}
return more;
}

动画的动画值计算设置

/frameworks/base/core/java/android/view/animation/Animation.java:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
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) {
normalizedTime = ((float) (currentTime - (mStartTime + startOffset))) /
(float) duration;
}
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
......
//通过标准时间,用插值器计算插值器转换之后的值
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//调用子类实现的方法,
applyTransformation(interpolatedTime, outTransformation);
}
return mMore;
}
protected void applyTransformation(float interpolatedTime, Transformation t) {
}

首先计算标准时间。标准时间是0到1的值,表示时间的进度。通过这个进度计算动画的进度。计算方法是,先用开始时间和延迟开始时间计算动画真正的开始时间,然后用当前时间减去动画真正开始的时间,算出动画已经运行的时间。用这个时间除以动画的总运行时间久得到当前动画的进度。

插值器

插值器改变的就是改变不同时间进度上的值,时间的流逝是线性的,速度是不变的,但是插值器通过改变不同时间上动画的值,达到控制动画的目的。

默认是加速加速插值器,里面是一个余弦曲线,随着标准时间从0到1,返回的数值是先加速再减速的,动画就会先变快在变慢,第一个效果图中看的很明显。

/frameworks/base/core/java/android/view/animation/AccelerateDecelerateInterpolator.java:

1
2
3
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}

下面可以看一下线性插值器,他就原原本本的返回了标准时间,不做任何改变,所以动画就会匀速执行:

/frameworks/base/core/java/android/view/animation/LinearInterpolator.java

1
2
3
public float getInterpolation(float input) {
return input;
}

Animation的子类实现applyTransformation方法

Animation的applyTransformation方法是一个空方法,需要子类去实现。下面看一啊AlphaAnimation的实现,现获取透明度总共要变化的值,然后通过传进来的插值器计算出的进度值,算出这个时间点上透明度应该是多少,然后设置给Transformation:

/Users/sunlinlin/Documents/AndroidSourcePart/frameworks/base/core/java/android/view/animation/AlphaAnimation.java

1
2
3
4
5
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}

AlphaAnimation里给传进来的Transformation设置了alpha值。Transformation主要有两个量,一个透明度,一个是矩阵。绘制时根据这个类里面存储的量来绘制,达到动画的效果。

例如旋转动画RotateAnimation,改变的就是矩阵,先算出动画一共要旋转的角度,然后根据插值器计算的进度值算出当前时间点上应该旋转到什么角度,然后设置给Transformation。

/frameworks/base/core/java/android/view/animation/RotateAnimation.java

1
2
3
4
5
6
7
8
9
10
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
float degrees = mFromDegrees + ((mToDegrees - mFromDegrees) * interpolatedTime);
float scale = getScaleFactor();
if (mPivotX == 0.0f && mPivotY == 0.0f) {
t.getMatrix().setRotate(degrees);
} else {
t.getMatrix().setRotate(degrees, mPivotX * scale, mPivotY * scale);
}
}

这时,回到上面View的draw()方法里,就会根据后面对Transformation的设置进行画面的绘制,一帧一帧的绘制就成了动画。

这也能解释为什么补间动画不会改变控件的真正位置了,因为这个动画只是重新对空间进行了draw,改变的只是看起来的样子,所以点击事件还得点击原来的地方。

简单的自定义插值器

插值器前面说的作用就是,在决定不同时间进度上的动画进度。时间进度是从0到1,而动画进度不一定非要从0到1.

比如,就已开始设置那个动画,3秒时间从0度旋转到360度,那么正常的他的动画进度就是:时间从0到3秒,角度从0到360.

动画的进度是可以再插值器中随便设置的,大于1也没可以。

下面是例子

自定义一个插值器

1
2
3
4
5
6
public class MyInterpolator implements Interpolator {
@Override
public float getInterpolation(float input) {
return 2*input;
}
}

传进来的标准时间是0到1 ,返回的动画进度从0到2. 写的是转到360度,但是动画会从0转到720度,转两圈,速度是匀速。

1
2
3
4
animation = new RotateAnimation(0,360);
animation.setDuration(3000);
animation.setInterpolator(new MyInterpolator());
iv.startAnimation(animation);