对任意属性做动画

125 阅读3分钟

对任意属性做动画

在 Android 开发中,属性动画要求动画作用的对象提供该属性的 getset 方法。属性动画通过外部传递的初始值和最终值,以动画效果多次调用对象的 set 方法,从而达到改变属性值的目的。本文将探讨如何对对象的任意属性做动画,特别是当对象没有直接的 set/get 方法时,如何处理。

属性动画的基本要求

要对对象的属性 abc 进行动画操作,需要满足以下两个条件:

  1. 对象必须提供 set 方法: 如果动画时没有传递初始值,还需要提供 get 方法。系统需要通过 get 方法获取属性 abc 的初始值(如果不满足这一点,程序会崩溃)。
  2. set 方法的效果setAbc 方法对属性 abc 的改变必须能够通过某种方式反映出来,例如带来 UI 的改变等(如果不满足这一点,动画无效果但不会崩溃)。

以上条件缺一不可。例如,直接对 Buttonwidth 属性做动画会无效果,因为 ButtonsetWidth 方法并不设置 View 的宽度,而是设置 View 的最大宽度和最小宽度。

解决方案

对于没有有效 set/get 方法的对象属性,官方文档提供了三种解决方法:

  1. 添加 getset 方法(如果有权限)
  2. 使用包装类间接提供 getset 方法
  3. 使用 ValueAnimator 监听动画过程,手动实现属性改变

1. 添加 getset 方法

在很多情况下,我们没有权限为对象添加 getset 方法。例如,我们无法为 Button 添加一个符合要求的 setWidth 方法。因此,这种方法在实际开发中常常不可行。

2. 使用包装类间接提供 getset 方法

通过创建包装类,可以间接为原始对象提供 getset 方法。下面是一个示例,展示如何使用包装类为 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,然后对 ViewWrapperwidth 属性做动画。在 ViewWrappersetWidth 方法中修改 Button 的宽度,这样就可以间接对 Buttonwidth 属性做动画。

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 开发者提供了丰富的动画实现手段。