目录
一 、需求
需要做一个倒计时3 2 1 的动画(并且进入后台时动画不会被暂停),我们知道可以通过安卓的补间动画和属性动画来做。
时间着急的直接看结果,不急的可以慢慢往下看原理。
-
补间动画是跟View绑定在一起的,App进入后台时,View自然就会停止绘画等一切活动(包括动画),那么自然动画也就被停止了。
-
这个需求点就只能通过属性动画来做,因为属性动画的更新是通过
AnimationHandler
来进行,自然就不会收到Activity,或者View的生命周期影响。
二 、动画实现代码
我们可以看下,补间动画运行效果。可以看到,当程序退入后台,等待几秒再回来时,动画又开始继续执行。
属性动画:当程序退入后台,等待几秒再回来时,动画已执行完毕。
补间动画代码
public static <T extends TextView> void start(final T animationViewTv, final int repeatCount) {
// 设置计时
sCurCount = repeatCount;
String text = String.valueOf(sCurCount);
animationViewTv.setText(text);
animationViewTv.setVisibility(View.VISIBLE);
// 缩放渐变动画
ScaleAnimation scaleAnimation = new ScaleAnimation(
0.8f, 1.5f, 0.8f, 1.5f,
Animation.RELATIVE_TO_SELF, 0.5f,
Animation.RELATIVE_TO_SELF, 0.5f);
scaleAnimation.setRepeatCount(sCurCount - 1);
scaleAnimation.setDuration(1000);
scaleAnimation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
}
@Override
public void onAnimationEnd(Animation animation) {
}
@Override
public void onAnimationRepeat(Animation animation) {
// 减秒
--sCurCount;
// 设置文本
if (sCurCount == 0)
animationViewTv.setText(LAST_SECOND_TEXT);
else {
String text = String.valueOf(sCurCount);
animationViewTv.setText(text);
}
}
});
animationViewTv.startAnimation(scaleAnimation);
}
属性动画代码
public static <T extends TextView> void start(final T animationViewTv, final int repeatCount) {
// 设置计时
sCurCount = repeatCount;
String text = String.valueOf(sCurCount);
animationViewTv.setText(text);
animationViewTv.setVisibility(View.VISIBLE);
// 缩放
ValueAnimator scaleAnimation = ValueAnimator.ofFloat(0.8f, 1.5f, 1f);
scaleAnimation.addUpdateListener(animation -> {
animationViewTv.setScaleX((Float) animation.getAnimatedValue());
animationViewTv.setScaleY((Float) animation.getAnimatedValue());
});
scaleAnimation.setRepeatCount(sCurCount-1);
scaleAnimation.addListener(new AnimatorListenerAdapter() {
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {
// 减秒
--sCurCount;
// 设置文本
if (sCurCount == 0)
animationViewTv.setText(LAST_SECOND_TEXT);
else {
String text = String.valueOf(sCurCount);
animationViewTv.setText(text);
}
}
});
scaleAnimation.setDuration(1000);
scaleAnimation.start();
}
代码很简单,具体还得看场景选择,不过更加建议使用属性动画,因为可以有更多的可能性和变化效果。
接下来,我们具体分析一下,为什么补间动画进入后台后动画会暂停?
三 、原理分析
带着问题找答案:既然是设置Scale,那就肯定会有一个地方setScaleX
和setScaleY
首先,肯定是先从ScaleAnimation
源码中搜索有没有以上两个方法设置。很可惜,源码中没有找到,但是却找到类型的方法,如下:
提示:Matrix不仅可以用来缩放,还可以平移,旋转,也就是说补间动画的缩放、平移和旋转都是通过Matrix来设置,而Alpha则是通过Transformation
中的一个属性来设置
@Override
protected void applyTransformation(float interpolatedTime, Transformation t) {
//省略部分代码
if (mPivotX == 0 && mPivotY == 0) {
t.getMatrix().setScale(sx, sy);
} else {
t.getMatrix().setScale(sx, sy, scale * mPivotX, scale * mPivotY);
}
}
OK,既然找到了设置参数的地方,那么就肯定会有地方去getMatrix
或getAlpha
来真正的对View进行绘制。
说到绘制,那就肯定离不开View的draw方法
为了便于理解,我们从动画设置的地方入手
animationViewTv.startAnimation(scaleAnimation);
跟一下代码,主要看setAnimation方法
public void startAnimation(Animation animation) {
animation.setStartTime(Animation.START_ON_FIRST_FRAME);
setAnimation(animation);
invalidateParentCaches();
invalidate(true);
}
把传进来的animation赋值给了mCurrentAnimation,注意这个属性,会提供getAnimation
返回 mCurrentAnimation
public void setAnimation(Animation animation) {
mCurrentAnimation = animation;
//以下代码省略
}
getAnimation方法,继续看下这个方法的调用。
public Animation getAnimation() {
return mCurrentAnimation;
}
可以发现,getAnimation在View的draw方法中被调用。
注意:看这个方法的注释,这个draw方法是由ViewGroup.drawChild来调用,由子View来自己负责绘制
/**
* This method is called by ViewGroup.drawChild() to have each child view draw itself.
*
* This is where the View specializes rendering behavior based on layer type,
* and hardware acceleration.
*/
boolean draw(Canvas canvas, ViewGroup parent, long drawingTime) {
final boolean hardwareAcceleratedCanvas = canvas.isHardwareAccelerated();
boolean drawingWithRenderNode = mAttachInfo != null
&&mAttachInfo.mHardwareAccelerated
&&hardwareAcceleratedCanvas;
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();
}
if (drawingWithRenderNode) {
renderNode.setAnimationMatrix(transformToApply.getMatrix());
} else {
canvas.translate(-transX, -transY);
canvas.concat(transformToApply.getMatrix());
canvas.translate(transX, transY);
}
return more;
}
在这里我们可以发现两个点
-
第一个很明显的可以看到,draw方法中会去调用getAnimation,如果有,就会去执行
applyLegacyAnimation
,而这个方法也就是传入Transformation
参数提供给具体类(ScaleAnimation
)设置applyTransformation
方法,也就是我们设置的缩放参数。 -
第二个就是在这里获取了
getMatrix
的值来渲染到当前canvas中。其中,drawingWithRenderNode
参数是用来区分是否支持硬件加速,无论结果是什么,最终都会绘制到canvas中。
接下来,可以继续跟着applyLegacyAnimation方法,最终会调到Animation
的applyTransformation
方法
protected void applyTransformation(float interpolatedTime, Transformation t) {
}
这个方法是个空方法,很明显,他的具体实现由他的子类来实现,正好就是四个补间动画。
四 、总结
主要讲了两点
1.补间动画和属性动画的区别
2.补间动画的参数大小设置与如何显示到View,流程如下图: