美文网首页
Android动画 - 属性动画

Android动画 - 属性动画

作者: 郑海鹏 | 来源:发表于2019-06-16 23:28 被阅读0次

    4年前在 CSDN 上写了一篇属性动画的文章,被很多人转载过。这次准备写个动画相关的专题,就用 Kotlin 再写一次属性动画的使用吧。

    原理
    通过不断的设置一个 View 的视觉属性让其出现动画效果。 例如不断设置 View 的 scaleX 属性,就能实现在 X 轴缩放的效果。
    属性动画有多种实现方式,简单的动画可以使用 ObjectAnimator,复杂一些的用 ValueAnimator 结合 AnimatorSetPropertyValuesHolder 使用。

    其内部的原理,以 ValueAnimator 为例,每当创建一个 animator 对象并调用 start 方法之后,animator 会把自己注册到一个 AnimatorHandler 中,AnimatorHandler 是一个单例对象,里面存放着多个需要执行的动画对象。
    如果当前存在需要执行的动画,AnimatorHandler 会向 Choreographer 注册屏幕刷新信号的监听。
    当接收到屏幕的刷新信号之后,AnimatorHandler 就会遍历属性动画列表,调用每一个动画的 doAnimaionrFrame 方法。
    动画内部再通过插值器,计算出当前时刻的动画值。
    动画当前帧计算完毕后,如果还有后续帧,则再将动画注册到 AnimatorHandler 中。
    同样的,AnimatorHandler 在遍历完所有属性动画后,如果还有属性动画,则再注册屏幕刷新信号的监听。


    1. 使用 ObjectAnimator 实现简单动画

    ObjectAnimator.gif

    这种简单的动画,使用 ObjectAnimator 就可以了:

    // 给 view 添加一个沿 X 轴缩放的动画。缩放比例从 1 到 0。
    ObjectAnimator.ofFloat(view, "scaleX", 1F, 0F).apply {
        duration = 1000
    
        // 设置动画完毕后再倒回去, repeatMode 和 repeatCount 需要都设置才有效果
        repeatMode = ObjectAnimator.REVERSE
        repeatCount = 1
    }.start()
    

    ObjectAnimator 有多个 ofXXX 方法,例如 ofIntofFloatofObject 等。具体使用哪个是第二个参数 propertyName 的真实类型决定的。例如 scaleX 对应的 setScaleX(float) 方法需要的参数是 float 型,所以这里要用 ofFloat 方法。

    第一个参数 view 是动画的目标控件;
    第二个参数 scaleX 是需要变化的属性名称;

    重复模式 repeatMode 有两种:从头开始(RESTART ) 和 反向开始(REVERSE)。


    2. 同时改变多个属性

    当有多个属性需要同时做动画时,只使用 ofIntofFloat 就无法实现了。可以使用 PropertyValuesHolder 或者 ValueAnimator 实现同时改变多个属性。下面就用两种方式实现下面的动画:

    多个属性的动画.gif

    3.1 使用 PropertyValuesHolder

    val scaleX   = PropertyValuesHolder.ofFloat("scaleX",     1F, 0F)
    val scaleY   = PropertyValuesHolder.ofFloat("scaleY",     1F, 0F)
    val rotation = PropertyValuesHolder.ofFloat("rotation", 360F, 0F)
    
    ObjectAnimator.ofPropertyValuesHolder(view, scaleX, scaleY, rotation).apply {
        duration = 1000
    
        // 设置动画完毕后再倒回去, repeatMode 和 repeatCount 需要都设置才有效果
        repeatMode = ObjectAnimator.REVERSE
        repeatCount = 1
    }.start()
    

    3.2 使用 ValueAnimator

    // 给 view 添加一个整体缩放,同时还旋转的动画
    ValueAnimator.ofFloat(1F, 0F).apply {
        duration = 1000
    
        // 添加监听器,值变化时,同时对多个属性做更新
        addUpdateListener { animator ->
            val value = animator.animatedValue as Float
            view.scaleX = value
            view.scaleY = value
            view.rotation = 360 * value
        }
    
        // 设置动画完毕后再倒回去, repeatMode 和 repeatCount 需要都设置才有效果
        repeatMode = ObjectAnimator.REVERSE
        repeatCount = 1
    }.start()
    

    3.3 两种方式的对比
    PropertyValuesHolder 的优点:每一个 Holder 的类型都是独立的。例如 rotation 是 float 类型就使用 ofFloat 方法,translationX 是 int 类型就使用 ofInt 方法。相反 ValueAnimator 地类型只能指定一种。

    ValueAnimator 的优点:可以同时对多个控件做动画。只需要在对应的 UpdateListeneer 中更新不同控件的对应属性就好。而 ObjectAnimator 做不到。


    3. 动画的时序

    上面对多个属性做动画都是同时进行的,如果有先后需求怎么办呢?答案是使用 AnimatorSet。下面来实现这个动画:

    AnimatorSet.gif
    val scaleX   = ObjectAnimator.ofFloat(view, "scaleX",   1F, 0.5F).setDuration(1500)
    val scaleY   = ObjectAnimator.ofFloat(view, "scaleY",   1F, 0.5F).setDuration(3000)
    val rotation = ObjectAnimator.ofFloat(view, "rotation", 0F, 135F).setDuration(1500)
    val alpha    = ObjectAnimator.ofFloat(view, "alpha",    1F,   0F).setDuration(500)
    
    AnimatorSet().apply {
        // 同时播放两个缩放动画
        play(scaleX).with(scaleY)
    
        // 缩放动画播放完后,播放旋转动画
        play(rotation).after(scaleX)
    
        // 延迟 1s 后再播放透明度的动画
        play(alpha).after(rotation).after(1000)
    }.start()
    

    AnimatorSetplay(Animator) 方法返回一个 Builder 对象,它包含4个方法:
    with(anotherAnimator): 和另外一个动画同时播放;
    after(anotherAnimator): 在另外一个动画播放完毕之后播放本动画;
    before(anotherAnimator): 在另外一个动画之前播放本动画;
    after(long): 延迟一定时间再播放。

    基于这个 Builder,官方还在 AnimatorSet 中封装了其它方法方便使用:
    playTogether(Animator[]): 传入的多个动画同时播放;
    playSequentially(Animator[]): 传入的多个动画按顺序播放;

    AnimatorSet().apply { 
        // 同时播放
        playTogether(scaleX, scaleY, rotation, alpha)
        // 顺序播放
        playSequentially(scaleX, scaleY, rotation, alpha)
    }.start()
    

    4. 动画的监听

    通常会有监听动画结束的需求,可以这样添加监听:

    scaleX.addListener(object: Animator.AnimatorListener {
        override fun onAnimationRepeat(animation: Animator?) {
        }
        override fun onAnimationEnd(animation: Animator?) {
        }
        override fun onAnimationCancel(animation: Animator?) {
        }
        override fun onAnimationStart(animation: Animator?) {
        }
    })
    

    如果只想监听某一个事件,还可以使用空实现的 AnimatorListenerAdapter,例如只监听结束时的回调:

    scaleX.addListener(object: AnimatorListenerAdapter() {
        override fun onAnimationEnd(animation: Animator?) {
            // Do something...
        }
    })
    

    5. 插值器

    当需要让动画有阻尼等效果时,就要使用插值器了。例如添加一个慢进慢出的效果:

    animator.setInterpolator(AccelerateDecelerateInterpolator())
    

    除了慢进慢出以外,还有更多插值器在 android.view.animation 包下。

    这就是属性动画相关的使用啦~

    相关文章

      网友评论

          本文标题:Android动画 - 属性动画

          本文链接:https://www.haomeiwen.com/subject/haigfctx.html