补间动画和属性动画的区别以及实践
目录
一 、需求
二 、动画实现代码
补间动画代码
属性动画代码
三 、原理分析
四 、总结
一 、需求
需要做一个倒计时3 2 1 的动画(并且进入后台时动画不会被暂停),我们知道可以通过安卓的补间动画和属性动画来做。
时间着急的直接看结果,不急的可以慢慢往下看原理。
-
补间动画是跟View绑定在一起的,App进入后台时,View自然就会停止绘画等一切活动(包括动画),那么自然动画也就被停止了。
-
这个需求点就只能通过属性动画来做,因为属性动画的更新是通过
AnimationHandler
来进行,自然就不会收到Activity,或者View的生命周期影响。
二 、动画实现代码
我们可以看下,补间动画运行效果。可以看到,当程序退入后台,等待几秒再回来时,动画又开始继续执行。
属性动画:当程序退入后台,等待几秒再回来时,动画已执行完毕。
补间动画代码
public static 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 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
中的一个属性来设置
@Overrideprotected 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,流程如下图: