文章目录

  • 一、属性动画基础内容
  • 二、ValueAnimator
    • 2.1、使用ValueAnimator
    • 2.2、ValueAnimator的常用方法
      • 2.1.1、其它静态工厂方法
      • 2.1.2、常用对象方法
      • 2.1.3、常用监听方法
    • 2.3、Interpolator插值器
    • 2.4、Evaluator
    • 2.5、ofObject
  • 三、ObjectAnimator
    • 3.1、这是什么
    • 3.2、使用ObjectAnimator
      • 3.2.1、什么时候需要提供对应属性的get方法?
  • 四、AnimatorSet
    • 4.1、playSequentially()
    • 4.2、playTogether()
    • 4.3、AnimatorSet.Builder

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

一、属性动画基础内容

二、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() {@Overridepublic 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"><Viewandroid:id="@+id/view"android:layout_width="50dp"android:layout_height="50dp"android:background="@color/colorPrimary" /><TextViewandroid:layout_width="wrap_content"android:layout_height="wrap_content"android:text="HurryYu" />
</LinearLayout>

效果如下图:

下面完成代码部分:

public class MainActivity extends AppCompatActivity {private View view;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);view = findViewById(R.id.view);view.setOnClickListener(new View.OnClickListener() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic 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() {@Overridepublic void onAnimationStart(Animator animation) {}@Overridepublic void onAnimationEnd(Animator animation) {}@Overridepublic void onAnimationCancel(Animator animation) {}@Overridepublic void onAnimationRepeat(Animator animation) {}
});
  • onAnimationStart:显然是在开始执行动画的时候调用。

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

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

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

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

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

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

2.3、Interpolator插值器

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

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

public class LinearInterpolator implements Interpolator {public LinearInterpolator() {}@overridepublic 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() {@Overridepublic 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"><Viewandroid: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();@Overridepublic 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 {@Overrideprotected 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() {@Overridepublic 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"><TextViewandroid:id="@+id/tv"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="HurryYu"android:textSize="18sp" /><Buttonandroid:id="@+id/btn_start"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="开始动画" /></LinearLayout>

然后编写业务代码:

public class MainActivity extends AppCompatActivity {@Overrideprotected 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() {@Overridepublic 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 {@Overrideprotected 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() {@Overridepublic 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 {@Overrideprotected 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() {@Overridepublic 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. vuex的计算属性_Vuex详细介绍

    1. 什么是Vuex Vuex 是一个专为 Vue.js 应用程序开发的状态管理模式.这是官网的说法,其实很简单:就是一个加强版的data! 在单页应用中会有一个data函数,里面就存放了当前页面的一 ...

  2. Cadence OrCAD Capture管脚Passive和Power属性功能详细介绍图文教程

      ⏪<上一篇>   

  3. Android --- AndroidManifest.xml文件内容详细介绍

    文章目录 1.android:label="@string/app_name" 2. android:icon="@mipmap/ic_launcher"与an ...

  4. android开发笔记之属性动画

    属性动画简单介绍 作用对象:任意 Java 对象 不再局限于 视图View对象 实现的动画效果:可自定义各种动画效果 不再局限于4种基本变换:平移.旋转.缩放 & 透明度 特点 作用对象进行了 ...

  5. Raft算法详细介绍

    raft是一个共识算法(consensus algorithm),所谓共识,就是多个节点对某个事情达成一致的看法,即使是在部分节点故障.网络延时.网络分割的情况下.这些年最为火热的加密货币(比特币.区 ...

  6. Android属性动画实战教程开篇

    本系列博客会分俩篇 本篇博客主要是会介绍属性动画代码使用和xml中使用 关于View动画和属性动画的区别不做过多的介绍,当然涉及到的地方会简单的提一下. 好了废话不多说,直接上内容 首先介绍代码中使用 ...

  7. Android动画完全解析--属性动画

    一.概述 上篇博客介绍了View动画的简单使用和基本工作原理原理,这篇来学习下属性动画.和View动画不同的是,属性动画不再简单的使用平移.旋转.缩放.透明度这4种变换,代替它们的是ValueAnim ...

  8. Android动画(帧动画、补间动画、属性动画)讲解

    Android动画(帧动画.补间动画.属性动画)讲解 首先我们来看看啥是帧动画.补间动画.属性动画. 介绍: 帧动画:是一种常见的动画形式(Frame By Frame),其原理是在"连续的 ...

  9. 三谈属性动画——Keyframe以及ViewPropertyAnimator

    Android动画和Transition系列文章 初识属性动画--使用Animator创建动画 再谈属性动画--介绍以及自定义Interpolator插值器 三谈属性动画--Keyframe以及Vie ...

最新文章

  1. matlab训练神经网络模型并导入simulink详细步骤
  2. LOJ2195 旅行
  3. 1到n阶乘算法的改进
  4. 链表(Linked List)之双向链表
  5. 黑科技!当会爬虫的Python遇上会画图的FineBI……
  6. 初识Python(二)
  7. linux下mongodb的安装及启动
  8. 【第三方软件】利用WIN8系统自带的绘图软件获取图像信息(位置和颜色信息)
  9. 解除webservice上下传文件大小限制
  10. 行业认证标准:ISO 26262-汽车软件功能安全标准
  11. 用IDEA构建Vue项目(主要指令)
  12. Windows平台通过CMD查询域名的Whois信息
  13. 旧手机改电脑外挂,文本补充
  14. X书app数美-sid分析
  15. linux系统文件夹
  16. 解决“试图加载格式不正确的程序”问题
  17. 从大数据挖掘大智慧,华为创造AI时代速度新高度
  18. SEOER必备的经典外链知识
  19. chroot的作用及详解
  20. Go (Golang) 工具之自动化版本工具 gsemver | semver 语义化版本规范

热门文章

  1. java repeatable_Java @Repeatable
  2. python scrapy请求手机版QQ空间数据
  3. 打算以“航空航天青少年科普”为主题举办夏令营,写一份策划案。
  4. windows安装annaconda
  5. 诗歌《忆水湖》(本人原创)
  6. Windows漏洞十年未修复,3CX供应链攻击影响全球60多万家企业
  7. IDEA项目覆盖黄色
  8. 鸿翼受邀参展 精彩亮相迪拜世博会
  9. 用matlab怎么求线性规划,用MATLAB求解线性规划
  10. 基于Python下的OpenCv人脸检测