Android的属性动画(Property Animation)详细教程

3,767 阅读6分钟

1. 简介

在引入属性动画之前,Android已经有了Tween animation(补间动画)。引入新的Animator的原因是因为补间动画有很多局限性。 补间动画的分类:

  1. AlphaAnimation
  2. ScaleAnimation
  3. TranslateAnimation
  4. RotateAnimation
  5. AnimationSet

补间动画的局限性:

  1. 可设置动画的对象有限
  2. 显示区域和可点击区域可能会不同

为了补充和解决上述补间动画的不足,所以在Android3的时候引入了ValueAnimatorObjectAnimator

2. 属性动画

2.1 属性动画的结成结构

序号 类名 说明
1 Animator 所有 Animator 的父类,主要用于定义通用的接口
2 AnimatorSet 多个属性动画组合
3 ValueAnimator 主要用于根据起始值和终止值产生动画,只负责产生在起始值和终止值之间的值
4 ObjectAnimator 主要用于根据起始值和终止值产生动画,并将动画产生的值设置在目标对象上
5 TimeAnimator 提供了一个简单的回调机制,通过 TimeAnimator.TimeListener,在动画的每一帧处通知你。(本文不会介绍)

继承结构:

2.2 定义属性动画方法

跟补间动画一样,可以使用Xml和Code的方式创建动画。

在animation文件夹中创建xml动画文件。(如果没有animation文件夹,需要手动创建)

<objectAnimator
   android:duration="int"
   android:interpolator="@[package:]anim/interpolator_resource"
   android:propertyName="string"
   android:valueType=["intType" | "floatType"]
   android:valueFrom="float | int | color"
   android:valueTo="float | int | color"
   android:startOffset="int"
   android:repeatCount="int"
   android:repeatMode=["repeat" | "reverse"]
   />
属性 含义 取值范围
duration 动画执行时间 整型,需要大于0。
interpolator 差值器,改变动画或者数值变化的速率 安卓自带,自定义
propertyName 动画目标对象要改变的属性 字符串,填入属性名称
valueType 值类型 整点型,浮点型。当属性动画值的类型为颜色值时可以省略
valueFrom 动画值的起始值 浮点数,整型数或者颜色值。当为颜色值时,必须符合颜色的定义方式(# + 六位十六进制数)
valueTo 动画值的结束值 浮点数,整型数或者颜色值。当为颜色值时,必须符合颜色的定义方式(# + 六位十六进制数)
startOffset 动画开始偏移时间 整型数,默认为 0。当为负数时,效果和默认值一样
repeatCount 动画重复的次数 整型数字,默认为 0。当为负数时,表示无限循环
repeatMode 下一次动画执行的方式 默认:重新开始播放。还有倒播
示例
//1. 在animation文件夹中创建xml文件 
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:interpolator="@android:anim/accelerate_decelerate_interpolator"
    android:propertyName="rotation"
    android:valueType="floatType"
    android:valueFrom="0"
    android:valueTo="360"
    android:startOffset="0"
    android:repeatCount="infinite"
    android:repeatMode="reverse"
    />
    
//2. 在代码中导入动画xml
val objectAnimator = AnimatorInflater.loadAnimator(this, R.animator.object_animator) as ObjectAnimator
objectAnimator.setTarget(target)
objectAnimator.start()

代码的创建方式在下面介绍。

3. ValueAnimator

ValueAnimator正如其名字,它是关于数字的计算动画。它不会与View直接交互,而是通过ValueAnimator#addUpdateListener来设置动画效果。比如在TextView上显示从0到50时,就可以用ValueAnimator

3.1 ValueAnimator的创建

在代码中创建ValueAnimator

// 创建Animator,同时传入取值范围
val animator = ValueAnimator.ofFloat(0F, 5000F) 
// 设置差值器, 这里选择的是线性差值器
animator.interpolator = LinearInterpolator()
// 设置动画执行时间
animator.setDuration(2000)

如果不设置interpolator,则默认使用的是AccelerateDecelerateInterpolator()。如果设置的是null,则会默认使用LinearInterpolator()

3.2 添加UpdateListener

在ValueAnimator中有数值变化时,会调用onAnimationUpdate接口。所以我们需要实现这个Listener。

animator.addUpdateListener {
    // 在TextView中更新text
    binding.textView.text = ((it.animatedValue as Float).toInt() / 100).toString()
    // 把值传入自定义贝塞尔View,是其产生动画效果
    binding.bezierView.setValue(it.animatedValue as Float)
}

自定义贝塞尔View的源码如下。 关于贝塞尔曲线,可以参考我的另一篇文章。

class BezierView : View {

    private var path: Path = Path()

    private lateinit var paint: Paint = Paint()

    private var h: Int = 0
    private var w: Int = 0

    private var controlPoint1: PointF = PointF()
    private var controlPoint2: PointF = PointF()

    constructor(context: Context) : this(context, null)
    constructor(context: Context, attributeSet: AttributeSet?) : super(context, attributeSet)

    override fun onSizeChanged(w: Int, h: Int, oldw: Int, oldh: Int) {
        super.onSizeChanged(w, h, oldw, oldh)

        // 设置当前view的高和宽
        this.h = h
        this.w = w

        controlPoint1 = PointF(this.w.toFloat() / 4, 0F)
        controlPoint2 = PointF(this.w.toFloat() / 4 * 3, this.h.toFloat())
    }

    fun setValue(degree: Float) {
        val controlY = degree / 5000 * h

        controlPoint1 = PointF(this.w.toFloat() / 4, controlY)
        controlPoint2 = PointF(this.w.toFloat() / 4 * 3, this.h.toFloat() - controlY)

        invalidate()
    }
    
    override fun onDraw(canvas: Canvas?) {
        super.onDraw(canvas)

        // 重置path, 为的是防止重复绘制贝塞尔曲线,使画布上残留多条曲线
        path.reset()

        // 配置画笔paint
        paint.color = context.getColor(R.color.colorAccent)
        paint.strokeWidth = 2F
        paint.style = Paint.Style.STROKE

        // 设置左右两个基准点
        val pointLeft = PointF(0F, h / 2.toFloat())
        val pointRight = PointF(w.toFloat(), h / 2.toFloat())

        // 绘制左右基准点
        canvas?.drawPoint(pointLeft.x, pointLeft.y, paint)
        canvas?.drawPoint(pointRight.x, pointRight.y, paint)

        paint.color = context.getColor(R.color.colorPrimaryDark)

        // 为了绘制贝塞尔曲线,需要移动到其中一个基准点
        path.moveTo(pointLeft.x, pointLeft.y)

        // 根据基准点和控制点,绘制贝塞尔曲线
        path.cubicTo(
            controlPoint1.x,
            controlPoint1.y,
            controlPoint2.x,
            controlPoint2.y,
            pointRight.x,
            pointRight.y
        )

        // 在画布上画path
        canvas?.drawPath(path, paint)
    }

}

3.3 播放动画

最后需要在合适的节点开始播放动画。

// 播放动画
animator.start()
// 动画暂定
animator.pause()
// 播放结束
animator.end()

4. ObjectAnimator

ValueAnimator一样的方式创建ObjectAnimator

// 创建动画,
val objectAnimator = ObjectAnimator.ofFloat(binding.imageView, "rotation", 0F, 360F, 0F)
// 设置动画执行时间
objectAnimator.setDuration(2000)
// 开始播放
objectAnimator.start()

创建动画时的第一个传参是被动画对象View,第二个是properyName,第三个是变长参数的起始数值。

其中propertyName的类型一共有以下几种:

名称 作用
alpha 透明化
rotation 旋转
rotationX 以X轴旋转
rotationY 以Y轴旋转
translationX 以X轴平移
translationY 以Y轴平移
scaleX 以X轴拉伸
scaleY 以Y轴拉伸

5. 属性动画的Listener

属性动画的监听器一共有三种。

  1. AnimatorListener
  2. AnimatorPauseListener
  3. AnimatorUpdateListener

5.1 AnimatorListener

AnimatorListener的监听器主要监听的是属性动画的开始,结束,取消,重复。

public static interface AnimatorListener{
    void onAnimationStart(Animator animation, boolean isReverse) {}
    
    void onAnimationEnd(Animator animation, boolean isReverse) {}
    
    void onAnimationCancel(Animator animation, boolean isReverse) {}
    
    void onAnimationRepeat(Animator animation, boolean isReverse) {}
}

5.2 AnimatorPauseListener

AnimatorPauseListener的监听器主要监听的是属性动画的暂停,恢复状态。

public static interface AnimatorPauseListener {
    void onAnimationPause(Animator animation);
    
    void onAnimationResume(Animator animation);
}

5.3 AnimatorUpdateListener

AnimatorUpdateListener的监听器主要监听的是属性动画中值得变化。

public static interface AnimatorUpdateListener {
    void onAnimationUpdate(ValueAnimator animation);
}

6. 插值器(Interpolator)

6.1 插值器类型

属性动画的Interpolator和补间动画的的是一样的。 插值器一共有一下几种:

名称 作用
AccelerateDecelerateInterpolator 先加速,后减速
AccelerateInterpolator 一直加速
AnticipateInterpolator 迂回,加速
OvershootInterpolator 加速超出,返回终点
AnticipateOvershootInterpolator 迂回,加速超出,返回终点
BounceInterpolator 弹簧效果
CycleInterpolator 正弦曲线
DecelerateInterpolator 一直减速
LinearInterpolator 线性速度

6.2 自定义插值器

如果上面的插值器不符合要求可以自定义一个新的插值器。 自定义插值器时需要继承BaseInterpolator。 重写public float getInterpolation(float input)

线性插值器是如下的代码,可以作为参考。

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {

    public LinearInterpolator() {
    }

    public LinearInterpolator(Context context, AttributeSet attrs) {
    }

    public float getInterpolation(float input) {
        return input;
    }

    /** @hide */
    @Override
    public long createNativeInterpolator() {
        return NativeInterpolatorFactoryHelper.createLinearInterpolator();
    }
}

7. 自定义估值器(TypeEvaluator)

估值器是用于计算ValueAnimator中的数值变化。ValueAnimator的默认的估值器是线性的。 如果想要自定义估值器需要继承TypeEvaluator,以及重写public T evaluate(float fraction, T startValue, T endValue);

示例如下:

class CustomTypeEvaluator : TypeEvaluator<Float> {
    override fun evaluate(fraction: Float, startValue: Float?, endValue: Float?): Float {
        return fraction * abs(endValue ?: 0F - startValue!!)
    }
}

8. Github

本文的示例: github.com/HyejeanMOON…

其他教程:
Google的MergeAdapter的使用: juejin.cn/post/684490…
Paging在Android中的应用: juejin.cn/post/684490…
Android UI测试之Espresso: juejin.cn/post/684490…
Android ConstraintLayout的易懂教程: juejin.cn/post/684490…
在RecyclerView中可以应对多个ViewType的库--Groupie: juejin.cn/post/684490…