> 文档中心 > 补间动画和属性动画的区别以及实践

补间动画和属性动画的区别以及实践

目录

一 、需求

二 、动画实现代码

补间动画代码

属性动画代码

三 、原理分析

四 、总结


一 、需求

需要做一个倒计时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,那就肯定会有一个地方setScaleXsetScaleY

首先,肯定是先从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,既然找到了设置参数的地方,那么就肯定会有地方去getMatrixgetAlpha来真正的对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方法,最终会调到AnimationapplyTransformation方法

protected void applyTransformation(float interpolatedTime, Transformation t) {}

这个方法是个空方法,很明显,他的具体实现由他的子类来实现,正好就是四个补间动画。

四 、总结

主要讲了两点
1.补间动画和属性动画的区别
2.补间动画的参数大小设置与如何显示到View,流程如下图: