Android 属性动画(Property Animation) 使用详解

谨以文章记录学习历程,如有错误还请指明。

动画综述

Google大大对动画的总述如下:

Animations can add visual cues that notify users about what's going on in your app. They are especially useful when the UI changes state, such as when new content loads or new actions become available. Animations also add a polished look to your app, which gives it a higher quality look and feel.

没错,放上原文我只是装个逼,~
简单来说,动画就两个作用:

  • 添加可视提示,通知我们这个APP中正在发生的事情。比如用户界面发生变化时,有新的内容加载或某些操作变为可用。
  • 提供高逼格的外观(装逼利器

动画的分类如下:

动画分类

属性动画(Property Animation)

概述

  • 视图动画的缺陷:
    • 对象的局限性:仅限于View
    • 只改变了View的视觉效果,而没有改变View的属性
    • 动画效果单一
  • 属性动画的特点:
    • 作用对象:任意对象,甚至没对象也可以
    • 作用方式:改变对象的属性
    • 动画效果:按需自定义,不再局限于上述4种基本变换
  • 继承关系


    继承关系
Java类名 XML关键字 说明
ValueAnimator <animator> 放置在res/animator/目录下 在一个特定的时间里执行一个动画
TimeAnimator 时序监听回调工具
ObjectAnimator <objectAnimator> 放置在res/animator/目录下 一个对象的一个属性动画
AnimatorSet <set> 放置在res/animator/目录下 动画集合

工作原理

指定时间内,修改属性(对象中对应的字段)的值,以此实现该对象在属性上的动画效果。
为了更好的理解,我们举一个栗子:

图1.linear animation

图1中我们搞出了一个假象的对象,动画作用于这个对象(实际可以说是对象的x属性,也即对象的水平位置),动画持续40ms,移动距离40px。每10ms(默认刷新速率),对象水平移动10px。40ms后,动画结束,物体停止在x=40处。这是一个典型的设置了linearInterpolator(匀速插值器)的动画。

为了更好的了解属性动画的工作原理,下面我们来看一看属性动画的组件是怎么计算上面例子的动画的。


图2.动画计算过程

其逻辑可以总结如下:

  1. 为 ValueAnimator 设置动画的时长,以及对应属性的始 & 末值
  2. 设置属性在 始 & 末值 间的变化逻辑
  • TimeInterpolator实现类:插值器-描述动画的变化速率
  • TypeEvaluator实现类:估值器-描述 属性值 变化的具体数值
  1. 根据2中的逻辑更新当前值
  2. 获取3中更新的 值 ,修改 目标属性值
  3. 刷新视图。
  4. 重复4-5,直到 属性值 == 末值

下面给出动画工作的关键类

Java类 说明
ValueAnimator 动画执行类;核心
ObjectAnimator 动画执行类
TimeInterpolator 时间插值(插值器接口),控制动画变化率
TypeEvaluator 类型估值(估值器接口),设置属性值计算方式,根据属性的 始 & 末值 和 插值 一起计算出当前时间的属性值
AnimatorSet 动画集
AnimatorInflater 加载属性动画的XML文件

一些额外的类

Java类 说明
LayoutTransition 布局动画,为布局的容器设置动画
ViewPropertyAnimator 为View的动画操作提供一种更加便捷的用法
PropertyValuesHolder 保存动画过程中所需要操作的属性和对应的值
Keyframe 控制每个时间段执行的动画距离
AnimationListener
AnimationUpdateListener
AnimatorListenerAdapter
动画事件的监听

具体使用

ValueAnimator

  • 属性动画的最核心的类
  • 原理:控制 值 的变化,之后 手动 赋值给对象的属性,从而实现动画

对于控制的 的不同,Android 提供给我们三种构造方法来实例ValueAnimator对象

  1. ValueAnimator.ofInt(int... values) -- 整型数值
  2. ValueAnimator.ofFloat(float... values) -- 浮点型数值
  3. ValueAnimator.ofObject(TypeEvaluator evaluator, Object... values) -- 自定义对象类型

下面我们一一介绍

ValueAnimator.ofInt()

  • 作用:将初始值 以整型数值的形式 过渡到结束值
  • 估值器:内置IntEvaluator估值器
  • 具体使用:
    操作 值 的方式分为 XML 方式/ Java 代码方式

方式1: Java 方式

推荐 Java 方式,因为某些时候我们需要动态获取属性的起始值,显然XML方式是不支持动态获取的。

//设置动画 始 & 末值
                //ofInt()两个作用:
                //1. 获取实例
                //2. 在传入参数之间平滑过渡
                //如下则0平滑过渡到3
                ValueAnimator animator = ValueAnimator.ofInt(0,3);
                //如下传入多个参数,效果则为0->5,5->3,3->10
                //ValueAnimator animator = ValueAnimator.ofInt(0,5,3,10);

                //设置动画的基础属性
                animator.setDuration(5000);//播放时长
                animator.setStartDelay(300);//延迟播放
                animator.setRepeatCount(0);//重放次数
                animator.setRepeatMode(ValueAnimator.RESTART);
                //重放模式
                //ValueAnimator.START:正序
                //ValueAnimator.REVERSE:倒序

                //设置更新监听
                //值 改变一次,该方法就执行一次
                animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator animation) {
                        //获取改变后的值
                        int currentValue = (int) animation.getAnimatedValue();
                        //输出改变后的值
                        Log.d("1111", "onAnimationUpdate: " + currentValue);
                        
                        //改变后的值发赋值给对象的属性值
                        view.setproperty(currentValue);
                        
                        //刷新视图
                        view.requestLayout();
                    }
                });
                //启动动画
                animator.start();

以上就是一个标准的Java方式的模板

方式2: XML 方式

  1. 在路径 res/animator/ 路径下常见 XML 文件,如 set_animator.xml
  2. 在上述文件中设置动画参数
// ValueAnimator采用<animator>  标签
<animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueFrom="1"
    android:valueTo="0"
    android:valueType="floatType"
    android:repeatCount="1"
    android:repeatMode="reverse"/>
/>
  1. Java代码启动动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.set_animation);  
// 载入XML动画

animator.setTarget(view);  
// 设置动画对象

animator.start();  
// 启动动画

ValueAnimator.ofFloat()

  • 作用:将初始值 以浮点型数值的形式 过渡到结束值
  • 估值器:内置FloatEvaluator估值器
  • 具体使用:
    和ValueAnimator.ofInt()及其类似,以下只说明不同之处,省略部分参考 ofInt()

方式1:Java方式

ValueAnimator anim = ValueAnimator.ofFloat(0, 3);  
//只是改了实例方法,除此之外完全一样

方式2:XML方式
只在设置动画 XML 文件中的属性时略有不同

// ValueAnimator 采用 <animator>  标签
// ObjectAnimator 采用 <objectAnimator> 标签
<animatorxmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="1000"
    android:valueTo="200"
    android:valueType="floatType"
    android:propertyName="y"
    android:repeatCount="1"
    android:repeatMode="reverse"/>

ValueAnimator.ofObject()

  • 作用:将初始值 以对象的形式 过渡到结束值
  • 估值器:Android 不提供,需要自定义估值器
  • 具体使用:
    ValueAnimator.ofObject() 属于 ValueAnimator 的高级用法,我们前面提到的对任意对象进行动画操作,就是通过此方法实现的。
    以下先放上示例模板:
// 创建初始动画的对象  & 结束动画的对象
Point point1 = new Point ();  
Point point2 = new Point ();  

// 创建动画对象 & 设置参数
ValueAnimator anim = ValueAnimator.ofObject(new myObjectEvaluator(), point1 , point2 );  
// 参数说明
// 1. 自定义的估值器对象(TypeEvaluator 类型参数) - 下面会详细介绍
// 2. 初始动画的对象
// 3. 结束动画的对象
anim.setDuration(length);  
anim.start();

上面示例中,我们看到有两个Point对象point1 , point2 。假设我们有一个自定义View,这个View中有一个Point对象用于管理坐标,然后我们在onDraw()方法中根据这个Point对象的坐标值进行绘制,也就是说,我们可以对Point对象进行动画操作,不停的根据Point的坐标刷新View重绘制,以此就可以实现 View 的动画了。
到这里都很好理解,不过我们还注意到传入了一个 new myObjectEvaluator()(TypeEvaluator实现类) 参数,这是干什么的呢?不要急,下面我们就详细解答。

TypeEvaluator 估值器

其实我们已经不止一次的提到估值器的概念,如 ValueAnimator.ofFloat() 方法中的 IntEvaluatorValueAnimator.ofFloat() 方法中的 FloatEvaluator,这两种都是Android预置供我们使用的,二者通过计算告知动画系统如何从初始值过渡到结束值。
但是二者虽然好用,但也有其局限性:只能针对 Int / Float 类型数值操作。因此某些我们需要对任意对象进行动画操作的时候,二者显然不能满足我们的需求了,这时候我们需要自定义一个TypeEvaluator来告知系统如何进行过渡。

那么如何自定义呢?别急,我们可以先看一看系统提供的 IntEvaluator 是如何实现的:

/**
 * This evaluator can be used to perform type interpolation between int values.
 */
public class IntEvaluator implements TypeEvaluator<Integer> {

    /**
     * This function returns the result of linearly interpolating the start and end values, with
     * fraction representing the proportion between the start and end values. The
     * calculation is a simple parametric calculation: result = x0 + t * (v1 - v0),
     * where x0 is startValue, x1 is endValue, and t is fraction.
     *
     * @param fraction   The fraction from the starting to the ending values
     * @param startValue The start value; should be of type int or Integer
     * @param endValue   The end value; should be of type int or Integer
     * @return A linear interpolation between the start and end values, given the fraction parameter.
     */
    public Integer evaluate(float fraction, Integer startValue, Integer endValue) {
        int startInt = startValue;
        return (int)(startInt + fraction * (endValue - startInt));
    }
}

源码很简单,注释也相当详细,方便观看,我再次解释一下:
IntEvaluator 实现了 TypeEvaluator 接口,然后重写了 evaluate() 方法。该方法的三个参数意义如下:

  • fraction:表示动画完成度,据此计算当前动画的值
  • startValue:动画初始值
  • endValue:动画结束值

那么 evalute() 方法的返回值就不难理解了,简单的数学公式,返回当前动画的值。

是不是很简单?因此我们自定义 TypeEvaluator 时只需要实现 TypeEvaluator 接口,然后重写 evaluate() 方法,在此方法中处理好逻辑即可。
下面我们就动手写一个自定义 TypeEvaluator

自定义TypeEvaluator

我们还是以上面提过的Point对象管理View坐标的为例:

  1. 定义Point
public class Point {
    //记录坐标位置
    private float x;
    private float y;

    //通过构造方法设置坐标,因此不需要额外的set方法
    public Point(float x, float y) {
        this.x = x;
        this.y = y;
    }

    //get方法,获取当前坐标值
    public float getX() {
        return x;
    }
    
    public float getY() {
        return y;
    }
}
  1. 定义 PointEvaluator
//实现TypeEvaluator接口
public class PointEvaluator implements TypeEvaluator {

    //重写evaluate()方法
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        //始末值强转为Point对象
        Point startPoint = (Point) startValue;
        Point endPoint = (Point) endValue;

        //通过fraction计算当前动画的坐标值x,y
        float x = startPoint.getX() + fraction * (endPoint.getX() - startPoint.getX());
        float y = startPoint.getY() + fraction * (endPoint.getY() - startPoint.getY());

        //返回以上述x,y组装的新的Point对象
        Point point = new Point(x,y);
        return point;
    }
}
  1. Point 对象间的平滑过渡
// 创建初始动画的对象  & 结束动画的对象
Point point1 = new Point(0, 0);  
Point point2 = new Point(500, 500);  

// 创建动画对象 & 设置参数
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), point1 , point2 );  
anim.setDuration(3000);
anim.start();

以上就是自定义TypeEvaluator的全部用法。
下面我们就可以尝试用上述知识练习如何对对象进行动画操作,从而实现自定义View的动画效果

自定义View的动画效果

  1. 新建MyAnimView继承View
public class MyAnimView extends View {

    //常量
    public static final float RADIUS = 50f;

    //当前Point,记录当前动画的值(x,y坐标)
    private Point curPoint;

    //画笔
    private Paint mPaint;

    //Java代码实例化View时调用
    public MyAnimView(Context context) {
        super(context);
    }
    //XML文件实例时调用
    public MyAnimView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        //初始化画笔
        mPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
        mPaint.setColor(Color.BLUE);
    }

    @Override
    protected void onDraw(Canvas canvas) {
        //第一次绘制时
        if (curPoint == null){
            //初始化坐标为(50f, 50f)
            curPoint = new Point(RADIUS,RADIUS);
            //画圆
            drawCircle(canvas);
            //开始动画
            startAnimation();
        }else {//非第一次绘制
            drawCircle(canvas);
        }
    }

    //在当前坐标处绘制一个半径为50f的圆
    private void drawCircle(Canvas canvas) {
        float x = curPoint.getX();
        float y = curPoint.getY();
        canvas.drawCircle(x,y,RADIUS,mPaint);
    }

    //开始动画
    private void startAnimation() {
        //设置 起始值&结束值
        Point startPoint = new Point(RADIUS,RADIUS);//起始为左上角(50f,50f)
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//终点为右下角(屏幕宽度-50f,屏幕高度-50f)
        final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        //设置插值器
        anim.setInterpolator(new BounceInterpolator());
        //设置监听
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //每当Point的值有改变的时候,都会调用onAnimationUpdate()方法
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新curPoint,即更新当前坐标
                curPoint = (Point) animation.getAnimatedValue();
                // 刷新,重现调用onDraw()方法
                // 由于curPoint的值改变,那么绘制的位置也会改变,也就实现了一个从左上到右下的平移动画
                invalidate();
            }
        });
        anim.setDuration(5000);
        anim.start();
    }
}
  1. 布局文件中引入该自定义view
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="match_parent">
    <com.example.whdalive.learn_propertyanimation.Property.MyAnimView
        android:layout_width="match_parent"
        android:layout_height="match_parent" />
</LinearLayout>

效果演示


上图中我们发现球并非匀速,而是有一个bounce的效果,这是因为我们为动画添加了一个插值器 anim.setInterpolator(new BounceInterpolator())
具体Interpolator的使用,我们在前一篇Android 视图动画(View Animation) 使用详解中已经详细讲解了,此处就不再赘述。

注意
ValueAnimator.ofObject()的本质还是操作 , 只是将 多个值 封装到一个对象里,同时对 该对象 里的 多个值 一起操作而已

至此,ValueAnimator 的用法就介绍完了,下面我们介绍一个貌似更加常用的类 ObjectAnimator

ObjectAnimator

  • 属性动画重要的类
  • 原理:控制 值 的变化,之后 自动 赋给对象的属性,从而实现动画
  • 与ValueAnimator对比
    • ValueAnimator的子类
    • ValueAnimator只是对 进行平滑的动画过渡;ObjectAnimator直接对 任意对象的任意属性 进行动画操作,如View的alpha属性
    • ValueAnimator需要我们为对象属性手动赋值;ObjectAnimator会为对象属性自动赋值

具体使用

由于继承关系的存在,ObjectAnimator的用法和ValueAnimator及其类似:Java方式设置 / XML方式设置(推荐Java方式)

方式1:Java方式

ObjectAnimator animator = ObjectAnimator.ofFloat(Object object, String property, float ....values);  
//ObjectAnimator animator = ObjectAnimator.ofInt(Object object, String property, int ....values);  
//ObjectAnimator animator = ObjectAnimator.ofObject(Object object, String property, TypeEvaluator evaluator,Object....values);  

// 以ofFloat为例 参数说明:
// Object object:需要操作的对象
// String property:需要操作的对象的属性
// float ....values:动画初始值 & 结束值(不固定长度)
// 若是两个参数a,b,则动画效果则是从属性的a值到b值
// 若是三个参数a,b,c,则则动画效果则是从属性的a值到b值再到c值
// 以此类推
// 至于如何从初始值 过渡到 结束值,同样是由估值器决定,此处ObjectAnimator.ofFloat()是有系统内置的浮点型估值器FloatEvaluator,同ValueAnimator讲解

//动画基本属性
anim.setDuration(500); 
anim.setStartDelay(500);
anim.setRepeatCount(0);
anim.setRepeatMode(ValueAnimator.RESTART);

animator.start();  
// 启动动画

方式2:XML方式设置

  1. 路径res/animator/下创建动画XML文件,如set_animation.xml
  2. 设置动画参数
ObjectAnimator 采用<objectAnimator >  标签
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"  
    android:valueFrom="1"   // 初始值
    android:valueTo="0"  // 结束值
    android:valueType="floatType"  // 变化值类型 :floatType & intType
    android:propertyName="alpha" // 对象变化的属性名称
/>
  1. Java启动动画
Animator animator = AnimatorInflater.loadAnimator(context, R.animator.view_animation);  
// 载入XML动画
animator.setTarget(view);  
// 设置动画对象
animator.start();  
// 启动动画

以上为ObjectAnimator的标准使用模板,我们通过控制传入 ofFloat()的第二个参数preperty 或者 XML中的android:propertyName属性 来产生不同动画效果,以下为Android预置好的一些属性:即四种基本变换,透明度、平移、缩放、旋转

属性 作用 数值类型
alpha 透明度 float
translationX X方向的位移 float
translationY Y方向的位移 float
scaleX X方向的缩放倍数 float
scaleY Y方向的缩放倍数 float
rotation 以屏幕方向为轴的旋转度数 float
rotationX 以X轴为轴的旋转度数 float
rotationY 以Y轴为轴的旋转度数 float

那么问题来了,除了以上的属性,我们还可以传入哪些属性值呢?
答案是 任意属性值

为了理解这个问题,我们需要先了解一下ObjectAnimator是如何自动为属性赋值的。

自动赋值
  • 以alpha+textview为例,很显然textview是不具备alpha这一属性的,那么ObjectAnimator是如何操作的呢?
  • 原理:实际上ObjectAnimator内部的工作机制是去寻找这个属性名对应的get和set方法,通过二者赋值。
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha",1f, 0f, 1f);
// TextView对象中并没有alpha这个属性值
// ObjectAnimator并不是直接对我们传入的属性名进行操作
// 而是根据传入的属性值"alpha" 去寻找对象对应属性名对应的get和set方法,从而通过set() &  get()对属性进行赋值

// 因为TextView对象中有alpha属性所对应的get & set方法
// 所以传入的alpha属性是有效的
// 所以才能对alpha这个属性进行操作赋值
public void setRotation(float value);  
public float getRotation();  

// 实际上,这两个方法是由View对象提供的,其余平移旋转等属性同理

因此:

  • ObjectAnimator 类针对的是任意对象 & 任意属性值,并不是单单针对于View对象
  • 如果需要采用ObjectAnimator 类实现动画效果,那么需要操作的对象就必须有该属性的set() & get()

特别注意:如果想让对象的属性a的动画生效,属性a需要同时满足下面两个条件:

  1. 对象必须要提供属性a的set()方法
    a. 如果没传递初始值,那么需要提供get()方法,因为系统要去拿属性a的初始值
    b. 若该条件不满足,程序直接崩溃
  2. 对象提供的 属性a的set()方法 对 属性a的改变 必须通过某种方法反映出来
    a. 如带来ui上的变化
    b. 若这条不满足,动画无效,但不会崩溃)
自定义对象实现动画效果

本质:

  • 为对象设置需要操作属性的set() & get()方法
  • 通过实现TypeEvaluator类从而定义属性变化的逻辑

下面我们通过实例来介绍如何通过自定义属性实现动画效果

  • 实现效果(在前面运动小球的基础上控制颜色变化)

    untitled.gif

  • 具体实现

  1. 为对象类属性color设置set() & get() 方法
public class MyAnimView extends View {
    
    ...
    
    //将颜色设置为String类型,使用#RRGGBB格式表示颜色
    private String color;

    //get()方法
    public String getColor() {
        return color;
    }

    //set()方法
    public void setColor(String color) {
        mPaint.setColor(Color.parseColor(color));
        this.color = color;
        //改变画笔颜色后立即刷新视图,然后onDraw()方法就会调用
        invalidate();
    }
    ...
}

  1. 布局中加入自定义view
    同前面ValueAnimator的自定义view
  2. 根据需求实现TypeEvaluator接口
public class ColorEvaluator implements TypeEvaluator {
    // 实现TypeEvaluator接口

    private int mCurrentRed;

    private int mCurrentGreen ;

    private int mCurrentBlue ;

    // 复写evaluate()
    // 在evaluate()里写入对象动画过渡的逻辑:此处是写颜色过渡的逻辑
    @Override
    public Object evaluate(float fraction, Object startValue, Object endValue) {

        // 获取到颜色的初始值和结束值
        String startColor = (String) startValue;
        String endColor = (String) endValue;

        // 通过字符串截取的方式将初始化颜色分为RGB三个部分,并将RGB的值转换成十进制数字
        // 那么每个颜色的取值范围就是0-255
        int startRed = Integer.parseInt(startColor.substring(1, 3), 16);
        int startGreen = Integer.parseInt(startColor.substring(3, 5), 16);
        int startBlue = Integer.parseInt(startColor.substring(5, 7), 16);

        int endRed = Integer.parseInt(endColor.substring(1, 3), 16);
        int endGreen = Integer.parseInt(endColor.substring(3, 5), 16);
        int endBlue = Integer.parseInt(endColor.substring(5, 7), 16);

        // 将初始化颜色的值定义为当前需要操作的颜色值
            mCurrentRed = startRed;
            mCurrentGreen = startGreen;
            mCurrentBlue = startBlue;


        // 计算初始颜色和结束颜色之间的差值
        // 该差值决定着颜色变化的快慢:初始颜色值和结束颜色值很相近,那么颜色变化就会比较缓慢;否则,变化则很快
        // 具体如何根据差值来决定颜色变化快慢的逻辑写在getCurrentColor()里.
        int redDiff = Math.abs(startRed - endRed);
        int greenDiff = Math.abs(startGreen - endGreen);
        int blueDiff = Math.abs(startBlue - endBlue);
        int colorDiff = redDiff + greenDiff + blueDiff;
        if (mCurrentRed != endRed) {
            mCurrentRed = getCurrentColor(startRed, endRed, colorDiff, 0,
                    fraction);
                    // getCurrentColor()决定如何根据差值来决定颜色变化的快慢 ->>关注1
        } else if (mCurrentGreen != endGreen) {
            mCurrentGreen = getCurrentColor(startGreen, endGreen, colorDiff,
                    redDiff, fraction);
        } else if (mCurrentBlue != endBlue) {
            mCurrentBlue = getCurrentColor(startBlue, endBlue, colorDiff,
                    redDiff + greenDiff, fraction);
        }
        // 将计算出的当前颜色的值组装返回
        String currentColor = "#" + getHexString(mCurrentRed)
                + getHexString(mCurrentGreen) + getHexString(mCurrentBlue);

        // 由于我们计算出的颜色是十进制数字,所以需要转换成十六进制字符串:调用getHexString()->>关注2
        // 最终将RGB颜色拼装起来,并作为最终的结果返回
        return currentColor;
    }


    // 关注1:getCurrentColor()
    // 具体是根据fraction值来计算当前的颜色。

    private int getCurrentColor(int startColor, int endColor, int colorDiff,
                                int offset, float fraction) {
        int currentColor;
        if (startColor > endColor) {
            currentColor = (int) (startColor - (fraction * colorDiff - offset));
            if (currentColor < endColor) {
                currentColor = endColor;
            }
        } else {
            currentColor = (int) (startColor + (fraction * colorDiff - offset));
            if (currentColor > endColor) {
                currentColor = endColor;
            }
        }
        return currentColor;
    }

    // 关注2:将10进制颜色值转换成16进制。
    private String getHexString(int value) {
        String hexString = Integer.toHexString(value);
        if (hexString.length() == 1) {
            hexString = "0" + hexString;
        }
        return hexString;
    }

}
  1. 调用ObjectAnimator.ofObject()方法
//借助AnimatorSet,实现组合动画
public class MyAnimView extends View {
     ...
    //开始动画
    private void startAnimation() {
        //设置 起始值&结束值
        Point startPoint = new Point(RADIUS,RADIUS);//起始为左上角(50f,50f)
        Point endPoint = new Point(getWidth() - RADIUS, getHeight() - RADIUS);//终点为右下角(屏幕宽度-50f,屏幕高度-50f)
        final ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(),startPoint,endPoint);
        //设置插值器
        anim.setInterpolator(new BounceInterpolator());
        //设置监听
        anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

            //每当Point的值有改变的时候,都会调用onAnimationUpdate()方法
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //更新curPoint,即更新当前坐标
                curPoint = (Point) animation.getAnimatedValue();
                // 刷新,重现调用onDraw()方法
                // 由于curPoint的值改变,那么绘制的位置也会改变,也就实现了一个从左上到右下的平移动画
                invalidate();
            }
        });
       // anim.setDuration(5000);
        //anim.start();
        

        //颜色过渡的代码逻辑放在startAnimation()方法中,本身就在MyAnimView中执行,因此传入 this 参数即可
        ObjectAnimator anim2 = ObjectAnimator.ofObject(this,"color",new ColorEvaluator(),"#FF0000", "#0000FF");
        //创建一个AnimatorSet,让两个动画同时播放,时长5s
        AnimatorSet animationSet = new AnimatorSet();
        animationSet.play(anim).with(anim2);
        animationSet.setDuration(5000);
        //启动动画
        animationSet.start();
    }

以上我们就实现了一个平移 & 颜色变化的动画。
至此关于ValueAnimator和ObjectAnimeato的使用就讲解完了,下面我们再讲讲一些额外的使用方法

1.AnimatorSet 组合动画

  • 独立的动画能够实现的视觉效果毕竟是相当有限的,因此将多个动画组合到一起播放就显得尤为重要
  • 使用:
AnimatorSet.play(Animator anim)   :播放当前动画
AnimatorSet.after(long delay)   :将现有动画延迟x毫秒后执行
AnimatorSet.with(Animator anim)   :将现有动画和传入的动画同时执行
AnimatorSet.after(Animator anim)   :将现有动画插入到传入的动画之后执行
AnimatorSet.before(Animator anim) :  将现有动画插入到传入的动画之前执行
  • 实例
    Java方式
ObjectAnimator a1 = ObjectAnimator.ofFloat(view, "alpha", 1.0f, 0f);  
ObjectAnimator a2 = ObjectAnimator.ofFloat(view, "translationY", 0f, viewWidth);  
......
AnimatorSet animSet = new AnimatorSet();  
animSet.setDuration(5000);  
animSet.setInterpolator(new LinearInterpolator());   
//animSet.playTogether(a1, a2, ...); //两个动画同时执行  
animSet.play(a1).after(a2); //先后执行
......//其他组合方式
animSet.start();  

XML方式

<set
  android:ordering=["together" | "sequentially"]>

    <objectAnimator
        android:propertyName="string"
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <animator
        android:duration="int"
        android:valueFrom="float | int | color"
        android:valueTo="float | int | color"
        android:startOffset="int"
        android:repeatCount="int"
        android:repeatMode=["repeat" | "reverse"]
        android:valueType=["intType" | "floatType"]/>

    <set>
        ...
    </set>
</set>

2. 监听动画

  • 监听到动画的各种事件,比如动画何时开始,何时结束,然后在开始或者结束的时候去执行一些逻辑处理
  • Animator类提供addListener()方法,说明其子类都可以使用该方法(关于继承关系,前面我们提到过了)
  • 使用方法
anim.addListener(new AnimatorListener() {
          @Override
          public void onAnimationStart(Animation animation) {
              //动画开始时执行
          }
      
           @Override
          public void onAnimationRepeat(Animation animation) {
              //动画重复时执行
          }

         @Override
          public void onAnimationCancel()(Animation animation) {
              //动画取消时执行
          }
    
          @Override
          public void onAnimationEnd(Animation animation) {
              //动画结束时执行
          }
      });

// 特别注意:每次监听必须4个方法都重写。
  • 缺点:
    很多时候我们并不想要监听那么多个事件,可能我只想要监听动画结束这一个事件,那么每次都要将四个接口全部实现一遍就显得非常繁琐。
  • 如何解决
    采用动画适配器(AnimatorListenerAdapter),解决 实现接口繁琐 的问题
anim.addListener(new AnimatorListenerAdapter() {  
// 向addListener()方法中传入适配器对象AnimatorListenerAdapter()
// 由于AnimatorListenerAdapter中已经实现好每个接口
// 所以这里不实现全部方法也不会报错
    @Override  
    public void onAnimationStart(Animator animation) {  
    // 如想只想监听动画开始时刻,就只需要单独重写该方法就可以
    }  
});

3. ViewPropertyAnimator用法

  • Google为View的动画操作提供的一种便捷用法
  • 具体使用:
// 使用解析
        View.animate().xxx().xxx();
        // ViewPropertyAnimator的功能建立在animate()上
        // 调用animate()方法返回值是一个ViewPropertyAnimator对象,之后的调用的所有方法都是通过该实例完成
        // 调用该实例的各种方法来实现动画效果
        // ViewPropertyAnimator所有接口方法都使用连缀语法来设计,每个方法的返回值都是它自身的实例
        // 因此调用完一个方法后可直接连缀调用另一方法,即可通过一行代码就完成所有动画效果
        
// 以下是例子
        mButton = (Button) findViewById(R.id.Button);
        // 创建动画作用对象:此处以Button为例

        mButton.animate().alpha(0f);
        // 单个动画设置:将按钮变成透明状态 
        mButton.animate().alpha(0f).setDuration(5000).setInterpolator(new BounceInterpolator());
        // 单个动画效果设置 & 参数设置 
        mButton.animate().alpha(0f).x(500).y(500);
        // 组合动画:将按钮变成透明状态再移动到(500,500)处
        
        // 特别注意:
        // 动画自动启动,无需调用start()方法.因为新的接口中使用了隐式启动动画的功能,只要我们将动画定义完成后,动画就会自动启动
        // 该机制对于组合动画也同样有效,只要不断地连缀新的方法,那么动画就不会立刻执行,等到所有在ViewPropertyAnimator上设置的方法都执行完毕后,动画就会自动启动
        // 如果不想使用这一默认机制,也可以显式地调用start()方法来启动动画
  • 注意事项
    • ViewPropertyAnimator实例通过View.animate()方法创建,之后的调用的所有方法,设置的所有属性都是通过这个实例完成的。
    • ViewPropertyAnimator隐式启动动画,即当我们将动画定义完成之后,动画就会自动启动。当然,如果需要手动显式启动,我们仍然可以调用start()方法启动动画。
    • ViewPropertyAnimator的连缀语法:每个方法返回值都是自身实例,因此方法的调用可以连缀调用,如上面示例代码中写的那样。

至此,关于属性动画能想到的就全都讲解完了,至于有所遗漏的,留作日后再补充吧~


总结


欢迎关注whd_Alive的简书

  • 不定期分享Android开发相关的技术干货,期待与你的交流,共勉。