属性动画是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()则正好与之相反。
网友评论