对任意属性做动画
在 Android 开发中,属性动画要求动画作用的对象提供该属性的 get 和 set 方法。属性动画通过外部传递的初始值和最终值,以动画效果多次调用对象的 set 方法,从而达到改变属性值的目的。本文将探讨如何对对象的任意属性做动画,特别是当对象没有直接的 set/get 方法时,如何处理。
属性动画的基本要求
要对对象的属性 abc 进行动画操作,需要满足以下两个条件:
- 对象必须提供
set方法: 如果动画时没有传递初始值,还需要提供get方法。系统需要通过get方法获取属性abc的初始值(如果不满足这一点,程序会崩溃)。 set方法的效果:setAbc方法对属性abc的改变必须能够通过某种方式反映出来,例如带来 UI 的改变等(如果不满足这一点,动画无效果但不会崩溃)。
以上条件缺一不可。例如,直接对 Button 的 width 属性做动画会无效果,因为 Button 的 setWidth 方法并不设置 View 的宽度,而是设置 View 的最大宽度和最小宽度。
解决方案
对于没有有效 set/get 方法的对象属性,官方文档提供了三种解决方法:
- 添加
get和set方法(如果有权限) - 使用包装类间接提供
get和set方法 - 使用
ValueAnimator监听动画过程,手动实现属性改变
1. 添加 get 和 set 方法
在很多情况下,我们没有权限为对象添加 get 和 set 方法。例如,我们无法为 Button 添加一个符合要求的 setWidth 方法。因此,这种方法在实际开发中常常不可行。
2. 使用包装类间接提供 get 和 set 方法
通过创建包装类,可以间接为原始对象提供 get 和 set 方法。下面是一个示例,展示如何使用包装类为 Button 实现宽度变化的动画。
private fun performWrapperAnimator() {
val viewWrapper = ViewWrapper(mButton)
ObjectAnimator.ofInt(viewWrapper, "width", 500).setDuration(3000).start()
}
/**
* View 包装类,间接为 View 的 width 属性提供 set/get 方法,方便做属性动画
*/
class ViewWrapper(private val mTarget: View) {
fun getWidth(): Int {
return mTarget.layoutParams.width
}
fun setWidth(width: Int) {
mTarget.layoutParams.width = width
mTarget.requestLayout()
}
}
使用 ViewWrapper 包装 View,然后对 ViewWrapper 的 width 属性做动画。在 ViewWrapper 的 setWidth 方法中修改 Button 的宽度,这样就可以间接对 Button 的 width 属性做动画。
3. 使用 ValueAnimator 监听动画过程,手动实现属性改变
ValueAnimator 本身不作用于任何对象,它可以对一个值做动画。我们可以监听其动画过程,在动画过程中修改对象的属性值,从而实现动画效果。
private fun performValueAnimator(target: View, start: Int, end: Int) {
val valueAnimator = ValueAnimator.ofInt(1, 100)
valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {
// 持有一个 IntEvaluator 对象,方便下面估值时使用
private val mEvaluator = IntEvaluator()
override fun onAnimationUpdate(animator: ValueAnimator) {
// 获得当前动画的进度值,整型,1-100 之间
val currentValue = animator.animatedValue as Int
// 获得当前进度占整个动画过程的比例,浮点型,0-1 之间
val fraction = animator.animatedFraction
// 直接调用整型估值器,通过比例计算出宽度,然后设给目标 View
target.layoutParams.width = mEvaluator.evaluate(fraction, start, end)
target.requestLayout()
}
})
valueAnimator.setDuration(3000).start()
}
在这个例子中,我们使用 ValueAnimator 监听动画进度,并在动画过程中手动更新 View 的宽度。这种方法允许我们对任意属性进行动画操作,即使该属性没有直接的 set/get 方法。
总结
通过使用包装类和 ValueAnimator,我们可以对没有直接 set/get 方法的对象属性进行动画操作。包装类提供了一个中间层,通过它可以间接更新对象属性;而 ValueAnimator 则允许我们在每一帧手动更新属性,实现更灵活的动画效果。这些方法为 Android 开发者提供了丰富的动画实现手段。