美文网首页
属性动画

属性动画

作者: echoSuny | 来源:发表于2020-06-28 01:00 被阅读0次

    属性动画是Android3.0才引入的。之所以引入属性动画其一是为了弥补补间动画的不足。例如有一个需求是改变view的颜色从绿色变成红色再变成蓝色,补间动画是实现不了的。其二是因为补间动画是一种“表象”动画。例如translate动画把view从一个位置移动到另外一个位置。假如要对这个view做点击事件,只在view的初始位置有效,而平移过后的地方点击是没有效果的。

    ValueAnimator

    从名字来看,这个动画是针对值的。ValueAnimator不会对控件,也就是view执行任何操作。

            btn.setOnClickListener {
                val valueAnimator = ValueAnimator.ofInt(1, 100)
                valueAnimator.duration = 3000
                valueAnimator.addUpdateListener {
                    val animatedValue = valueAnimator.animatedValue
                    tv.text = "$animatedValue"
                }
                valueAnimator.start()
            }
    

    可以看到在3秒钟的时间内,数值从1逐渐增大到了100。这就是ValueAnimator的功能:对指定区间进行动画运算。
    虽然ValueAnimator只能对值进行动画,但是通过其他的方式也可以达到对view做动画,下面稍微修改一下布局和代码:

            tv.setOnClickListener { toast("😄😄😄") }
    
            btn.setOnClickListener {
                val valueAnimator = ValueAnimator.ofInt(0, 500)
                valueAnimator.duration = 3000
                valueAnimator.addUpdateListener {
                    val animatedValue = valueAnimator.animatedValue as Int
                    tv.layout(
                        animatedValue,
                        animatedValue,
                        (animatedValue + tv.width),
                        (animatedValue + tv.height)
                    )
                }
                valueAnimator.start()
            }
    

    可以看到不仅让view进行了平移,并且点击事件也在动画结束的位置上。
    其实除了ofInt()可以创建一个ValueAnimator对象,ofFloat()函数也可以。区别就是这ofFloat()需要传入的是float类型的数据。另外这两个函数的参数是可变参数,是可以传入多个数值的。

            val animator = ValueAnimator.ofFloat(1.0f, 33.3f, 12.9f)
    

    另外ofArgb()以及ofObject()也可以创建ValueAnimator对象。ofArgb()是专门处理颜色变化的:

            btn.setOnClickListener {
                val valueAnimator = ValueAnimator.ofArgb(Color.BLACK, Color.GREEN, Color.RED)
                valueAnimator.duration = 3000
                valueAnimator.addUpdateListener {
                    val animatedValue = valueAnimator.animatedValue as Int
                    tv.setBackgroundColor(animatedValue)
                }
                valueAnimator.start()
            }
    

    而ofObject()的使用则要稍微的复杂一些

    public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values) {
            ......
        }
    

    可以看到除了需要传入需要改变的object,还需要传入一个TypeEvaluator。TypeEvaluator其实就是估值器。它的作用是计算动画执行到某个进度时对应的值。以上面ofInt()的例子来看,假如动画执行了20%且插值器是线性的,那么此时对应的值则为0+(500-0)*0.2= 100,也就是在监听器中得到的animatedValue为100。至于需要传入一个估值器是因为传入的object是我们自己定义的,系统不知道需要转换的值是什么。

    // 自定义估值器
    class CharEvaluator : TypeEvaluator<Char> {
        override fun evaluate(fraction: Float, startValue: Char, endValue: Char): Char {
            val startInt = startValue.toInt()
            val endInt = endValue.toInt()
            val current = startInt + (fraction * (endInt - startInt))
            return current.toChar()
        }
    }
    
             btn.setOnClickListener {
                val valueAnimator = ValueAnimator.ofObject(CharEvaluator(),Character.valueOf('A'),Character.valueOf('Z'))
                valueAnimator.duration = 3000
                valueAnimator.addUpdateListener {
                    val animatedValue = valueAnimator.animatedValue as Char
                    tv.text = animatedValue.toString()
                }
                valueAnimator.start()
            }
    

    ObjectAnimator

    ObjectAnimator是ValueAnimator的子类。从名字也可以看的出来是作用于对象的。

            btn.setOnClickListener {
                val objectAnimator = ObjectAnimator.ofFloat(tv, "scaleX", 1f, .5f, 2f)
                objectAnimator.duration = 2000
                objectAnimator.start()
            }
    

    上面是ObjectAnimator最简单的一个使用。第一个参数是动画要操作的控件,第二个参数是动画要操作控件的哪个属性,第三个参数是可变参数,指的是第二个参数的值如何变化。
    其实,ObjectAnimator做动画,并不是根据XML中的属性来改变的,而是通过制定属性所对应的set函数来改变的。比如上面指定的scaleX属性,ObjectAnimator在做动画的时候就会去指定的控件,也就是上面例子中的TextView中去寻找对应的setScaleX()来改变控件中对应的值。可是在TextView中并没有scaleX属性,这些属性对应的函数都在父类View中。在View中,有关动画共有以下几组set函数:

    public void setAlpha(float alpha)  // 透明度
    
    public void setRotation(float rotation)  // 旋转
    public void setRotationX(float rotationX)  // x轴方向旋转
    public void setRotationY(float rotationY)  //  y轴方向旋转
    
    public void setTranslationX(float translationX)  // x轴方向平移
    public void setTranslationY(float translationY)  // y轴方向平移
    
    public void setScaleX(float scaleX)  // y轴方向缩放
    public void setScaleY(float scaleY)  // y轴方向缩放
    

    以上这些在View中实现了的set函数对应的属性都可以直接使用:

    val objectAnimator = ObjectAnimator.ofFloat(tv, "alpha", 1f, .5f, 2f)
    
    val objectAnimator = ObjectAnimator.ofFloat(tv, "rotation", 1f, .5f, 2f)
    val objectAnimator = ObjectAnimator.ofFloat(tv, "rotationX", 1f, .5f, 2f)
    val objectAnimator = ObjectAnimator.ofFloat(tv, "rotationY", 1f, .5f, 2f)
    
    val objectAnimator = ObjectAnimator.ofFloat(tv, "translationX", 1f, .5f, 2f)
    val objectAnimator = ObjectAnimator.ofFloat(tv, "translationY", 1f, .5f, 2f)
    
    val objectAnimator = ObjectAnimator.ofFloat(tv, "scaleX", 1f, .5f, 2f)
    val objectAnimator = ObjectAnimator.ofFloat(tv, "scaleY", 1f, .5f, 2f)
    

    其实ObjectAnimator能够执行,就是因为不断的调用对应属性的get/set方法。例如上面例子中用到的scaleX。ObjectAnimator会把这个属性名的第一个字母大写,然后与set拼接(就是字符串拼接)得到setScaleX,然后通过反射找到对应控件的setScaleX()方法,将当前的值当作参数传入这个方法中。

    自定义ObjectAnimator属性

    了解了ObjectAnimator的简单原理之后,那么就可以来自定义ObjectAnimator的属性。首先写一个简单的自定义view和估值器:

    class MyTextView @JvmOverloads constructor(context: Context) :
        androidx.appcompat.widget.AppCompatTextView(context) {
    
        fun setFallPointer(p: Point) {
            layout(p.x, p.y, p.x + width, p.y + height)
        }
    }
    
    class MyEvaluator : TypeEvaluator<Point> {
        val point = Point()
        override fun evaluate(fraction: Float, startValue: Point, endValue: Point): Point {
            point.x = (startValue.x + fraction * (endValue.x - startValue.x)).toInt()
            if (fraction * 2 <= 1) {
                point.y = (startValue.y + fraction * 2 * (endValue.y - startValue.y)).toInt()
            } else {
                point.y = endValue.y
            }
            return point
        }
    }
    

    下面是具体使用:

            btn.setOnClickListener {
                val objectAnimator = ObjectAnimator.ofObject(
                    myTextView,
                    "fallPointer",
                    MyEvaluator(),
                    Point(0, 0),
                    Point(500, 500)
                )
                objectAnimator.duration = 2000
                objectAnimator.start()
            }
    

    刚才一直说的是set函数相关,其实get比较简单,只需要记住:
    当且仅当动画只有一个过渡值时(也就是可变参数长度为1),系统才会调用对应属性的 get 函数来得到动画的初始值。当不存在 get 函数时,则会取动画参数类型的默认值作为初始值,当无法取得动画参数类型的默认值时,则会直接崩渍。
    结合上面自定义ObjectAnimator的例子来说,如果在可变参数处只传入一个Point对象,由于在自定义view中没有定义对应的get函数,则系统在调用get函数去获得Point对象的时候会获得null,那必定会出现空指针异常。如果可变参数是基本类型的话则是会发出警告,因为基本类型是有默认值的。

    AnimatorSet

    ValueAnimator和ObjectAnimator只能实现一种动画效果,如果想使用一个组合动画,就需要用到AnimatorSet。类似于补间动画里的AnimationSet。
    在AnimatorSet中提供了两个函数:playSequentialy()与 playTogether()。前者表示所有动画依次播放,后者表示所有动画一起开始。

    playSequentialy()
            btn.setOnClickListener {
                val bgAnimator =
                    ObjectAnimator.ofArgb(tv, "backgroundColor", Color.RED, Color.GREEN)
                val transAnimator = ObjectAnimator.ofFloat(tv, "translationX", 100f, 400f)
                val rotateAnimator = ObjectAnimator.ofFloat(tv, "rotationY", 0f, 720f)
                val animatorSet = AnimatorSet()
                animatorSet.playSequentially(bgAnimator, transAnimator, rotateAnimator)
                animatorSet.duration = 3000
                animatorSet.start()
            }
    
    playTogether()
            btn.setOnClickListener {
                val bgAnimator =
                    ObjectAnimator.ofArgb(tv, "backgroundColor", Color.RED, Color.GREEN)
                val transAnimator = ObjectAnimator.ofFloat(tv, "translationX", 100f, 400f)
                val rotateAnimator = ObjectAnimator.ofFloat(tv, "rotationY", 0f, 720f)
                val animatorSet = AnimatorSet()
                animatorSet. playTogether(bgAnimator, transAnimator, rotateAnimator)
                animatorSet.duration = 3000
                animatorSet.start()
            }
    

    playTogether()和 playSequentially() 函数在开始动画时,只是把每个控件的动画激活,至于每个控件自身的动画是否延时、是否无限循环,只与控件自身的动画设定有关,playTogether()和playSequentially() 函数无关,它们只负责到时间后激活动画。playSequentially()函数只有在上 个控件做完动画以后,才会激活下个控件的动画,如果上一个控件的动画是无限循环的,那么下一个控件就别再指望能做动画了。
    ps:如果需要实现无限循环的组合动画,则需要在每个单独动画上设定setRepeatCount(ValueAnimator.INFINITE)就可以了。

    AnimatorSet.Builder

    假如现在有三个动画A、B、C,想先播放C,然后一起播放A和B。playSequentialy()与 playTogether()是无法实现的。为了方便的使用组合动画,Android提供了AnimatorSet.Builder类。而获得AnimatorSet.Builder对象的唯一方法是通过AnimatorSet的play()方法

            btn.setOnClickListener {
                val bgAnimator =
                    ObjectAnimator.ofArgb(tv, "backgroundColor", Color.RED, Color.GREEN)
                val transAnimator = ObjectAnimator.ofFloat(tv, "translationX", 100f, 400f)
                val rotateAnimator = ObjectAnimator.ofFloat(tv, "rotationY", 0f, 720f)
                val animatorSet = AnimatorSet()
                val builder: AnimatorSet.Builder = animatorSet.play(bgAnimator)
                builder.with(transAnimator).after(rotateAnimator)
                animatorSet.duration = 3000
                animatorSet.start()
            }
    

    AnimatorSet.Builder提供的函数除了上面代码中展示的with()和after(),还有一个before()。通过例子想必都清楚了after()函数的作用是先执行方法中的动画,再执行其他的动画。before()则正好与之相反。

    相关文章

      网友评论

          本文标题:属性动画

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