Cursor blinking

Android 属性动画

Android 基础|Android动画|字数 1,626|阅读时长≈ 5 分钟

简介

属性动画(Property Animation)是一种强大的动画系统,允许您更改任何对象的属性值,属性动画系统通过在一段时间内逐渐更改属性值来创建动画效果。

!icon 属性动画系统不只可以用来在视图层级上更改 View 属性的值,也可以作用于别的对象属性。它的核心在于”一段时间内按照指定的规则更改属性的值“。属性动画的性质,你可以扩展到别的开发语言甚至是别的领域。

重要的组成部分

下面从一个简单的示例开始,来认识属性动画的各个组成部分

属性动画第一个示例.gif
属性动画第一个示例.gif
Code
fun testAnim() {        val valueAnimator = ValueAnimator.ofFloat(0f, 1f)        // 动画持续时间        valueAnimator.duration = 400        // 插值器,⽤于设置时间完成度到动画完成度的计算公式,速度曲线。LinearInterpolator 为默认插值器        valueAnimator.interpolator = LinearInterpolator()        // 评估器(求值器),⽤于设置动画完成度到属性具体值的计算。FloatEvaluator 为默认求值器,允许开发人员在任意属性类型上创建动画        valueAnimator.setEvaluator(FloatEvaluator())        // 监听器        valueAnimator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {            override fun onAnimationUpdate(animation: ValueAnimator) {                rootView.v_0.translationX = animation.animatedValue as Float * 500            }        })        valueAnimator.start()    }

通过这个示例,引入属性动画几个最重要的组成部分

  • 时长:您可以指定动画的时长。默认时长为 300 毫秒。
  • 插值器(Interpolator):您可以指定如何根据动画当前已播放时间来计算属性的值。
  • 评估器(TypeEvaluator):你可以指定属性动画如何计算给定属性的值。
  • 动画监听器(AnimatorUpdateListener、AnimatorListener)
  • AnimatorUpdateListener 您可以访问更新后的动画值,并将其用在您的某个视图的属性中。
  • AnimatorListener 监听动画的开始、结束、取消、重复。

评估器

评估器会告知属性动画系统如何计算给定属性的值。它们会获取 Animator 类提供的时间数据(动画的起始值和结束值),并根据这些数据计算属性的动画值。属性动画系统可提供以下评估程序:

类/接口说明
IntEvaluator这是用于计算 int 属性的值的默认评估程序。
FloatEvaluator这是用于计算 float 属性的值的默认评估程序。
ArgbEvaluator这是用于计算颜色属性的值(以十六进制值表示)的默认评估程序。
TypeEvaluator此接口用于创建您自己的评估程序。如果您要为不是 int、float 或颜色的对象属性添加动画效果,则必须实现 TypeEvaluator 接口以指定如何计算对象属性添加动画效果之后的值。如果您想以不同于默认行为的方式处理 int、float 和颜色值,还可以为这些类型指定自定义 TypeEvaluator。如需详细了解如何编写自定义评估器,请参阅使用 TypeEvaluator 部分。

插值器

插值器定义了如何根据动画当前已播放时间来计算属性的值。例如,您可以指定动画在整个动画中线性播放,即动画在整个过程中均匀移动,也可以指定动画使用非线性时间,例如在动画开始时加速,在动画结束时减速。

类/接口说明
AccelerateDecelerateInterpolator该插值器的变化率在开始和结束时缓慢,但在中间会加快。
AccelerateInterpolator该插值器的变化率在开始时较为缓慢,然后会加快。
AnticipateInterpolator该插值器先反向变化,然后再急速正向变化。
AnticipateOvershootInterpolator该插值器先反向变化,再急速正向变化并超过目标值,然后最终回到最终值。
BounceInterpolator该插值器的变化会跳过结尾处。
CycleInterpolator该插值器的动画会在指定数量的周期内重复。
DecelerateInterpolator该插值器的变化率开始时很快,然后减慢。
LinearInterpolator该插值器的变化率恒定不变。
OvershootInterpolator该插值器的变化先急速正向,再超过最终值,然后返回结果。
TimeInterpolator该接口用于实现您自己的插值器。

ValueAnimator 执行动画原理

通过查看 ValueAnimator、AnimationHandler 源码可知,AnimationHandler 通过 ThreadLocal 将获取到的 AnimationHandler 实例限制在单个线程中(每个线程对应有自己的 AnimationHandler 实例),来确保动画值的设置将在动画启动的同一线程上进行,并且所在线程所有动画将共享相同的时间来计算其值。默认情况下,处理程序使用 Choreographer 进行定期回调,用户可以自定义 AnimationFrameCallbackProvider 接口的实现来提供自己的动画执行机制。

下面示例我们使用 Choreographer 进行定期回调模拟属性动画的执行:

screen-20240326-093855.gif
screen-20240326-093855.gif
Code
    fun testAnim() {        startTime = 0        val choreographer = Choreographer.getInstance()        // 在当前线程启动        postNextFrame(choreographer)        // 在动画执行一秒后,开启一个耗时操作,阻塞当前线程        mainHandler.postDelayed({ blockByCalculate() }, 1000)    } 		// 通过 Choreographer 帧回调,模拟属性动画的执行    private fun postNextFrame(choreographer: Choreographer) {        choreographer.postFrameCallback {            if (startTime <= 0L) {                startTime = System.currentTimeMillis()                currentProgress = 0f            } else {                currentProgress = (System.currentTimeMillis() - startTime) / DURATION_TIME.toFloat()            }            if (currentProgress > 1) {                currentProgress = 1f                rootView.v_1.translationX = currentProgress * DEFAULT_DISTANCE                return@postFrameCallback            }            rootView.v_1.translationX = currentProgress * DEFAULT_DISTANCE            postNextFrame(choreographer)        }    }

通过这个示例可以看出,属性动画基于当前已播放时间来计算属性的值,当前已播放时间是基于帧时间计算得来的,所有示例中 UI 线程执行完耗时操作,动画出现了跳动。

ObjectAnimator

ObjectAnimator 是 ValueAnimator 的子类,ObjectAnimator 可以直接为目标对象的命名属性添加动画效果的功能,这样可以更轻松地为任何对象添加动画效果,因为动画属性会自动更新,因此您不再需要实现 ValueAnimator.AnimatorUpdateListener。

ObjectAnim_演示.gif
ObjectAnim_演示.gif

实例化 ObjectAnimator 与 ValueAnimator 类似,但您还需要指定对象和该对象属性的名称(作为字符串),以及要在哪些值之间添加动画效果:

Code
 // 0f~500f 500f~600f 分别消费 500ms val objectAnimator =ObjectAnimator.ofFloat(targetView, View.TRANSLATION_X, 0f, 500f, 600f) objectAnimator.setDuration(1000).start()
  • 自定义动画处理的属性值
Code
 // 自定义动画处理的属性 val TRANSLATION_X: Property<View, Float> = object : FloatProperty<View>("xxxxxxx") {        override fun setValue(view: View, value: Float) {            view.translationX = value * 1.1f        }        override fun get(view: View): Float {            return view.translationX * 1.1f        }    }  var objectAnimator = ObjectAnimator.ofFloat(targetView, TRANSLATION_X, 0f, 500f, 600f)objectAnimator.setDuration(1000).start()
  • 设置路径
Code
 var path = Path() path.moveTo(0f, 0f) path.lineTo(0f, 500f) path.lineTo(500f, 500f) path.lineTo(500f, 0f) path.lineTo(0f, 0f) var objectAnimator = ObjectAnimator.ofFloat(targetView, View.TRANSLATION_X, View.TRANSLATION_Y, path) objectAnimator.setDuration(1000).start()
  • 设置路径+自定义动画处理的属性
Code
  fun setCoordinates(x: Float, y: Float) {        targetView.translationX = x        targetView.translationY = y    }      fun testPath(){			   var path = Path()        path.moveTo(0f, 0f)        path.lineTo(0f, 500f)        path.lineTo(500f, 500f)        path.lineTo(500f, 0f)        path.lineTo(0f, 0f)        var objectAnimator = ObjectAnimator.ofMultiFloat(this, "setCoordinates", path)        objectAnimator.setDuration(1000).start()  }  
  • 颜色变化
Code
 var objectAnimator = ObjectAnimator.ofArgb(       targetView,       "textColor",       Color.parseColor("#000000"),       Color.parseColor("#E012CD"),       Color.parseColor("#00ff00"))  objectAnimator.setDuration(1000).start()

PropertyValuesHolder

设置有关属性的信息以及该属性在动画期间应采用的值。PropertyValuesHolder 对象可用于使用 ValueAnimator 或 ObjectAnimator 创建并行处理同一个对象多个不同属性的动画。

Code
 val xHolder = PropertyValuesHolder.ofFloat(View.TRANSLATION_X, 0f, 600f) val yHolder = PropertyValuesHolder.ofFloat(View.TRANSLATION_Y, 0f, 600f) val animator = ObjectAnimator.ofPropertyValuesHolder(targetView, xHolder, yHolder) animator.setDuration(1000) animator.start()

Keyframe

ValueAnimator 使用 Keyframe 类来定义动画目标在动画过程中将具有的值。随着时间从一个关键帧转到另一个关键帧,目标对象的值将在上一个关键帧的值和下一个关键帧的值之间以动画形式出现。每个关键帧还包含一个可选的 TimeInterpolator 对象,该对象定义对关键帧之前的值进行时间插值。

Code
 fun testKeyframe() {            val scaleFrame1 = Keyframe.ofFloat(0f, 1.0f)            val scaleFrame2 = Keyframe.ofFloat(0.5f, 2.0f)            val scaleFrame3 = Keyframe.ofFloat(0.8f, 1.5f)            val scaleFrame4 = Keyframe.ofFloat(1.0f, 1.0f)            val scaleX = PropertyValuesHolder.ofKeyframe(                "scaleX",                scaleFrame1,                scaleFrame2,                scaleFrame3,                scaleFrame4            )            val scaleY = PropertyValuesHolder.ofKeyframe(                "scaleY",                scaleFrame1,                scaleFrame2,                scaleFrame3,                scaleFrame4            )            val animator = ObjectAnimator.ofPropertyValuesHolder(targetView, scaleX, scaleY)            animator.duration = 1000            animator.addUpdateListener(object : ValueAnimator.AnimatorUpdateListener {                override fun onAnimationUpdate(animation: ValueAnimator?) {                    LogUtils.d(TAG, "curValue=" + animation?.animatedValue)                }            })            animator.start()        })

AnimatorSet

指定是同时启动动画、依序启动还是在指定的延迟后启动。您还可以相互嵌套 AnimatorSet 对象。

Code
 val animatorSet = AnimatorSet() animatorSet.playTogether(animator1, animator2)  animatorSet.start()

参考文档