在上面我们所认识到得到动画中,最常见的就是插值器。我们可以为动画添加适当的插值器,让其有不同的变化。
作用
他可以根据时间流逝的百分比来计算出当前属性的百分比。
流逝时间比每次都是固定增长的,那么要怎么才能实现不同的速率效果呢?这个解决办法就是使用使用我们所介绍的插值器。根据我们输入的值,可以经过自己的变换,最终输出一个不同的值,从而达到速率变换的效果。
系统预置的插值器有:
DecelerateInterpolator 减速,开始时快然后减速
AccelerateDecelerateInterolator 先加速后减速,开始结束时慢,中间加速
AnticipateInterpolator 反向,先向相反方向改变一段再加速播放
AnticipateOvershootInterpolator 反向加超越,先向相反方向改变,再加速播放,会超出目的值然后缓慢移动至目的值
BounceInterpolator 跳跃,快到目的值时值会跳跃,如目的值100,后面的值可能依次为85,77,70,80,90,100
CycleIinterpolator 循环,动画循环一定次数,值的改变为一正弦函数:Math.sin(2* mCycles* Math.PI* input)
LinearInterpolator 线性,线性均匀改变
OvershootInterpolator超越,最后超出目的值然后缓慢改变到目的值
单是看这些文字是有点抽象,插值器的作用就如同下图所示(X是所行走的总距离):
第一幅
第二幅
从第一幅图中我们可以从X的行走的距离和时间来看,那是一个典型的匀速动画,而第二幅图,在相同的时间间隔中,X的距离不再是固定了,而是变化越来越大,最后形成了一个加速的动画效果。
而在Android的动画效果中,实现这一点的就是使用了插值器。
TypeEveluator
当然,类型插值器不会自己把这些事情做完,而做完这些事情,就需要用到 TypeEveluator ,也就是类型估值器。他的作用就是根据当前改变的百分比来改变后的属性值,也就是说TypeEvaluaton算到的才是属性的值。时间插值器计算得到当前时间点的时间流逝百分比,TypeEvaluator根据这个百分比、属性起始值、目标值来计算出当前时刻该属性的值,最后这个值被设置给View,不断地重复这个过程就形成了动画。
系统预置的有整型属性(IntEvaluator)、浮点型属性(FloatEvaluator)和Color属性(ArgbEvaluato)
从代码中查找时间插值器
要找到插值器,肯定就要从动画中入手。
ImageView ivTweenCode = (ImageView) findViewById(R.id.iv_tween_code);
AlphaAnimation alphaAnimation = new AlphaAnimation(0, 1);
alphaAnimation.setInterpolator(new AccelerateDecelerateInterpolator());
alphaAnimation.setDuration(1000);
alphaAnimation.setRepeatMode(Animation.REVERSE);
alphaAnimation.setRepeatCount(-1);
ivTweenCode.startAnimation(alphaAnimation);
上面的代码中是为一个View设置了一个 透明度变化的动画 , 最后调用了 startAnimation 来启动动画。这时View的动画就启动了。
View # startAnimation
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
在 startAnimation 中主要做了以下几个操作:
- 初始化动画开始时间
- 对View设置动画
- 刷新父类缓存
- 刷新View本身及子View
动画的实现其实就是不断对现有的视图不断的重绘。 在startAnimation中最后在向ViewGroup请求刷新视图,随后
ViewGroup就会调用dispatchDraw方法对这个View所在的区域进行重绘。对于View的重绘,最终就是调用View的draw。
View # draw
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
....
boolean more = false;
....
//获取动画信息
final Animation a = getAnimation();
if (a != null) {
//绘制动画
more = applyLegacyAnimation(parent, drawingTime, a, scalingRequired);
....
} else {
....
}
}
在draw方法内部,会获取View的动画的信息,如果设置了动画,就会调用applyLegacyAnimation来绘制动画。
View # 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) {
....
//如果设置了动画的监听,则触发对应的回调
onAnimationStart();
}
....
//通过 getTransformation 来 获取动画的相关值
boolean more = a.getTransformation(drawingTime, t, 1f);
....
if (more) {
//判断当前动画类型是否需要进行调整位置大小,然后刷新不同的区域
if (!a.willChangeBounds()) {
....
} else {
...
//获取重绘的区域
a.getInvalidateRegion(0, 0, mRight - mLeft, mBottom - mTop, region,
invalidationTransform);
parent.mPrivateFlags |= PFLAG_DRAW_ANIMATION;
//重新计算有效区域
final int left = mLeft + (int) region.left;
final int top = mTop + (int) region.top;
//更新这块区域
parent.invalidate(left, top, left + (int) (region.width() + .5f),
top + (int) (region.height() + .5f));
}
}
return more;
}
在这个方法的内部主要是对动画初始化、动画操作、界面刷新。
动画的具体实现是通过Animation中的 getTransformation 方法。
Animation # getTransformation
public boolean getTransformation(long currentTime, Transformation outTransformation,
float scale) {
mScaleFactor = scale;
return getTransformation(currentTime, outTransformation);
}
在这个方法的内部,最后就是调用了 getTransformation(long currentTime, Transformation outTransformation) 来计算和应用动画效果。
Animation # getTransformation
public boolean getTransformation(long currentTime, Transformation outTransformation) {
.....
float normalizedTime;
//计算当前时间的流逝百分比
if (duration != 0) {
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();
....
if ((normalizedTime >= 0.0f || mFillBefore) && (normalizedTime <= 1.0f || mFillAfter)) {
....
// 通过插值器获取动画的执行的百分比
final float interpolatedTime = mInterpolator.getInterpolation(normalizedTime);
//应用动画效果
applyTransformation(interpolatedTime, outTransformation);
}
....
}
在上面的方法中主要的关键就是会获取获取已流逝动画执行的百分比,然后再通过插值器来重新计算这个百分比,也就是调用了插值器的getInterpolation方法来获取当前时间的百分比,并且以此来计算当前动画的属性值。
下面我们来看看几个插值器吧
LinearInterpolator
public float getInterpolation(float input) {
return input;
}
LinearInterpolator这个插值器并没有使动画的速率发生改变。
AccelerateInterpolator
public float getInterpolation(float input) {
if (mFactor == 1.0f) {
return input * input;
} else {
return (float)Math.pow(input, mDoubleFactor);
}
}
AccelerateInterpolator是加速插值器, 会随时间的推移,变化范围就会越大。
在调用了getInterpolation之后,会继续调用动画类的applyTransformation方法来将属性应用到对应的对象中。
在 类 Animation中 applyTransformation 其实就是一个空实现
Animation # applyTransformation
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
而因为我们一开始设置的是透明度动画,所以由 AlphaAnimation 中看一下applyTransformation。
AlphaAnimation # applyTransformation
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
//通过了Matrix实现View的透明度变化
final float alpha = mFromAlpha;
t.setAlpha(alpha + ((mToAlpha - alpha) * interpolatedTime));
}
当执行完applyTransformation之后,View的属性就会发生变化,只要动画没有结束,那么这个过程会不断的重复,动画也就产生了。
Animation # setInterpolator
//为动画设置 插值器
public void setInterpolator(Interpolator i) {
mInterpolator = i;
}
在上面的代码中,插值器扮演了一个很重要的角色。我们可以为动画设置不同的插值器。不同的插值器通过一个统一的接口 getInterpolation(float input) 来修改动画的流逝时间百分比。最终可以形成不同的动态效果,而无需修改Animation中的代码。