美文网首页
属性动画

属性动画

作者: 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()则正好与之相反。

相关文章

  • 属性动画学习使用总结

    Android 属性动画总结 属性动画的优点 属性动画的使用步骤 实例化属性动画对象 设置属性动画的时长 启动属性...

  • [Android动画]属性动画-小球下落动画实现

    属性动画 属性动画是通过直接改变View属性,实现的动画效果。与补间动画不同的是,属性动画是对象的属性的真实改变,...

  • Android动画-属性动画

    属性动画 所谓属性动画,就是改变对象Object的属性来实现动画过程。属性动画是对View的动画的扩展,通过它可以...

  • 动画之属性动画基础篇

    属性动画 属性动画,改变view或者object的属性实现动画,属性动画比补间动画更强大,不但可以实现旋转、平移等...

  • UI(四十八)属性动画

    属性动画->通过改变图层或者视图上面的属性值(支持动画的属性)产生的动画 属性动画的常用方法属性: 1、初始化 +...

  • iOS - 属性动画

    属性动画->通过改变图层或者视图上面的属性值(支持动画的属性)产生的动画 属性动画的常用方法属性: 1、初始化+(...

  • Android 动画

    动画类型 视图动画(补间动画、逐帧动画)属性动画 补间动画 逐帧动画 属性动画 对比 插值器:确定属性值从初始值过...

  • Android Animation 动画介绍与详解

    一、Animation 动画属性 动画相关的属性:SET属性 二、Animation 动画类型 Android的a...

  • 如何在网页中做出炫酷的动画(使用Spine)

    属性动画和帧动画 web中的动画主要分为属性动画和帧动画两种,属性动画是通过改变dom元素的属性如宽高、字体大小或...

  • 属性动画案例二(加载动画)

    继续属性动画,之前写过 属性动画案例一(基础动画与飘心动画) 简单了解了属性动画,这次来点炫酷的~加载动画,先看一...

网友评论

      本文标题:属性动画

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