【Android - 进阶】之Animator属性动画

2018-12-09 15:52:37

1、概述

  在3.0系统之前,Android给我们提供了逐帧动画Frame Animation和补间动画Tween Animation两种动画:

  • 逐帧动画的原理很简单,就是将一个完整的动画拆分成一张张单独的图片,然后将它们连贯起来进行播放;

  • 补间动画是专门为View提供的动画,可以实现View的透明度、缩放、平移和旋转四种效果。

  补间动画有两个个缺陷:

  • 补间动画只能对View设置动画,对非View的对象不能设置动画;

  • 补间动画只是改变了View的显示效果而没有真正的改变View的属性。例如,我们想使用补间动画将一个按钮从一个位置移动到一个新的位置,那么当移动完成之后我们点击这个按钮,是不会触发其点击事件的,而当我们点击移动前的位置时,会触发其点击事件,即补间动画只是在另一个地方重新绘制了这个View,其他的东西都没有改变。

  在3.0系统之后,Android为我们提供了一种新的动画——Animator属性动画。在属性动画中,我们不仅可以像补间动画那样设置控件的透明度、缩放、平移或旋转的动画,还可以做到将这些动画联合起来播放、将一组动画按顺序播放、控制动画的播放速度,甚至可以对非View设置动画等等。

  属性动画,顾名思义,是对对象的属性设置的动画。简单的说,只要一个对象的某个属性有set和get方法,就可以对其设置属性动画。一句话概括,属性动画就是不断的改变一个对象的某个属性。我们只需要告诉系统动画的运行时长,需要执行哪种类型的动画,以及动画的初始值和结束只,剩下的工作就可以全部交给系统去完成了。

  正是因为属性动画是对属性的动画,因此补间动画的第二个缺陷就不复存在了。使用属性动画移动一个按钮,那么这个按钮就是真的被移动了,而不仅仅是在另一个地方重新绘制了自己那么简单。

 

2、ObjectAnimator

  ObjectAnimator是属性动画中最常用的一个类,我们可以通过它直接控制一个对象的属性。使用ObjectAnimator时,我们只需要我们想修改哪个对象的哪个属性、属性的起始值和结束值,以及动画的持续时间,系统就可以为我们运行动画了。ObjectAnimator的使用方法如下。

  执行单个动画:

ObjectAnimator.ofFloat(image, "translationX", 0f, 500f) // 初始化动画,设置各个参数
        .setDuration(3000) // 设置动画持续时间
        .start(); // 开始运行动画

  上面代码中,ofFloat()方法的第一个参数是动画作用的对象,这里是一个ImageView;第二个参数是属性名称,这里指定的是X轴的平移;第三个参数是一个不定长参数,指定属性的起始值和结束值;setDuration()方法指定的是动画执行的时长,这里是3秒钟;最后调用start()方法,动画就开始执行了。

  ObjectAnimator不仅有ofFloat()方法,还有很多其他方法,例如ofInt()、ofObject()、ofPropertyValuesHolder()等,这些方法会在后面详细介绍。

  如果想要同时执行多个动画,只需要指定多个动画后分别调用start()方法即可,代码如下:

ObjectAnimator.ofFloat(image, "translationX", 0f, 500f).setDuration(3000).start();
ObjectAnimator.ofFloat(image, "translationY", 0f, 500f).setDuration(3000).start();
ObjectAnimator.ofFloat(image, "rotation", 0f, 360f).setDuration(3000).start();
 为了加深对ObejctAnimator的理解,这里我们使用ObjectAnimator实现一个卫星菜单的效果,效果图如下:

                         

  如上图所示,开始的时候七个菜单图标是隐藏在右下角的红色按钮底下的,如左图所示;当我们点击了红色按钮的时候,其他菜单图标就会弹出,像卫星一样围绕早红色按钮外层,如右图所示。实现这个效果的核心代码如下:

复制代码

for (int i = 0; i < menus.size(); i++) {
    ImageView menu = menus.get(i);    double angle = Math.toRadians(i * (90 * 1.0 / (menus.size() - 1))); // 角度
    double radius = 450; // 半径
    float distanceX = (float) (Math.cos(angle) * radius); // X坐标偏移量
    float distanceY = (float) (Math.sin(angle) * radius); // Y坐标偏移量    ObjectAnimator animatorX;
    ObjectAnimator animatorY;    if (isOpen) { // 如果菜单是打开的则关闭菜单
        animatorX = ObjectAnimator.ofFloat(menu, "translationX", -distanceX, 0f);
        animatorY = ObjectAnimator.ofFloat(menu, "translationY", -distanceY, 0f);
    } else { // 如果菜单是关闭的则打开菜单
        animatorX = ObjectAnimator.ofFloat(menu, "translationX", 0f, -distanceX);
        animatorY = ObjectAnimator.ofFloat(menu, "translationY", 0f, -distanceY);
    }
    AnimatorSet set = new AnimatorSet(); // X、Y轴同时移动    set.playTogether(animatorX, animatorY);
    set.setDuration(500);
    set.setInterpolator(new BounceInterpolator());
    set.start();
}

复制代码

 

3、组合动画

  所谓的组合动画,就是将多个动画同时播放,或将一组动画按顺序播放。在上面介绍ObjectAnimator时我们已经介绍了一种方式,就是指定多个动画后分别调用start()方法。下面介绍其他几种方法:

  方法二:使用PropertyValuesHolder:

PropertyValuesHolder holder1 = PropertyValuesHolder.ofFloat("translationX", 0f, 500f);
PropertyValuesHolder holder2 = PropertyValuesHolder.ofFloat("translationY", 0f, 500f);
PropertyValuesHolder holder3 = PropertyValuesHolder.ofFloat("rotation", 0f, 360f);
ObjectAnimator.ofPropertyValuesHolder(image, holder1, holder2, holder3).setDuration(3000).start();
 方法三:使用AnimatorSet:

复制代码

ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "rotation", 0f, 360f);
AnimatorSet set = new AnimatorSet();
set.setDuration(3000);
set.playTogether(animator1, animator2, animator3);
set.start();

复制代码

 上面说到,属性动画不光可以让一组动画同时播放,还可以按一定顺序播放动画。按顺序播放动画的几种方式代码如下:

  方法一:使用AnimatorSet对象中的API实现:

复制代码

ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);
ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "rotationX", 0f, 360f);
ObjectAnimator animator4 = ObjectAnimator.ofFloat(image, "rotationY", 0f, 360f);
AnimatorSet set = new AnimatorSet();
set.play(animator3).before(animator2).after(animator1).with(animator4);
set.setDuration(3000);
set.start();

复制代码

 方法二:设置动画监听事件AnimatorListener:

复制代码

ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "alpha", 0f, 1f);final ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);final ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
animator1.setDuration(3000);
animator2.setDuration(3000);
animator3.setDuration(3000);// 设置属性动画的监听事件(使用AnimatorListener必须要监听所有四个事件)animator1.addListener(new Animator.AnimatorListener() {
    @Override    public void onAnimationStart(Animator animation) {
        animator2.start();
    }
    @Override    public void onAnimationEnd(Animator animation) {
        animator3.start();
    }
    @Override    public void onAnimationCancel(Animator animation) {
    }
    @Override    public void onAnimationRepeat(Animator animation) {
    }
});
animator1.start();

复制代码

 方法三:设置动画监听事件AnimatorListenerAdapter:

复制代码

ObjectAnimator animator1 = ObjectAnimator.ofFloat(image, "alpha", 0f, 1f);final ObjectAnimator animator2 = ObjectAnimator.ofFloat(image, "translationX", 0f, 500f);final ObjectAnimator animator3 = ObjectAnimator.ofFloat(image, "translationY", 0f, 500f);
animator1.setDuration(3000);
animator2.setDuration(3000);
animator3.setDuration(3000);// 设置属性动画的监听事件(使用AnimatorListenerAdapter可以选择不监听所有事件)animator1.addListener(new AnimatorListenerAdapter() {
    @Override    public void onAnimationEnd(Animator animation) {
        animator3.start(); // 在animator1执行完后执行animator3    }
    @Override    public void onAnimationStart(Animator animation) {
        animator2.start(); //在animator1执行的同时执行animator2    }
});
animator1.start();

复制代码

 

4、ValueAnimator

  ValueAnimator是整个属性动画最核心的类,ObjectAnimator类就是ValueAnimator类的一个子类。前面我们说过,属性动画的原理就是通过不断改变对象的属性值来实现过渡的,而这种过渡就是通过ValueAnimator类来负责计算的。除此之外,ValueAnimator还负责管理动画的播放次数、播放模式和动画监听等。

  ValueAnimator的使用方法和ObjectAnimator的使用方法基本类似,不同的是ValueAnimator中不能指定运行动画的对象,因此ValueAnimator往往需要设置一个动画监听,通过不断监听当前动画运行到的属性值来动态的进行处理。代码如下:

复制代码

ValueAnimator animator = ValueAnimator.ofInt(0, 100); // 产生一个从0到100变化的整数的动画animator.setDuration(2000);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override    public void onAnimationUpdate(ValueAnimator animation) {
        Integer value = (Integer) animation.getAnimatedValue(); // 动态的获取当前运行到的属性值
        ((Button) view).setText(value + "");
    }
});
animator.start(); // 开始播放动画

复制代码

 和ObjectAnimator一样,除了ofFloat()之外,ValueAnimator也有一些其他的方法,如ofPropertyValuesHolder()、ofObject()、ofInt()等方法。

  除了上面的功能,ValueAnimator还可以通过setRepeatCount()和setRepeatMode()方法来设置动画重复的次数和播放模式、通过setStartDelay()方法来设置动画延迟播放的时间。这些方法也都可以在ObjectAnimator中使用。

 

5、Animator监听器

  在“组合动画”章节中我们已经接触过Animator的监听器了。常用的Animator监听器有AnimatorListener和AnimatorListenerAdapter,都是通过Animator对象的addListener()方法设置的。

  如果添加的监听器是AnimatorListener,那么就必须要实现onAnimationStart()、onAnimationRepeat()、onAnimationEnd()和onAnimationCancel()四个方法,示例代码如下:

复制代码

animator.addListener(new Animator.AnimatorListener() {
    @Override    public void onAnimationStart(Animator animation) {        // 动画开始的监听事件    }

    @Override    public void onAnimationEnd(Animator animation) {        // 动画结束的监听事件    }

    @Override    public void onAnimationCancel(Animator animation) {        // 动画取消的监听事件    }

    @Override    public void onAnimationRepeat(Animator animation) {        // 动画重播的监听事件    }
});

复制代码

 如果添加的是AnimatorListenerAdapter,那么就不必一一实现这四个方法,而是可以根据自己的需要自定义实现,示例代码如下:

复制代码

animator.addListener(new AnimatorListenerAdapter() {
    @Override    public void onAnimationEnd(Animator animation) {        // 动画结束的监听事件    }

    @Override    public void onAnimationStart(Animator animation) {        // 动画开始的监听事件    }    
    // 其他两个事件可以选择不实现});

复制代码

 

6、使用XML编写动画

  过去的补间动画可以通过XML的方式编写,属性动画也可以编写到XML文件中。编写到XML文件中的一个好处是可以方便的实现动画的重用。

  我们需要在项目的res目录下创建一个名为animator的文件夹,在这个文件夹中定义动画。animator动画XML文件中可以包括以下三种标签:

  • <animator>:相当于JAVA代码中的ValueAnimator;

  • <objectAnimator>:相当于JAVA代码中的ObjectAnimator;

  • <set>:相当于JAVA代码中的AnimatorSet。

  如果我们想直接定义一个ValueAnimator,可以这样写:

复制代码

<?xml version="1.0" encoding="utf-8"?><animator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000"
    android:interpolator="@android:anim/bounce_interpolator"
    android:repeatCount="2"
    android:repeatMode="restart"
    android:valueFrom="0"
    android:valueTo="100"
    android:valueType="intType" />

复制代码

 如果我们想直接定义一个ObjectAnimator,可以这样写:

复制代码

<?xml version="1.0" encoding="utf-8"?><objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
    android:duration="5000"
    android:interpolator="@android:anim/accelerate_interpolator"
    android:propertyName="scaleX"
    android:repeatCount="2"
    android:repeatMode="reverse"
    android:valueFrom="0"
    android:valueTo="360"
    android:valueType="floatType" />

复制代码

 如果我们想要定义一系列动画,可以这样写:

复制代码

<?xml version="1.0" encoding="utf-8"?><set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="sequentially">

    <objectAnimator        android:duration="5000"
        android:propertyName="translationX"
        android:valueFrom="0"
        android:valueTo="500"
        android:valueType="floatType" />

    <objectAnimator        android:duration="5000"
        android:propertyName="translationY"
        android:valueFrom="0"
        android:valueTo="500"
        android:valueType="floatType" />

    <set android:ordering="together">
        <objectAnimator            android:duration="5000"
            android:propertyName="scaleX"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" />

        <objectAnimator            android:duration="5000"
            android:propertyName="scaleY"
            android:valueFrom="0"
            android:valueTo="360"
            android:valueType="floatType" />
    </set></set>

复制代码

 在JAVA代码中调用这些XML文件中定义的动画时这样写:
Animator animator = AnimatorInflater.loadAnimator(MainActivity.this,R.animator.object_animator);
animator.setTarget(view);
animator.start();

 

7、TypeEvaluator

  TypeEvaluator的作用是告诉动画如何从初始值过渡到结束值。

  我们一直在使用的ObjectAnimator.ofFloat()方法中其实就封装了一个FloatEvaluator,FloatEvaluator是TypeEvaluator的一个子类,可以在指定的float类型的初始值与结束值之间进行平滑的过渡,源码如下:

复制代码

public class FloatEvaluator implements TypeEvaluator<Number> {    public Float evaluate(float fraction, Number startValue, Number endValue) {        float startFloat = startValue.floatValue();        return startFloat + fraction * (endValue.floatValue() - startFloat);
    }
}

复制代码

  可以看到,FloatEvaluator实现了TypeEvaluator接口并实现了器evaluate方法,在方法中第一个参数是进度,相当于百分比;第二个和第三个参数是开始值和结束值,通过这三个值来确定当前需要的属性值并返回。

  前面说过,ValueAnimator和ObjectAnimator除了常用的ofFloat()、ofInt()方法之外,还有一个ofObject()方法,这个方法是对任意对象的属性进行动画操作的,但是Android不知道我们的任意对象是什么对象,因此也不确定我们的属性是怎样从初始值过渡到结束值的,这个时候我们就需要实现一个自己的TypeEvaluator来告诉系统我们的对象的属性是如何过渡的。

  例如,我们自定义一个动画,让一个TextView中的文本从“0%”变化到“100%”,核心代码如下:

复制代码

ValueAnimator animator = ValueAnimator.ofObject(new TypeEvaluator () {
    @Override    public Object evaluate(float fraction, Object startValue, Object endValue) {        int start = Integer.parseInt((String) startValue);        int end = Integer.parseInt((String) endValue);        int result = (int) ((start + fraction * (end - start)));        return result + "%";
    }
}, "0", "100");
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override    public void onAnimationUpdate(ValueAnimator animation) {
        String result = (String) animation.getAnimatedValue();
        text.setText(result);
    }
});
animator.setDuration(5000);
animator.start();

复制代码

 

8、Interpolator

  Interpolator的作用是可以控制动画的变化速率。

  Android系统中默认为我们提供了多种已经实现好的Interpolator,常用的如下:

复制代码

1)  BounceInterpolator:弹跳效果;
2)  AccelerateInterpolator:逐渐加速;
3)  DecelerateInterpolator:逐渐减速;
4)  AccelerateDecelerateInterpolator:先加速后减速;
5)  OvershootInterpolator:到达目标点时“跑过头了”,再返回到目标点;
6)  AnticipateInterpolator:移动之前先向后“助跑”;
7)  AnticipateOvershootInterpolator:OvershootInterpolator和AnticipateInterpolator的组合效果;
8)  CycleInterpolator:对于指定的动画,正向做一遍,反响做一遍;

复制代码

  Android中还为我们提供了一个接口TimeInterpolator,供我们自定义插值器。我们也可以实现TimeInterpolator的一些已有实现类(如BaseInterpolator、Interpolator)来自定义插值器。

  TimeInterpolator接口中又一个抽象方法setInterpolation(),方法中又一个参数input,这个参数的值在整个动画过程中从0匀速变化到1,也就是相当于一个百分比,指示当前动画播放到哪了。

  如果我们在这个方法中直接返回input参数,那么这个插值器就是一个匀速插值器,Android默认给我们提供的LinearInterpolator就是这样写的,源码如下:

public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {    public float getInterpolation(float input) {        return input;  //匀速    }
}
 当然,如果我们修改input的值并返回,就会得到加速或减速的效果,如Android默认提供的AccelerateDecelerateInterpolator中的源码:
public class AccelerateDecelerateInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {    public float getInterpolation(float input) {        return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;  //先加速后减速    }
}
 有了以上的源码作为参考,我们就可以写自定义的插值器了。例如,我们写一个先减速后加速的插值器DecelerateAccelerateInterpolator,代码如下:

复制代码

public class DecelerateAccelerateInterpolator implements TimeInterpolator {
    @Override    public float getInterpolation(float input) {        float result;        if (input <= 0.5) {
            result = (float) (Math.sin(Math.PI * input)) / 2;  //前半部分减速
        } else {
            result = (float) (2 - Math.sin(Math.PI * input)) / 2;  //后半部分加速        }        return result;
    }
}

复制代码

 

9、ViewPropertyAnimator

  ViewPropertyAnimator是在Android 3.1的API中新加入的更加易懂、更加便于使用的动画API。

  属性动画虽然可以对所有对象添加动画效果,但我们在使用过程中还是以对View使用动画为多,因此Google为我们提供了一个ViewPropertyAnimator,让我们可以简单的调用这套API来单独为View设置动画。

  ViewPropertyAnimator让我们可以使用链式编程,一行代码为一个View对象设置属性,示例代码如下:

复制代码

textView.animate()
        .translationX(200)
        .translationY(200)
        .setDuration(2000)
        .setInterpolator(new BounceInterpolator())
        .start();

复制代码

  当我们对一个View对象使用animate()方法时,就返回了一个ViewPropertyAnimator对象,这个对象中支持所有补间动画的API以及属性动画的API。ViewPropertyAnimator是默认执行的动画,也就是说,我们不需要在最后调用start()方法,动画也会自动播放。

  • 2019-12-06 10:47:29

    date-fns日期工具的使用方法详解

    isToday() 判断传入日期是否为今天 isYesterday() 判断传入日期是否为昨天 isTomorrow() 判断传入日期是否为 format() 日期格式化 addDays() 获得当前日期之后的日期 addHours() 获得当前时间n小时之后的时间点 addMinutes() 获得当前时间n分钟之后的时间 addMonths() 获得当前月之后n个月的月份 subDays() 获得当前时间之前n天的时间 subHours() 获得当前时间之前n小时的时间 subMinutes() 获得当前时间之前n分钟的时间 subMonths() 获得当前时间之前n个月的时间 differenceInYears() 获得两个时间相差的年份 differenceInWeeks() 获得两个时间相差的周数 differenceInDays() 获得两个时间相差的天数 differenceInHours() 获得两个时间相差的小时数 differenceInMinutes() 获得两个时间相差的分钟数

  • 2019-12-06 10:49:39

    npm 查看源 换源

    npm,cnpm,查看源,切换源,npm config set registry https://registry.npmjs.org

  • 2019-12-06 11:01:31

    npm发布包流程详解 有demo

    npm发布包步骤,以及踩过的坑(见红颜色标准): 1.注册npm账号,并完成Email认证(否则最后一步提交会报Email错误) 2.npm添加用户或登陆:npm adduser 或 npm login

  • 2019-12-06 13:16:18

    vue mixins组件复用的几种方式

    最近在做项目的时候,研究了mixins,此功能有妙处。用的时候有这样一个场景,页面的风格不同,但是执行的方法,和需要的数据非常的相似。我们是否要写两种组件呢?还是保留一个并且然后另个一并兼容另一个呢? 不管以上那种方式都不是很合理,因为组件写成2个,不仅麻烦而且维护麻烦;第二种虽然做了兼容但是页面逻辑造成混乱,必然不清晰;有没有好的方法,有那就是用vue的混合插件mixins。混合在Vue是为了提出相似的数据和功能,使代码易懂,简单、清晰。

  • 2019-12-06 13:26:30

    vue的mixins混入合并规则

    混入minxins:分发vue组件中可复用功能的灵活方式。混入对象可以包含任意组件选项。组件使用混入对象时,所有混入对象的选项将混入该组件本身的选项。

  • 2019-12-06 16:50:34

    Intellij idea 如何关闭无用的提示

    Linux:Settings —> Editor —> Inspections —> General —> Duplicated Code Mac:Preferences --> Editor —> Inspections —> General —> Duplicated Code fragment 将对应的勾去掉。

  • 2019-12-09 15:36:56

    神秘的 shadow-dom 浅析,shadow-root

    顾名思义, shadow-dom,直译的话就是 影子dom ?我觉得可以理解为潜藏在黑暗中的 DOM 结构,也就是我们无法直接控制操纵的 DOM 结构。前端同学经常用开发者工具的话,查看 DOM 结构的时候,肯定看到过下面这样的结构:

  • 2019-12-10 11:13:50

    前端实战-基于Nuxt的SVG使用

    虽然我们在日常开发的时候,在使用iview 或者element ui等组件时,通常会包含一些常用icon;但是在面对一些特定的需求时,或者自己想high一下,这些通用的icon并不能很好的满足我们。这个时候我们可能会拿到一些SVG适量图,但是怎么去使用这些矢量图呢。