一无所有的人是有福的,因为他们将获得一切! ——罗曼罗兰
前面两篇博客我们讲解的ValueAnimator和ObjectAnimator都是单飞,只能单独实现一个动画,如果想把动画一锅烩了,让他们一块执行,要用到组合动画,就需要AnimatorSet。
playSequentially()与playTogether()函数
sequentially 单词的意思是顺序,像接力赛跑步,前面一个人跑步完成2km,后面一个人接着完成2km,指动画按顺序一个一个播放。together 单词的意思是 一起,一起玩游戏,一起打羽毛球,两个或多个人一起运动,一起玩,指多个动画一块播放。一般我们会用ObjectAnimator操作View动画,我们着重讲解它。
playSequentially()函数声明如下
public void playSequentially(Animator... items)
public void playSequentially(List<Animator> items)
第一个构造函数是我们最常用的,可以传入任意多个Animator对象,这些对象的动画会依次播放。下面说明playSequentially()函数的使用方法。先看下界面
单击按钮时,调用下面的代码
package com.as.propertyanimator;
import android.animation.AnimatorSet;
import android.animation.ObjectAnimator;
import android.os.Bundle;
import android.view.View;
import android.widget.TextView;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
public class SetAnimatorActivity extends AppCompatActivity {
private TextView tv1;
private TextView tv2;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_set_animator);
AppCompatButton btnStart = findViewById(R.id.btnStart);
tv1 = findViewById(R.id.tv1);
tv2 = findViewById(R.id.tv2);
btnStart.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
setAnimator1();
}
});
}
private void setAnimator1() {
ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 300, 0);
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 500, 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(tv1BgAnimator, tv1TranslationY, tv2TranslationY);
animatorSet.setDuration(2000);
animatorSet.start();
}
}
构造了三个动画,针对 tv1 的是tv1BgAnimator 和 tv1TranslationY ,分别用于改变背景和改变控件Y坐标位置,针对tv2 的是 tv2TranslationY,用于改变控件Y坐标位置。然后利用AnimatorSet 的playSequentially()函数将这三个动画组装起来,依次播放。
从效果图中看出,TextView1 做颜色改变动画,之后做位移动画,最后TextView2 做位移动画。
playTogether()函数声明如下
public void playTogether(Animator... items)
public void playTogether(Collection<Animator> items)
第一个构造函数表示传入任意多个Animator对象,第二个参数则需要传入一个组装好的Collection对象,都是将参数中的动画一起播放。
同样是上面的例子,使用playTogether()函数来播放动画,我们来看看效果。
private void setPlayTogether() {
ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 300, 0);
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 500, 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playTogether(tv1BgAnimator, tv1TranslationY, tv2TranslationY);
animatorSet.setDuration(2000);
animatorSet.start();
}
从效果图中可以看出,三个动画是一起播放的。大家可能会好奇,我们没有给每个动画设置时长,每个动画的时长都是2秒中,在 animatorSet.setDuration(2000);设置的时长,会覆盖每个动画设置的时长。只有animatorSet.setStartDelay(1000) 这个函数是设置自己延迟开始时间,不会覆盖每个动画,除了这个函数,其它的设置函数会覆盖每个动画设置。
playSequentially()与playTogether()函数的含义
激活动画后,动画开始后的操作只由动画自己负责,至于动画结不结束,也只有动画自己知道。playSequentially()函数就是激活一个动画后,动画之后的操作由动画自己负责,这个动画结束之后,再开始下一个动画。如果上一个动画没有结束,那么下一个动画永远不会开始。
我们看一个playTogether()函数的例子
ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 300, 0);
tv1TranslationY.setStartDelay(2000);
tv1TranslationY.setDuration(5000);
tv1TranslationY.setRepeatCount(ObjectAnimator.INFINITE);
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 500, 0);
tv2TranslationY.setStartDelay(2000);
animatorSet = new AnimatorSet();
animatorSet.playTogether(tv1BgAnimator, tv1TranslationY, tv2TranslationY);
animatorSet.setDuration(2000);
animatorSet.start();
在这个例子中,我们将tv1TranslationY 延迟2000ms开始,动画时长5000ms,并且无限循环;将tv2TranslationY设为延迟2000ms;而对tv1BgAnimator 没有任何设置,所以默认直接开始。
在效果图中,单击按钮后,先进行tv1背景颜色变化,颜色变化完以后,tv2的延迟延迟刚好结束,此时两个TextView开始做平移操作。最后tv1 的位移变换时无限循环的,而且我们将它的动画时长设置为5000ms,但是我们发现它的动画时长2000ms,是因为animatorSet.setDuration(2000);覆盖了每一个动画的时长。
在这个例子中可以看到playTogether()函数只负责动画一起开始,动画开始后,只有每个动画自己清楚干什么事情。我们点击了Cancel Anim,animatorSet 可以取消掉所有正在执行的动画。
再看一个playSequentially()函数的例子
ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
tv1BgAnimator.setStartDelay(2000);
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 300, 0);
tv1TranslationY.setRepeatCount(ObjectAnimator.INFINITE);
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 400, 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.playSequentially(tv1BgAnimator, tv1TranslationY, tv2TranslationY);
animatorSet.setDuration(2000);
animatorSet.start();
同样是三个动画,tv1BgAnimator先设置了延迟,tv1TranslationY设置了无限循环。使用playSequentially()函数来一次播放这三个动画,tv1BgAnimator在开始动画后延迟2000ms再开始,结束之后进入tv1TranslationY,这个动画会无限循环,也就意味着永远不会结束,第三个动画tv2TranslationY也永远不会开始。
从效果图中可以看出,TextView1 先等了2000ms毫秒在开始颜色变化,然后开始无限循环的上下移动;而tv2 永远不会开始。
虽然playSequentially()与playTogether()函数分别能实现一起开始动画和依次开始动画,但是并不能非常自由的组合动画。比如我们有三个动画,A,B,C,想先播放C,然后同时播放A和B,为了更方便的组合动画,通过AnimatorSet.Builder类。
我们先看一下它们的函数声明
// 表示要播放哪个动画
public Builder play(Animator anim)
// 和前面的动画一起执行
public Builder with(Animator anim)
// 当前动画在anim动画之前
public Builder before(Animator anim)
// 当前动画在 anim 之后
public Builder after(Animator anim)
// 延迟 n 毫秒之后执行
public Builder after(long delay)
play(Animator anim) 表示当前播放哪个动画,而with(Animator anim) ,before(Animator anim),after(Animator anim) 都是以play()中当前所播放的动画为基准。
比如play(Animator playAnim) 与 before(Animator beforeAnim)共用时,表示在播放beforeAnim 之前,先播放playAnim 动画。同样play(Animator playAnim) 与 after(Animator afterAnim)共用时,则表示播放afterAnim动画之后,再播放playAnim动画。
private void setPlayBuilder() {
ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 300, 0);
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 400, 0);
AnimatorSet animatorSet = new AnimatorSet();
animatorSet.play(tv1TranslationY).with(tv2TranslationY).after(tv1BgAnimator);
animatorSet.setDuration(2000);
animatorSet.start();
}
AnimatorSet.Builder play = animatorSet.play(tv1TranslationY);
play.with(tv2TranslationY);
play.after(tv1BgAnimator);
这两种写法的运行结果一样,这里实现的效果时tv1颜色变化完成之后,两个控件一起开始平移动画。
AnimatorSet 监听器
单机Start Anim 按钮时,两个TextView 一起平移,设置tv2 动画无限循环,给组合动画添加回调监听Animator.AnimatorListener() 函数中每一部分添加打印日志
private void setListenerAnimator() {
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 500, 0);
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 500, 0);
tv2TranslationY.setRepeatCount(ValueAnimator.INFINITE);
mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(tv1TranslationY).with(tv2TranslationY);
mAnimatorSet.setDuration(2000);
mAnimatorSet.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
Log.d("AnimatorSet", "-----onAnimationStart----");
}
@Override
public void onAnimationEnd(Animator animation) {
Log.d("AnimatorSet", "-----onAnimationEnd----");
}
@Override
public void onAnimationCancel(Animator animation) {
Log.d("AnimatorSet", "-----onAnimationCancel----");
}
@Override
public void onAnimationRepeat(Animator animation) {
Log.d("AnimatorSet", "-----onAnimationRepeat----");
}
});
mAnimatorSet.start();
}
我们看打印结果
06-12 11:27:06.194 17105-17105/com.as.propertyanimator D/AnimatorSet: -----onAnimationStart----
06-12 11:27:09.891 17105-17105/com.as.propertyanimator D/AnimatorSet: -----onAnimationCancel----
06-12 11:27:09.891 17105-17105/com.as.propertyanimator D/AnimatorSet: -----onAnimationEnd----
- 从效果图中看出,虽然TextView2 再无限循环,但日志中没有打印出对应的重复日志;从日志中也可以看出,AnimatorSet 的监听函数只是用来监听 AnimatorSet 的状态,与其中的动画无关。
- AnimatorSet没有设置循环的函数,所以动画执行一次就结束了,永远无法执行到onAnimationRepeat()函数中。
常用函数
// 设置单次动画时长
public AnimatorSet setDuration(long duration)
// 设置插值器
public void setInterpolator(TimeInterpolator interpolator)
// 设置ObjectAnimator 动画目标控件
public void setTarget(Object target)
这个函数再ObjectAnimator也有,那么与AnimatorSet设置的区别是啥?
区别就是AnimatorSet 设置以后,会覆盖掉ObjectAnimator中的设置。也就是说,AnimatorSet设置后,以AnimatorSet为准。如果AnimatorSet没有设置,以ObjectAnimator为准。前门我们讲过动画时长的覆盖就是这样。
来看下面的例子
private void setAnimator2() {
ObjectAnimator tv1TranslationY = ObjectAnimator.ofFloat(tv1, "translationY", 0, 300, 0);
tv1TranslationY.setDuration(1000 * 100);
tv1TranslationY.setInterpolator(new BounceInterpolator());
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 300, 0);
tv2TranslationY.setInterpolator(new AccelerateDecelerateInterpolator());
mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(tv1TranslationY).with(tv2TranslationY);
mAnimatorSet.setInterpolator(new LinearInterpolator());
mAnimatorSet.setDuration(2000);
mAnimatorSet.start();
}
在这个例子中,我们通过 mAnimatorSet.setDuration(2000); 设置为所有动画的单词执行时间2000ms,虽然给tv1 设置了 1000 * 10 ms,但是由于AnimatorSet设置了setDuration(2000),单个动画的时长设置无效。我们还分为给tv1 和 tv2 设置了插值器,给AnimatorSet也设置了插值器,将覆盖tv1 和 tv2 的插值器,如果AnimatorSet没有设置插值器,tv1 和 tv2 按各自的插值器做动画。
从效果图中可以看出,两个控件同时开始和结束,并且都是匀速运动。最后我们来看看Target 这个函数。
ObjectAnimator tv1BgAnimator = ObjectAnimator.ofInt(tv1, "backgroundColor", 0xffff00ff, 0xffffff00, 0xffff00ff);
tv1BgAnimator.setDuration(1000 * 100);
tv1BgAnimator.setInterpolator(new BounceInterpolator());
ObjectAnimator tv2TranslationY = ObjectAnimator.ofFloat(tv2, "translationY", 0, 300, 0);
tv2TranslationY.setInterpolator(new AccelerateDecelerateInterpolator());
mAnimatorSet = new AnimatorSet();
mAnimatorSet.play(tv1BgAnimator).with(tv2TranslationY);
mAnimatorSet.setInterpolator(new LinearInterpolator());
mAnimatorSet.setDuration(2000);
mAnimatorSet.setTarget(tv2);
mAnimatorSet.start();
在这段代码中,我们给tv1设置了背景色,给tv2设置了上下移动。我们 mAnimatorSet.setTarget(tv2);将各个动画的目标空间设置为tv2,所以tv1不会有任何动画。所有的动画都发生在tv2上。
- AnimatorSet.setTarget() 函数的作用就是将动画的目标控件同意设置为当前控件。
Animator 动画的XML实现
着重看下如何利用XML来实现ValueAnimator ,ObjectAnimator,AnimatorSet。
- <animator> : 对应ValueAnimator
- <objectanimator> : 对应ObjectAnimator
- <set> : 对应AnimatorSet
animator 标签
<?xml version="1.0" encoding="utf-8"?>
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode="reverse | restart"
android:valueType="colorType intType floatType"
android:interpolator="@android:interpolator/xxx"
>
</animator>
- android:duration : 每次动画播放时长
- android:valueFrom : 初始动画值,取值范围为 float ,int,和color这三种类型的值。如果取值为float,则对应的值样式为20.0,如果取值为int ,则对应的样式为20,如果取值为color,则对应的样式为#666666。
- android:valueTo : 结束动画值,取值范围为 float ,int,和color这三种类型的值。
- android:startOffset :动画激活延时,对应代码中的startDelay(long dealy)函数
- android:repeatCount : 动画重复次数
- android:repeatMode :动画重复模式,reverse倒叙重播,restart正序重播。
- android:valueType :表示参数值类型,取值为 intType,floatType和colorType ,与android:valueFrom和android:valueTo相对应,如果取值为int类型,那么android:valueFrom和android:valueTo对应int类型,如果取值为color类型,那么android:valueFrom和android:valueTo对应color类型。
- android:interpolator="@android:interpolator/xxx" 设置插值器
写个例子
首先,在 res/animator 文件夹下生成一个动画的xml文件
<animator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="1000"
android:interpolator="@android:anim/bounce_interpolator"
android:valueFrom="0"
android:valueTo="300"
android:valueType="intType" />
在这里,我们将valueType 设置为intType,所以对应android:valueFrom和android:valueTo都必须是int类型:插值器使用回弹插值器。
private void loadXmlAnimator() {
ValueAnimator animator = (ValueAnimator) AnimatorInflater.loadAnimator(this, R.animator.valueanimator);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int curValue = (int) animation.getAnimatedValue();
tv1.layout(curValue, curValue, tv1.getWidth() + curValue, tv1.getHeight() + curValue);
}
});
animator.start();
}
由于xml文件中根标签所对应的是ValueAnimator,所以在加载后,将其转换为ValueAnimator,然后对其添加控件监听。在监听是,动态改变TextView 的位置。
objectAnimator标签
下面是完整的objectAnimator标签所有字段
<?xml version="1.0" encoding="utf-8"?>
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:propertyName="string"
android:duration="int"
android:valueFrom="float | int | color"
android:valueTo="float | int | color"
android:startOffset="int"
android:repeatCount="int"
android:repeatMode="reverse | restart"
android:valueType="colorType intType floatType"
android:interpolator="@android:interpolator/xxx"
>
</objectAnimator>
其中android:propertyName字段对应属性名,既ObjectAnimator所要操作控件的属性名。其它字段与含义与animator 标签的相应字段是一样的。
写个例子
在res/animator 文件夹下生成一个动画xml文件
<objectAnimator xmlns:android="http://schemas.android.com/apk/res/android"
android:duration="2000"
android:interpolator="@android:anim/accelerate_interpolator"
android:propertyName="translationY"
android:repeatCount="1"
android:repeatMode="reverse"
android:startOffset="3000"
android:valueFrom="0"
android:valueTo="500"
android:valueType="floatType" />
我们更改的属性为translationY,既改变纵坐标,时长为2000ms,translationY的值从0到500,使用的是加速插值器,对用的值类型Float,因为setTranslationY()函数的参数是Float类型。该函数的声明如下
public void setTranslationY(float translationY)
同时,设置重复次数为1,重复模式倒叙重新开始,将动画延迟2000ms后激活。然后加载动画
ObjectAnimator animator = (ObjectAnimator) AnimatorInflater.loadAnimator(this, R.animator.objectanimator);
animator.setTarget(tv1);
animator.start();
从效果图中可以看出,单击按钮后,延迟了2000ms开始动画,然后倒叙重复运行 1 次。
网友评论