属性动画我决定用两篇文章做总结
从名字就可以看出,这是针对值的动画,它并不会对View做出任何动画效果。使用ValueAnimator可以让某一个值在设定时间内平滑过渡成另一个值,根据值的变换过程,自己操作View的变换。
要使用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轴方向上向右平移,首先我们完成布局:
<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()。
之前我们已经使用过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。
前面我们已经接触过了几个ValueAnimator提供的方法,比如setDuration用来设置动画时长,getAnimatedValue用来获取当前变换中的值,start用来开始动画。下面我们再来学习几个常用的方法:
①、setRepeatCount用于设置动画的重复次数,传入0表示不重复,传入ValueAnimator.INFINITE表示无限重复,默认是不重复,这个方法比较好理解。
②、setRepeatMode用于设置动画重复的模式,有两种选择,一种是正序重复(ValueAnimator.RESTART),另一种是倒序重复(ValueAnimator.REVERSE),默认是正序重复。这两种有什么区别呢?拿按钮变颜色的例子来说,如果我们把重复次数设定为无限重复,那么每一次重复时,都会从最后的蓝色突然变成开始的红色然后继续新一轮动画,这就是正序:
而如果将RepeatMode设置为ValueAnimator.REVERSE,将使用倒序的方式进行动画的重复,即:
第一轮:红-绿-蓝
第二轮:蓝-绿-红
第三轮:红-绿-蓝
以此类推,这样就不存在突然从第三种颜色变为第一种颜色的情况:
③、cancel用于取消当前动画。
我们已经使用过一个监听(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类不能被直接实例化,这样就能逼迫调用者至少去重写里面的一个方法。
插值器的作用是控制动画在执行过程中的速度,比如有一个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
这些插值器,具体效果可查阅相关资料。
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);
}
下面对参数作出解释:
animation.getAnimatedValue()
所得到的值现在我们回过头来看FloatEvaluator中的evaluate()方法:
public Float evaluate(float fraction, Number startValue, Number endValue) {
float startFloat = startValue.floatValue();
return startFloat + fraction * (endValue.floatValue() - startFloat);
}
这就应该很好理解了,为了计算出在某一进度时对应的值,采用了如下公式:
当前值 = 开始值 + 进度值 * (结束值 - 开始值)
学习完了Evaluator以后,我们在来看下ValueAnimator提供的静态工厂方法:
其中大部分我们都已经使用过了,但是还有一个ofObject似乎我们从未提起过,之所以之前没有提起,是因为还没有学习Evaluator,而现在是时候了解一下它了!
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values)
这就是ofObject()方法的原型,可以看到它接收一个Evaluator和一个Object类型的可变参数。这就意味着我们可以处理任意类型的值。可是为什么还要传入Evaluator呢?这是因为Evaluator的作用是根据进度计算出当前进度所对应的值,而现在这个Object是我们自己传入的,系统并不知道如何去转换,因此必须要我们手动提供一个自定义Evaluator。下面我将使用ofObject()方法来实现模拟小球下落(X坐标与Y坐标都会发生变化),首先完成布局:
<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>
<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();
}
}
运行效果:
前面我们学习了ValueAnimator,难道大家没发现一个问题吗?ValueAnimator只能针对值进行动画改变,如果我们需要关联到View的变化,就需要设置监听事件,根据值得变化手动去操作这个View变化。这相比补间动画要麻烦很多。为了能让动画直接作用于View,Google基于ValueAnimator编写了ObjectAnimator。也就是说ObjectAnimator继承自ValueAnimator,ValueAnimator能用的方法在ObjectAnimator中照样可以用。但是ObjectAnimator重新编写了几个方法,例如:ofInt()、ofFloat()等。
既然说ObjectAnimator能直接作用于View,想必一定很好用吧。下面我将使用ObjectAnimator实现让TextView从不透明->透明->不透明的alpha动画。按照惯例,先编写界面:
<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)
我们去TextView查找是否真的存在alpha这个属性,发现并没有。接着我们去它的父类中查找,也没有!那ObjectAnimator是如何改变透明度的呢?实际上它并不是直接修改第二个参数传入的属性,而是查找它对应的set方法来设置值。例如上面的例子中,我们传入“alpha”,则它会去TextView中找setAlpha()方法。那TextView中有这个方法吗?确实是有的,继承自View。
因此我们要使用ObjectAnimator实现动画,必须保证如下两点:
注意ObjectAnimator会在设定时间内不断调用对应属性的set方法,就像ValueAnimator中加监听后会不断回调onAnimationUpdate()方法一样。不过它也是仅仅调用对应属性的set方法,而set方法中对控件的操作还是要我们自己去实现的,只不过常用的操作系统已经为我们实现好。
如果我们只给第三个参数传入一个值,系统在执行动画以前就会先去调用对应属性的get方法获取初始值,如果没有提供get方法,则会使用默认值。通过ofInt()构造出的ObjectAnimator,属性的默认值为0;ofFloat()构造出来的,属性的默认值为0.0;ofObject()构造出来的,属性的默认值为null,这就有问题了,著名异常NullPointerException。因此,当动画只传入了一个过渡值时,系统会调用属性对应的get方法来获取初始值,如果没有提供get方法,则会使用属性类型对应的默认值,当无法正常获取到属性的初始值时,会直接报异常。
之前我们使用的ObjectAnimator和ValueAnimator都只能同时播放一种动画,能不能让多个动画同时播放或者按顺序依次播放呢?答案是使用AnimatorSet。我们先来看看最基本的使用方法:
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(),否则单个动画设置的时间会被覆盖。
这个看名字就应该能猜出是让动画一起执行的方法,同样它也有一个重载:
public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)
其实这个方法**只负责同时开始传入的所有动画,至于里面的动画执行时间是多长?是不是一直重复?等等这些问题都与它没有关系。**它的完整用法和上面的playSequentially()是一模一样的。
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。