属性动画详细介绍(一)

属性动画我决定用两篇文章做总结

一、属性动画基础内容

二、ValueAnimator

从名字就可以看出,这是针对值的动画,它并不会对View做出任何动画效果。使用ValueAnimator可以让某一个值在设定时间内平滑过渡成另一个值,根据值的变换过程,自己操作View的变换。

2.1、使用ValueAnimator

要使用ValueAnimator非常简单,首先通过ValueAnimator提供的静态方法创建其对象,然后设置动画的时长,最后调用start()方法开启动画即可。下面我们来创建一个ValueAnimator,它能在2秒内从0变换到500:

ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
valueAnimator.setDuration(2000);
valueAnimator.start();

没错就是这么简单,运行后的确在两秒内值从0一直平滑过渡到了500。可是我们需要过程而不是结果呀,如果没办法监听到值变换的过程,就没办法利用变换中的值给View设置动画了。要想监听值变化的过程,我们可以使用addUpdateListener()方法给valueAnimator加上监听事件:

valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
        int value = (int) animation.getAnimatedValue();
        Log.i("HurryYu", "value:" + value);
    }
});

在回调中,animation表示当前ValueAnimator对象,使用animation.getAnimatedValue()就可以获取到当前变换的值(默认是返回Object类型,由于使用的是ofInt()创建的ValueAnimator对象,因此可以直接强转为int类型)。此时观察Logcat,得到如下输出:

2019-05-11 15:19:36.858 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:0
2019-05-11 15:19:36.960 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:0
2019-05-11 15:19:37.253 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:26
2019-05-11 15:19:37.626 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:121
2019-05-11 15:19:37.654 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:134
2019-05-11 15:19:37.669 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:140
2019-05-11 15:19:37.695 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:147
2019-05-11 15:19:37.705 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:153
.
.
.
2019-05-11 15:19:38.885 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:498
2019-05-11 15:19:38.903 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:499
2019-05-11 15:19:38.941 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:499
2019-05-11 15:19:38.958 14906-14906/com.hurryyu.viewdemo I/HurryYu: value:500

现在明白ValueAnimator了吧,实际上就是对给定区间的值进行平滑变换,我们需要自己监听值的变换过程,然后自己对View执行相应的动画效果。就拿上面的这个从0平滑过渡到500的ValueAnimator做实验,现在我们准备利用它让View在X轴方向上向右平移,首先我们完成布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/view"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@color/colorPrimary" />

    <TextView
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HurryYu" />
</LinearLayout>

效果如下图:

下面完成代码部分:

public class MainActivity extends AppCompatActivity {

    private View view;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        view = findViewById(R.id.view);
        view.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Toast.makeText(MainActivity.this, "看看有效果吗?",
                        Toast.LENGTH_SHORT).show();
            }
        });
        startAnim();
    }

    private void startAnim() {
        ValueAnimator valueAnimator = ValueAnimator.ofInt(0, 500);
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                int value = (int) animation.getAnimatedValue();
                view.setTranslationX(value);
            }
        });
        valueAnimator.start();
    }
}

请注意看ValueAnimator的回调中,我们使用了view.setTranslationX(value)来改变view在X轴上的偏移量,其中value会在两秒内从0变换到500。最终动画完成后的效果如图:

点击动画完成后的View,仍然可以响应点击事件。不过奇怪的是,父布局是水平线性布局,View向右移动后,TextView应该也会跟着向右移动呀,可是为什么没有呢?因为我们使用的是setTranslationX()去改变view的偏移量,它的确能改变view的位置,但它并不会改变view的LayoutParams中的margin属性值,即不会影响getLeft()与getRight()。

2.2、ValueAnimator的常用方法

2.1.1、其它静态工厂方法

之前我们已经使用过ValueAnimator.ofInt()方法创建了对象,除了ofInt()以外,它还提供了如下静态工程方法:

①、来看ofArgb,ofInt是对int类型的数字进行变换,而它的作用是可以对颜色进行过度变换,我们还注意到,这些方法接收的都是可变参数,意味可以传入任意个参数,它将依次变换。现在我想把按钮的颜色从红色过度到绿色再过度到蓝色,首先创建ValueAnimator:

private void changeColor() {
    ValueAnimator valueAnimator = ValueAnimator.ofArgb(0xFFFF5454, 0xFF5DDE5D, 0xFF5DBEDE);
	valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            int color = (int) animation.getAnimatedValue();
            button.setBackgroundColor(color);
        }
    });
    valueAnimator.setDuration(2000);
    valueAnimator.start();
}

这里注意ofArgb需要接收ARGB的颜色。效果如下所示:

②、ofFloat就不必多说了吧。

③、ofObject这个我们后面再来研究,因为它需要我们提供自定义的Evaluator。

2.1.2、常用对象方法

前面我们已经接触过了几个ValueAnimator提供的方法,比如setDuration用来设置动画时长,getAnimatedValue用来获取当前变换中的值,start用来开始动画。下面我们再来学习几个常用的方法:

①、setRepeatCount用于设置动画的重复次数,传入0表示不重复,传入ValueAnimator.INFINITE表示无限重复,默认是不重复,这个方法比较好理解。

②、setRepeatMode用于设置动画重复的模式,有两种选择,一种是正序重复(ValueAnimator.RESTART),另一种是倒序重复(ValueAnimator.REVERSE),默认是正序重复。这两种有什么区别呢?拿按钮变颜色的例子来说,如果我们把重复次数设定为无限重复,那么每一次重复时,都会从最后的蓝色突然变成开始的红色然后继续新一轮动画,这就是正序:

而如果将RepeatMode设置为ValueAnimator.REVERSE,将使用倒序的方式进行动画的重复,即:

第一轮:红-绿-蓝

第二轮:蓝-绿-红

第三轮:红-绿-蓝

以此类推,这样就不存在突然从第三种颜色变为第一种颜色的情况:

③、cancel用于取消当前动画。

2.1.3、常用监听方法

我们已经使用过一个监听(addUpdateListener),它用于监听动画执行过程中值的变化。ValueAnimator除了能监听值的变化以外,还能监听动画执行过程中的状态,可以使用addListener进行添加:

valueAnimator.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) {
    }
});
  • onAnimationStart:显然是在开始执行动画的时候调用。

  • onAnimationRepeat:在每次重复执行时调用。

  • onAnimationCancel:在取消动画的时候调用。

  • onAnimationEnd:在动画结束的时候调用,注意,无论是正常结束还是调用cancel结束,此方法都会被回调

如果我们只想监听这四种动画状态中的其中一个或是少数几个,实现Animator.AnimatorListener这个接口的匿名内部类代价就太大了,因此我们可以使用AnimatorListenerAdapter这个抽象类,实际上它对Animator.AnimatorListener里面的方法都做了空实现,我们需要用到哪个方法就去重写哪个方法就行了:

valueAnimator.addListener(new AnimatorListenerAdapter() {
    @Override
    public void onAnimationStart(Animator animation) {
    }
});

顺便提一句,为何对Animator.AnimatorListener里的方法都提供了空实现,还要把类声明成abstract的呢?这是因为abstract类不能被直接实例化,这样就能逼迫调用者至少去重写里面的一个方法。

2.3、Interpolator插值器

插值器的作用是控制动画在执行过程中的速度,比如有一个valueAnimator,它能使一个值在两秒内从0过渡到200,那过渡中的速度到底是怎么样的呢?先快后慢?匀速?先慢后快?控制值在变化中的速度,就是靠Interpolator来完成的。

系统已经为我们提供了非常多的插值器供我们选择,这里选择最为简单的一个插值器(LinearInterpolator)来研究下Interpolator:

public class LinearInterpolator implements Interpolator {

    public LinearInterpolator() {
    }

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

我对LinearInterpolator类的代码稍作删减,便于查看。首先它实现了Interpolator接口,只要实现了Interpolator(TimeInterpolator)接口的类就可以是一个插值器。我们来看看Interpolator接口的代码:

public interface Interpolator extends TimeInterpolator {
}

这个接口只是继承了TimeInterpolator接口,除此之外什么都没做。来看最终的TimeInterpolator接口:

public interface TimeInterpolator {
    float getInterpolation(float input);
}

因此在插值器中只需要实现getInterpolation()方法就可以了。重点也就是在这个方法上,下面我将详细说明一下这个方法的作用:它接收一个float类型的参数,这个参数的意思是表示当前动画执行到的进度,取值范围是0~1,在动画刚刚开始时,值为0,在动画完成时,值为1。这个进度值时系统帮我们计算出的,它永远是匀速增加的,不受任何设置的影响。比如一秒内值从1变换成10,在0.5秒时,值应该为5,在0.8秒时,值应该为8,这就是匀速变化。我们可以参考系统计算出的当前动画的进度值,计算并返回另一个进度值,这个进度值将会影响到AnimatorUpdateListener中数值的取值,最终达到控制数值变化速度的作用。LinearInterpolator是一个线性的插值器,因此它能使动画在执行过程中始终保持匀速执行,因此它在getInterpolation方法中直接返回了参数input。我们可以使用setInterpolator()方法给动画设置插值器,**在ValueAnimator中,如果没有显式设置插值器,会默认使用AccelerateDecelerateInterpolator作为默认插值器。**AccelerateDecelerateInterpolator会使动画在开始和结束时变化缓慢,在中间部分变化较快。除此之外还有

AccelerateDecelerateInterpolator, AccelerateInterpolator, AnticipateInterpolator, AnticipateOvershootInterpolator, BaseInterpolator, BounceInterpolator, CycleInterpolator, DecelerateInterpolator, LinearInterpolator, OvershootInterpolator, PathInterpolator

这些插值器,具体效果可查阅相关资料。

2.4、Evaluator

Evaluator的作用就是将插值器返回的进度值转换成对应的数值。在详细介绍Evaluator前,我们先来梳理一下AnimatorUpdateListener中是怎么得到当前变化的值?

通过上图相信已经能很清晰的区别出Interpolator与Evaluator的区别了,前者是返回数值区间变化的进度,范围只能是0~1之间,而后者是返回当前进度对应的具体值,这个就跟我们使用哪种静态工厂有关了,如果我们使用ofInt,那么Evaluator计算后的返回值应该是int类型;如果使用ofFloat,那么Evaluator计算后的返回值应该是float类型。所以说Interpolator是可以通用的,而Evaluator是专用的。ValueAnimator中提供了设置Evaluator的方法:setEvaluator(),下面我们以ofFloat为例,分析一下这个Evaluator:

ValueAnimator valueAnimator = ValueAnimator.ofFloat(0, 200);
valueAnimator.setDuration(2000);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
    @Override
    public void onAnimationUpdate(ValueAnimator animation) {
    }
});
valueAnimator.start();

我们知道,如果没有显式指定Interpolator,它将默认使用AccelerateDecelerateInterpolator,但是Evaluator呢?在PropertyValuesHolder这个类中,找到了如下内容:

private static final TypeEvaluator sIntEvaluator = new IntEvaluator();
private static final TypeEvaluator sFloatEvaluator = new FloatEvaluator();

void init() {
    if (mEvaluator == null) {
        mEvaluator = (mValueType == Integer.class) ? sIntEvaluator :
        (mValueType == Float.class) ? sFloatEvaluator :null;
    }
    if (mEvaluator != null) {
        mKeyframes.setEvaluator(mEvaluator);
    }
}

如果是ofFloat,就会使用FloatEvaluator,如果是ofInt,就会使用IntEvaluator。如果之前设置过自定义Evaluator,就会使用自定义的Evaluator,如:

public static ValueAnimator ofArgb(int... values) {
    ValueAnimator anim = new ValueAnimator();
    anim.setIntValues(values);
    anim.setEvaluator(ArgbEvaluator.getInstance());
    return anim;
}

ofArgb()就在静态工厂方法中给ValueAnimator设置了ArgbEvaluator。下面我将详细分析FloatEvaluator:

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);
    }
}

可以发现,它实现了TypeEvaluator接口:

public interface TypeEvaluator<T> {
    public T evaluate(float fraction, T startValue, T endValue);
}

下面对参数作出解释:

  • fraction:这个参数就是插值器返回的值,表示当前动画对应的值进度(0~1)
  • startValue:表示我们所设置的值区间的开始值(例如ofFloat(0, 200),则startValue为0)
  • endValue:表示我们所设置的值区间的结束值(例如ofFloat(0, 200),则startValue为200)
  • 返回值表示计算后的具体值,也就是我们在AnimatorUpdateListener->onAnimationUpdate中通过animation.getAnimatedValue()所得到的值

现在我们回过头来看FloatEvaluator中的evaluate()方法:

public Float evaluate(float fraction, Number startValue, Number endValue) {
    float startFloat = startValue.floatValue();
    return startFloat + fraction * (endValue.floatValue() - startFloat);
}

这就应该很好理解了,为了计算出在某一进度时对应的值,采用了如下公式:

当前值 = 开始值 + 进度值 * (结束值 - 开始值)

2.5、ofObject

学习完了Evaluator以后,我们在来看下ValueAnimator提供的静态工厂方法:

其中大部分我们都已经使用过了,但是还有一个ofObject似乎我们从未提起过,之所以之前没有提起,是因为还没有学习Evaluator,而现在是时候了解一下它了!

public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)

这就是ofObject()方法的原型,可以看到它接收一个Evaluator和一个Object类型的可变参数。这就意味着我们可以处理任意类型的值。可是为什么还要传入Evaluator呢?这是因为Evaluator的作用是根据进度计算出当前进度所对应的值,而现在这个Object是我们自己传入的,系统并不知道如何去转换,因此必须要我们手动提供一个自定义Evaluator。下面我将使用ofObject()方法来实现模拟小球下落(X坐标与Y坐标都会发生变化),首先完成布局:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">

    <View
        android:id="@+id/view_ball"
        android:layout_width="50dp"
        android:layout_height="50dp"
        android:background="@drawable/shape_ball" />

</LinearLayout>
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
    <solid android:color="#35A9F7"/>
</shape>

因为我们准备实现的小球下落是X坐标与Y坐标都会发生变化,因此我们需要借助Point类来保存X坐标与Y坐标的值,在这里,我将自己定义Point类,而不使用android.graphics.Point,自定义Point如下:

public class Point {
    private int x;
    private int y;
    
    public Point() {
    }

    public Point(int x, int y) {
        this.x = x;
        this.y = y;
    }

    public int getX() {
        return x;
    }

    public void setX(int x) {
        this.x = x;
    }

    public int getY() {
        return y;
    }

    public void setY(int y) {
        this.y = y;
    }
}

好了,基本的东西都准备好了,下面创建ValueAnimator:

ValueAnimator.ofObject(???, new Point(0, 0), new Point(300, 500));

一开始就卡住了,原因是还没有提供自定义Evaluator。下面我们定义一个BallEvaluator:

public class BallEvaluator implements TypeEvaluator<Point> {
    private Point point = new Point();

    @Override
    public Point evaluate(float fraction, Point startValue, Point endValue) {
        point.setX((int) (startValue.getX() + fraction * (endValue.getX() - startValue.getX())));
        point.setY((int) (startValue.getY() + fraction * (endValue.getY() - startValue.getY())));
        return point;
    }
}

现在我们可以继续实现代码了:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final View viewBall = findViewById(R.id.view_ball);

        ValueAnimator valueAnimator = ValueAnimator.ofObject(new BallEvaluator(),
                new Point(0, 0), new Point(300, 500));
        valueAnimator.setDuration(2000);
        valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                Point point = (Point) animation.getAnimatedValue();
                viewBall.layout(point.getX(), point.getY(), point.getX() + viewBall.getWidth(),
                        point.getY() + viewBall.getHeight());
            }
        });
        valueAnimator.start();
    }
}

运行效果:

三、ObjectAnimator

3.1、这是什么

前面我们学习了ValueAnimator,难道大家没发现一个问题吗?ValueAnimator只能针对值进行动画改变,如果我们需要关联到View的变化,就需要设置监听事件,根据值得变化手动去操作这个View变化。这相比补间动画要麻烦很多。为了能让动画直接作用于View,Google基于ValueAnimator编写了ObjectAnimator。也就是说ObjectAnimator继承自ValueAnimator,ValueAnimator能用的方法在ObjectAnimator中照样可以用。但是ObjectAnimator重新编写了几个方法,例如:ofInt()、ofFloat()等。

3.2、使用ObjectAnimator

既然说ObjectAnimator能直接作用于View,想必一定很好用吧。下面我将使用ObjectAnimator实现让TextView从不透明->透明->不透明的alpha动画。按照惯例,先编写界面:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".MainActivity">

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="HurryYu"
        android:textSize="18sp" />

    <Button
        android:id="@+id/btn_start"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="开始动画" />

</LinearLayout>

然后编写业务代码:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv,"alpha",1,0,1);
                objectAnimator.setDuration(2000);
                objectAnimator.start();
            }
        });
    }
}

关键部分就是ObjectAnimator了:

ObjectAnimator objectAnimator = ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
objectAnimator.setDuration(2000);
objectAnimator.start();

ObjectAnimator的静态工厂方法比ValueAnimator的多出了两个参数,下面我将介绍多出的两个参数的含义:

public static ObjectAnimator ofFloat(Object target, String propertyName, float... values)
  • target:指定操作的是哪个控件
  • propertyName:指定要操作这个控件的哪个属性

我们去TextView查找是否真的存在alpha这个属性,发现并没有。接着我们去它的父类中查找,也没有!那ObjectAnimator是如何改变透明度的呢?实际上它并不是直接修改第二个参数传入的属性,而是查找它对应的set方法来设置值。例如上面的例子中,我们传入“alpha”,则它会去TextView中找setAlpha()方法。那TextView中有这个方法吗?确实是有的,继承自View。

因此我们要使用ObjectAnimator实现动画,必须保证如下两点:

  1. 在要操作的控件中,必须存在对应属性的set方法,且该方法接收的参数类型要与静态工厂方法所用类型一致,例如ofInt()就要求set方法中接收的参数类型为int类型
  2. set方法的命名必须满足驼峰命名法,例如属性名为rotate,则对应的set方法必须为setRotate()

注意ObjectAnimator会在设定时间内不断调用对应属性的set方法,就像ValueAnimator中加监听后会不断回调onAnimationUpdate()方法一样。不过它也是仅仅调用对应属性的set方法,而set方法中对控件的操作还是要我们自己去实现的,只不过常用的操作系统已经为我们实现好。

3.2.1、什么时候需要提供对应属性的get方法?

如果我们只给第三个参数传入一个值,系统在执行动画以前就会先去调用对应属性的get方法获取初始值,如果没有提供get方法,则会使用默认值。通过ofInt()构造出的ObjectAnimator,属性的默认值为0;ofFloat()构造出来的,属性的默认值为0.0;ofObject()构造出来的,属性的默认值为null,这就有问题了,著名异常NullPointerException。因此,当动画只传入了一个过渡值时,系统会调用属性对应的get方法来获取初始值,如果没有提供get方法,则会使用属性类型对应的默认值,当无法正常获取到属性的初始值时,会直接报异常。

四、AnimatorSet

之前我们使用的ObjectAnimator和ValueAnimator都只能同时播放一种动画,能不能让多个动画同时播放或者按顺序依次播放呢?答案是使用AnimatorSet。我们先来看看最基本的使用方法:

4.1、playSequentially()

AnimatorSet为我们提供了playSequentially()方法,此方法可以接收一个可变长度的Animator或是一个List集合:

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

当然我认为一般情况下使用可变参数的那个更爽,省去了创建集合的麻烦。这个方法的作用是能依次执行传入的动画,一般情况下我们会传入ObjectAnimator而不是ValueAnimator,原因相信大家看过前面的内容后都能懂。需要特别说明的是:**它只能依次执行传入的动画,如果其中一个动画是无限重复的,那么它后面的动画将都不会执行。必须确保执行完成一个动画之后,才回去执行下一个。**它的完整用法如下:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator alphaAnimator = 
                        ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
                ObjectAnimator rotateAnimator = 
                        ObjectAnimator.ofFloat(tv, "rotation", 0, 360, 0);
                ObjectAnimator scaleAnimator = 
                        ObjectAnimator.ofFloat(tv, "scaleX", 1, 2, 1);
                ObjectAnimator translateAnimator = 
                        ObjectAnimator.ofFloat(tv, "translationX", 0, 100, 0);
                
                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.playSequentially(alphaAnimator, rotateAnimator, scaleAnimator, translateAnimator);
                animatorSet.setDuration(2000);
                animatorSet.start();
            }
        });
    }
}

当然也可以单独给里面的每一个动画设置执行时间。但是请注意,如果单独给每个动画设置了执行时间,就不要再去调用AnimatorSet的setDuration(),否则单个动画设置的时间会被覆盖。

4.2、playTogether()

这个看名字就应该能猜出是让动画一起执行的方法,同样它也有一个重载:

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

其实这个方法**只负责同时开始传入的所有动画,至于里面的动画执行时间是多长?是不是一直重复?等等这些问题都与它没有关系。**它的完整用法和上面的playSequentially()是一模一样的。

4.3、AnimatorSet.Builder

Builder是AnimatorSet类中的内部类,它里面提供了一些方法,我们可以使用这些方法来组合出一组动画,它可以控制这组动画中先执行什么,后执行什么,什么与什么一起执行。下面我将列举Builder中每个方法的含义:

方法名说明
with设置当前动画与前一个动画一起执行
before设置当前动画在之前所有动画之后执行,也可理解为之前的动画都在这个动画之前执行
after设置当前动画在之前所有动画之前执行,也可理解为之前的所有动画都在这个动画之后执行

关于before与after的说明,我个人的理解与网上很多文章的理解有一些不同,但实验结果却是我这个说法是对的。如果读者发现我的理解错误,请指出,谢谢!

要得到AnimatorSet.Builder对象,只能使用AnimatorSet的play()方法,下面我将使用AnimatorSet.Builder实现一个组合动画:

public class MainActivity extends AppCompatActivity {

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        final TextView tv = findViewById(R.id.tv);
        findViewById(R.id.btn_start).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ObjectAnimator alphaAnimator =
                        ObjectAnimator.ofFloat(tv, "alpha", 1, 0, 1);
                ObjectAnimator rotateAnimator =
                        ObjectAnimator.ofFloat(tv, "rotation", 0, 360, 0);
                rotateAnimator.setDuration(15000);
                ObjectAnimator scaleAnimator =
                        ObjectAnimator.ofFloat(tv, "scaleX", 1, 2, 1);
                ObjectAnimator translateAnimator =
                        ObjectAnimator.ofFloat(tv, "translationX", 0, 100, 0);

                AnimatorSet animatorSet = new AnimatorSet();
                animatorSet.play(alphaAnimator)
                        .with(rotateAnimator)
                        .after(scaleAnimator)
                        .before(translateAnimator);
                animatorSet.setDuration(2000).start();
            }
        });
    }
}

这个动画的执行过程是:先执行scaleAnimator,当scaleAnimator执行完成后,alphaAnimator与rotateAnimator一起执行,等它们两个执行完成后,执行translateAnimator。

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值