相关文章推荐

1. 简介

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

  • AlphaAnimation
  • ScaleAnimation
  • TranslateAnimation
  • RotateAnimation
  • AnimationSet
  • 补间动画的局限性:

  • 可设置动画的对象有限
  • 显示区域和可点击区域可能会不同
  • 为了补充和解决上述补间动画的不足,所以在Android3的时候引入了 ValueAnimator ObjectAnimator

    2. 属性动画

    2.1 属性动画的结成结构

    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"]
    
    //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的类型一共有以下几种:

    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和补间动画的的是一样的。 插值器一共有一下几种:

    如果上面的插值器不符合要求可以自定义一个新的插值器。 自定义插值器时需要继承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…

    分类:
    Android
    标签:
     
    推荐文章