自定义View(六)-动画- AnimatorSet与XML设置属性动画

介绍

AnimatorSet是组合动画,前面在ObjectAnimator.ofPropertyValuesHolder(),时也可以做到控制多个属性做动画,但是.ofPropertyValuesHolder(),仅仅是将多个属性同时做动画却无法灵活控制每个属性的播放顺序,针对的是一个控件,而AnimatorSet是组合动画。更侧重的是在多个动画播放时对动画的控制(可以控制动画的顺序,延时,同时可以控制多个控件的动画等等)。


<font color=#006400 size=6>AnimatorSet</font>

AnimatorSet针对ValueAnimator和ObjectAnimator都是适用的,但一般而言,我们不会用到ValueAnimator的组合动画,所以我们仅讲解ObjectAnimator下的组合动画实现。

主要方法:

  1. playSequentially :表示所有动画依次播放
  2. playTogether :表示所有动画一起开始。
  • <font color=#006400>playSequentially :</font>

方法参数:

public void playSequentially(Animator... items);
public void playSequentially(List<Animator> items);

第一个是我们最常用的,它的参数是可变长参数,也就是说我们可以传进去任意多个Animator对象。这些对象的动画会逐个播放。第二个构造函数,是传进去一个List< Animator>的列表。原理一样,也是逐个去取List中的动画对象,然后逐个播放。

使用:

·
  private void doPlaySequentiallyAnimator() {
        ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(mTv1, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
        tv1BgAnimator.setEvaluator(new ArgbEvaluator());
        ObjectAnimator tv1TranslateY = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 300, 0);
        ObjectAnimator tv2TranslateY = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 300, 0);

            AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(tv1BgAnimator, tv1TranslateY, tv2TranslateY);
        animatorSet.setDuration(1000);
        animatorSet.start();
    }

当按钮被点击时执行doPlaySequentiallyAnimator()方法。效果如下:

GIF11.gif

这就是playSequentially的效果,即逐个播放动画,一个动画结束后,播放下一个动画,播放的顺序就是传入动画的先后顺序。(这里也可以看出AnimatorSet是针对于动画,并不管你在动画是在哪个控件或是几个控件,这就与.ofPropertyValuesHolder()不同)

  • <font color=#006400>playTogether :</font>

playTogether表示将所有动画一起播放 。

public void playTogether(Animator... items);
public void playTogether(Collection<Animator> items);

参数含义与上面一致。
代码:

animatorSet.playTogether(tv1BgAnimator, tv1TranslateY, tv2TranslateY);

其他代码去上面一致.效果:

GIF12.gif

此时三个动画一起播放。

  • <font color=#006400>playSequentially,playTogether真正意义 :</font>
    • <font color=#DC143C>playTogether :</font> 只是一个时间点上的一起开始,对于开始后,各个动画怎么操作就是他们自己的事了,至于各个动画结不结束也是他们自已的事了。
    • <font color=#DC143C>playSequentially : </font> 意义是把激活一个动画之后,动画之后的操作就是动画自己来负责了,这个动画结束之后,再激活下一个动画。如果上一个动画没有结束,那下一个动画就永远也不会被激活。

首先用playTogether来看个例子:

GIF12.gif

将tv1TranslateY开始延迟2000毫秒开始,并设为无限循环。tv2TranslateY设为开始延迟2000毫秒。而tv1BgAnimator则是没有任何设置,所以是默认直接开始。
从这个例子中也可以看到,playTogether只是负责在同一时间点一起开始,对于开始后,各个动画怎么操作就是他们自己的事了,至于各个动画结不结束也是他们自已的事了。

将播放改成playSequentially顺序播放动画:

·
 private void doPlaySequentiallyAnimator2() {
        ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(mTv1, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
        tv1BgAnimator.setEvaluator(new ArgbEvaluator());
        ObjectAnimator tv1TranslateY = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
        tv1TranslateY.setRepeatCount(ValueAnimator.INFINITE);
        ObjectAnimator tv2TranslateY = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playSequentially(tv1BgAnimator, tv1TranslateY, tv2TranslateY);
        animatorSet.setDuration(2000);
        animatorSet.start();
    }

效果:

GIF13.gif

tv1BgAnimator颜色改变后移动,并设置成无限循环。那么tv2TranslateY永远无法得到执行。

总结:
<font color=#006400>

  • 第一:playTogether和playSequentially在激活动画后,控件的动画情况与它们无关,他们只负责定时激活控件动画。
  • 第二:playSequentially只有上一个控件做完动画以后,才会激活下一个控件的动画,如果上一控件的动画是无限循环,那下一个控件就别再指望能做动画了。
    </font>

AnimatorSet.Builder-自由设置动画顺序

上面两种播放方法只能一起播放或者顺序播放,无法指定某一个动画的播放顺序,如果想ABC三个动画想指定C先播放就要用到AnimatorSet.Builder。
AnimatorSet.Builder可以更加灵活设置动画播放的先后顺序。

  • <font color=#006400>AnimatorSet.Builder :</font>
//调用AnimatorSet中的play方法是获取AnimatorSet.Builder对象的唯一途径
//表示要播放哪个动画
public Builder play(Animator anim)
  • <font color=#006400>更多方法 :</font>
//和前面动画一起执行
public Builder with(Animator anim)
//执行前面的动画后才执行该动画
public Builder before(Animator anim)
//执行先执行这个动画再执行前面动画
public Builder after(Animator anim)
//延迟n毫秒之后执行动画
public Builder after(long delay)

<font color=#DC143C>注意: play(Animator anim)表示当前在播放哪个动画,另外的with(Animator anim)、before(Animator anim)、after(Animator anim)都是以play中的当前所播放的动画为基准的</font>。
例如: 当play(playAnim)与before(beforeAnim)共用,则表示在播放beforeAnim之前,先播放playAnim动画;同样,当play(playAnim)与after(afterAnim)共用时,则表示在播放afterAnim动画之后,再播放playAnim动画。

  • <font color=#006400>使用 :</font>

方式一:使用builder对象逐个添加动画

AnimatorSet.Builder builder = animatorSet.play(tv1TranslateY);
builder.with(tv2TranslateY);
builder.after(tv1BgAnimator);

方式二:串行方式

animatorSet.play(tv1TranslateY).with(tv2TranslateY).after(tv1BgAnimator);

如下:

ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(mTv1, "BackgroundColor",  0xffff00ff, 0xffffff00, 0xffff00ff);
ObjectAnimator tv1TranslateY = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
ObjectAnimator tv2TranslateY = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);

AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(tv1TranslateY).with(tv2TranslateY).after(tv1BgAnimator);
animatorSet.setDuration(2000);
animatorSet.start();

表示在tv1颜色变化后,两个控件一同开始位移动画:

GIF14.gif

AnimatorSet监听器

因为ValueAnimator和AnimatorSet都派生自Animator类,而AnimatorListener是Animator类中的函数。所以都是使用相同的监听:

/** 
 * 监听器二:监听动画变化时四个状态 
 */ 
public static interface AnimatorListener {
    /**
     * 当AnimatorSet开始时调用
     */
    void onAnimationStart(Animator animation);

    /**
     * 当AnimatorSet结束时调用
     */
    void onAnimationEnd(Animator animation);

    /**
     * 当AnimatorSet被取消时调用
     */
    void onAnimationCancel(Animator animation);

    /**
     * 当AnimatorSet重复时调用,由于AnimatorSet没有设置repeat的函数,所以这个方法永远不会被调用
     */
    void onAnimationRepeat(Animator animation);
}
 
//添加方法为:public void addListener(AnimatorListener listener)   
/** 
 * 监听器三:监听动画暂停与重新开始
 */ 
public static interface AnimatorPauseListener {
       
        void onAnimationPause(Animator animation);
        void onAnimationResume(Animator animation);
    }
//添加方法为:public void addPasueListener(AnimatorPauseListener listener)

同时我们也可以传入AnimatorListenerAdapter监听器,他是一个抽象方法,里面实现了Animator.AnimatorListener,Animator.AnimatorPauseListener接口,这样我们可以只实现我们需要的方法。这里我全部实现因为为了打印看出效果:

GIF16.gif

虽然我们的tv2TranslateY动画在无限循环,但Log中没有打印出对应的repeat的日志,从日志中也可以看出,AnimatorSet的监听函数也只是用来监听AnimatorSet的状态的,与其中的动画无关;

总结:
<font color=#006400>

  • AnimatorSet的监听函数也只是用来监听AnimatorSet的状态的,与其中的动画无关;
  • AnimatorSet中没有设置循环的函数,所以AnimatorSet监听器中永远无法运行到onAnimationRepeat()中!
    </font>

AnimatorSet设置与单个动画属性冲突时

  • <font color=#006400>常见冲突函数 </font>
//设置单次动画时长
public AnimatorSet setDuration(long duration);
//设置加速器
public void setInterpolator(TimeInterpolator interpolator)
//设置ObjectAnimator动画目标控件
public void setTarget(Object target)

这几个函数在ObjectAnimator也存在,当单个动画设置上面的属性,同时组合动画AnimatorSet也设置了相同的属性。则遵循下面的规则:

<font color=#006400>在AnimatorSet中设置以后,会覆盖单个ObjectAnimator中的设置;即如果AnimatorSet中没有设置,那么就以ObjectAnimator中的设置为准。如果AnimatorSet中设置以后,ObjectAnimator中的设置就会无效。
</font>

例如:
<font color=#DC143C>

  1. 当单个动画(ObjectAnimator)与组合动画(AnimatorSet)同时设置setDuration(long duration)动画时长,那么所有单个动画设置的时长失效。如果组合动画(AnimatorSet)没有设置setDuration(long duration)动画时长,那么会每个动画会根据自己的时长做动画。
  2. 当单个动画(ObjectAnimator)与组合动画(AnimatorSet)同时设置setInterpolator,那么所有单个动画设置的加速器失效。如果组合动画(AnimatorSet)没有设置加速器,那么会每个动画会根据自己的加速器做动画。
    </font>
    如下:
·
    private void doPlaySequentiallyAnimator4() {
        ObjectAnimator tv1TranslateY = ObjectAnimator.ofFloat(mTv1, "translationY", 0, 400, 0);
        tv1TranslateY.setDuration(50000000);
        tv1TranslateY.setInterpolator(new BounceInterpolator());

        ObjectAnimator tv2TranslateY = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);
        tv2TranslateY.setDuration(5000);
        tv2TranslateY.setInterpolator(new AccelerateDecelerateInterpolator());

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.setDuration(2000);
        animatorSet.play(tv2TranslateY).with(tv1TranslateY);
        animatorSet.start();
    }

效果:

GIF17.gif

此时tv1TranslateY与tv2TranslateY都设置了时长同时AnimatorSet也设置了时长,发现只有animatorSet.setDuration(2000);生效,由于animatorSet没有设置加速器,所有动画执行各自的加速器。
<font color=#DC143C>

  1. AnimatorSet.setTarget()的作用就是将动画的目标统一设置为当前控件,AnimatorSet中的所有动画都将作用在所设置的target控件上
    </font>

如下:

 ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(mTv1, "BackgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
        ObjectAnimator tv2TranslateY = ObjectAnimator.ofFloat(mTv2, "translationY", 0, 400, 0);

        AnimatorSet animatorSet = new AnimatorSet();
        animatorSet.playTogether(tv1BgAnimator,tv2TranslateY);
        animatorSet.setDuration(2000);
        animatorSet.setTarget(mTv2);
        animatorSet.start();
GIF18.gif

setStartDelay(long startDelay)

//设置延时开始动画时长
public void setStartDelay(long startDelay)

当AnimatorSet所拥有的函数与单个动画所拥有的函数冲突时,就以AnimatorSet设置为准。但唯一的例外就是setStartDelay。
setStartDelay函数不会覆盖单个动画的延时,而且仅针对性的延长AnimatorSet的激活时间,单个动画的所设置的setStartDelay仍对单个动画起作用。
遵循原则:
<font color=#DC143C>

  • AnimatorSet的延时是仅针对性的延长AnimatorSet激活时间的,对单个动画的延时设置没有影响。
  • AnimatorSet真正激活延时 = AnimatorSet.startDelay+第一个动画.startDelay
  • 在AnimatorSet激活之后,第一个动画绝对是会开始运行的,后面的动画则根据自己是否延时自行处理。
    </font>

<font color=#006400 size=6>XML实现属性动画</font>

ValueAnimator、ObjectAnimator和AnimatorSet属性动画也可以在SML中设置。在res/animator/目录下创建XML属性动画详细可以参照
属性动画官方文档
下面只记录简单实用:

  • <font color=#006400>Animator(ValueAnimator):</font>

XML标签含义:

<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"]
    android:interpolator=["@android:interpolator/XXX"]/>
  • android:duration:每次动画播放的时长
  • android:valueFrom:初始动化值;取值范围为float,int和color,如果取值为float对应的值样式应该为89.0,取值为Int时,对应的值样式为:89;当取值为clolor时,对应的值样式为 #333333;
  • android:valueTo:动画结束值;取值范围同样是float,int和color这三种类型的值;
  • android:startOffset:动画激活延时;对应代码中的startDelay(long delay)函数;
  • android:repeatCount:动画重复次数
  • android:repeatMode:动画重复模式,取值为repeat和reverse;repeat表示正序重播,reverse表示倒序重播
  • android:valueType:表示参数值类型,取值为intType和floatType;与android:valueFrom、android:valueTo相对应。如果这里的取值为intType,那么android:valueFrom、android:valueTo的值也就要对应的是int类型的数值。如果这里的数值是floatType,那么android:valueFrom、android:valueTo的值也要对应的设置为float类型的值。非常注意的是,如果android:valueFrom、android:valueTo的值设置为color类型的值,那么不需要设置这个参数;
  • android:interpolator:设置加速器;有关系统加速器所对应的xml值对照表如下:
    image

XML:

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

代码:

ValueAnimator valueAnimator= (ValueAnimator) AnimatorInflater.loadAnimator(this,R.animator.animator);
                valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
                    @Override
                    public void onAnimationUpdate(ValueAnimator valueAnimator) {
                        int value= (int) valueAnimator.getAnimatedValue();
                        tv_text.layout(value,value,tv_text.getWidth()+value,tv_text.getHeight()+value);
                    }
                });
                valueAnimator.start();

效果:

GIF19.gif

  • <font color=#006400>objectAnimator(ObjectAnimator):</font>
<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"]
    android:interpolator=["@android:interpolator/XXX"]/>

参数:

  • android:propertyName:对应属性名,即ObjectAnimator所需要操作的属性名。
    其它字段的意义与animator的意义与取值是一样的,下面再重新列举一下。
  • android:duration:每次动画播放的时长
  • android:valueFrom:初始动化值;取值范围为float,int和color;
  • android:valueTo:动画结束值;取值范围同样是float,int和color这三种类型的值;
  • android:startOffset:动画激活延时;对应代码中的startDelay(long delay)函数;
  • android:repeatCount:动画重复次数
  • android:repeatMode:动画重复模式,取值为repeat和reverse;repeat表示正序重播,reverse表示倒序重播
  • android:valueType:表示参数值类型,取值为intType和floatType;与android:valueFrom、android:valueTo相对应。如果这里的取值为intType,那么android:valueFrom、android:valueTo的值也就要对应的是int类型的数值。如果这里的数值是floatType,那么android:valueFrom、android:valueTo的值也要对应的设置为float类型的值。非常注意的是,如果android:valueFrom、android:valueTo的值设置为color类型的值,那么不需要设置这个参数;
  • android:interpolator:设置加速器;

XML:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:propertyName="TranslationY"
                android:duration="2000"
                android:valueFrom="0.0"
                android:valueTo="400.0"
                android:interpolator="@android:anim/accelerate_interpolator"
                android:valueType="floatType"
                android:repeatCount="1"
                android:repeatMode="reverse"
                android:startOffset="2000"
    >
</objectAnimator>

代码:

 ObjectAnimator objectAnimator= (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.objectanimator);
                objectAnimator.setTarget(tv_text);
                objectAnimator.start();

效果:

GIF20.gif
  • <font color=#006400>ObjectAnimator设置背景颜色:</font>

XML:

<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
                android:propertyName="BackgroundColor"
                android:duration="5000"
                android:valueFrom="#ffff00ff"
                android:valueTo="#ffffff00"/>

代码:

ObjectAnimator objectAnimator= (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.animatorcolor);
        objectAnimator.setTarget(mTv1);
        objectAnimator.start();

效果:

GIF.gif
  • <font color=#006400>Set(AnimatorSet):</font>
    XML:
<?xml version="1.0" encoding="utf-8"?>
<set xmlns:android="http://schemas.android.com/apk/res/android"
    android:ordering="together">
    <objectAnimator
        android:propertyName="x"
        android:duration="500"
        android:valueFrom="0"
        android:valueTo="400"
        android:valueType="floatType"/>
    <objectAnimator
        android:propertyName="y"
        android:duration="500"
        android:valueFrom="0"
        android:valueTo="300"
        android:valueType="floatType"/>
</set>
<!--这里有两个objectAnimator动画,一个改变值x坐标,一个改变值y坐标;取值分别为0-400和0-300;然后在代码中加载-->

代码:

 ObjectAnimator objectAnimator= (ObjectAnimator) AnimatorInflater.loadAnimator(this,R.animator.animatorcolor);
        objectAnimator.setEvaluator(new ArgbEvaluator());
        objectAnimator.setTarget(mTv1);
        objectAnimator.start();

效果:

GIF.gif

更多动画

  • LayoutAnimation : viewGroup添加进入统一动画的
  • gridLayoutAnimation : grideView添加进入动画的
  • android:animateLayoutChanges属性 : 在API 11之后,Android为了支持ViewGroup类控件,在添加和移除其中控件时自动添加动画,为我们提供了一个非常简单的属性:android:animateLayoutChanges=[true/false],所有派生自ViewGroup的控件都具有此属性,只要在XML中添加上这个属性,就能实现添加/删除其中控件时,带有默认动画了。
  • LayoutTransaction 对animateLayoutChanges属性的扩展,可以使用自定义删除/添加动画。

结语

到此动画部分到此结束。后期根据自身的理解如果学习到了新得关于动画的知识会继续记录动画相关的知识点。这是本人的学习笔记。十分感谢启航大神。也希望大家多多支持。下篇文章将会讲解Acitvity启动布局的加载。


感谢

站在巨人的肩膀上可以让我们看的更远。
Android自定义控件三部曲文章

推荐阅读更多精彩内容

  • Animation Animation类是所有动画(scale、alpha、translate、rotate)的基...
    四月一号阅读 969评论 0 9
  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 122,516评论 15 533
  • 1 背景 不能只分析源码呀,分析的同时也要整理归纳基础知识,刚好有人微博私信让全面说说Android的动画,所以今...
    未聞椛洺阅读 1,020评论 0 7
  • 有个好兄弟身体出了些状况,让我帮他打听哪个医院治的好。他给我的信息里,一个是某个病友大姐介绍某个四线城市的医院,...
    大司马大将军阅读 40评论 0 1
  • 下午康乃馨到货,很高心,虽然开得还不是很灿烂,需要养两三天才到盛开时期,但是我依然很激动。还发了人朋友圈,评论还公...
    罗xuexue阅读 13评论 0 0