Android Animation完全总结(三)Animator与LayoutTransition

示例代码、思维导图可在此下载
https://github.com/jzj1993/AndroidAnimation

Animator (属性动画 / Property Animation) (Android 3.0+)

Animation 与 Animator 对比

  • 原理:Animation通过改变View的Matrix,不断重绘实现动画,Animator则直接调用Object的setter方法
  • 前者只改变界面显示,即使View的显示位置发生变化,点击事件还是发生在原来的地方
  • 前者只能作用于View,后者可以作用于任意Object
  • 前者只能改变View的显示效果,后者可以平滑改变Object的任意属性
  • 前者的执行效率相对较高,后者需要使用反射,效率较低
  • 后者在Android 3.0+版本中才能使用

相关的类和继承关系

  • 定义了新的TimeInterpolator插值器,为了兼容性,原有的Interpolator继承自TimeInterpolator。

ValueAnimator

ValueAnimator可以生成一个渐变的数值。例如使用ValueAnimator可以实现类似支付宝中账户余额渐变的动画效果。

使用示例

  1. ValueAnimator anim = ValueAnimator.ofInt(1, 100);

  2. anim.setDuration(3000);

  3. anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  4. @Override

  5. public void onAnimationUpdate(ValueAnimator animation) {

  6. Integer val = (Integer) animation.getAnimatedValue();

  7. mTextView.setText(String.valueOf(val));

  8. }

  9. });

  10. anim.addListener(new Animator.AnimatorListener() {

  11. @Override

  12. public void onAnimationStart(Animator animation) {

  13. }

  14. @Override

  15. public void onAnimationEnd(Animator animation) {

  16. mTextView.setText("HelloWorld");

  17. }

  18. @Override

  19. public void onAnimationCancel(Animator animation) {

  20. }

  21. @Override

  22. public void onAnimationRepeat(Animator animation) {

  23. }

  24. });

  25. // API Level 19 (Android 4.4) 才可以使用

  26. anim.addPauseListener(new Animator.AnimatorPauseListener() {

  27. @Override

  28. public void onAnimationPause(Animator animation) {

  29. }

  30. @Override

  31. public void onAnimationResume(Animator animation) {

  32. }

  33. });

  34. anim.start();

实例化

ValueAnimator有四个静态方法可用于实例化:

  1. public static ValueAnimator ofInt(int... values);
  2. public static ValueAnimator ofFloat(float... values);
  3. public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values);
  4. public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values);
  • 其中前两个方法可以根据设定的起始值和终止值生成int、float序列。

  • 第三个方法用于从PropertyValuesHolder获取数据生成动画,后面介绍。

  • ofObject方法配合自定义的TypeEvaluator,可以计算任意类型的数据序列。

Listener

AnimatorListener, AnimatorPauseListener, AnimatorUpdateListener

TypeEvaluator

ValueAnimator默认只能产生int和float型数据动画序列。而使用自定义TypeEvaluator可以创建任意类型的动画序列。

  1. class MyTypeEvaluator implements TypeEvaluator<PointF> {

  2. /**

  3. * @param fraction 当前帧 0 ~ 1f

  4. * @param startValue 起始值

  5. * @param endValue 终止值

  6. * @return 当前帧的value插值计算结果

  7. */

  8. @Override

  9. public PointF evaluate(float fraction, PointF startValue, PointF endValue) {

  10. return new PointF(

  11. startValue.x + fraction * (endValue.x - startValue.x),

  12. startValue.y + fraction * (endValue.y - startValue.y)

  13. );

  14. }

  15. }

  16. PointF start = new PointF(0, 0);

  17. PointF end = new PointF(mTextView.getX(), mTextView.getY());

  18. ValueAnimator anim = ValueAnimator.ofObject(new MyTypeEvaluator(), start, end);

  19. anim.setDuration(2000);

  20. anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

  21. @Override

  22. public void onAnimationUpdate(ValueAnimator animation) {

  23. PointF val = (PointF) animation.getAnimatedValue();

  24. mTextView.setX(val.x);

  25. mTextView.setY(val.y);

  26. }

  27. });

  28. anim.start();

ObjectAnimator、AnimatorSet

ValueAnimator只能生成Value值的动画序列,而ObjectAnimator则可以指定Target(目标)和Property(属性),利用反射中的Setter,将Value序列设置给Target的Property。

AnimatorSet可以组合多个Animator,使其一起播放或按照指定的先后顺序播放。

用法示例

  1. Animator[] anim = new Animator[3];

  2. anim[0] = ObjectAnimator.ofFloat(mTextView, "alpha", 0, 1);

  3. anim[1] = ObjectAnimator.ofFloat(mTextView, "scaleX", 2f, 1f);

  4. anim[2] = ObjectAnimator.ofFloat(mTextView, "translationY", -300, 0);

  5. for (Animator a : anim) {

  6. a.setDuration(1000);

  7. }

  8. AnimatorSet set = new AnimatorSet();

  9. set.playTogether(anim);

  10. set.start();

方法ofFloat中的参数:

  • 第一个为Target,即要对哪个对象执行动画;
  • 第二个为属性,这里用的是字符串,执行动画时会用反射,自动调用字符串对应属性的setter;
  • 后面的参数,用于设置属性的值。

AnimatorSet

AnimatorSet.playTogether 方法,指定同时播放每个Animator动画。因为AnimatorSet继承自Animator,所以该方法的参数也可以为其他AnimatorSet

AnimatorSet.playSequentially方法,指定依次执行每个Animator动画。

  1. Animator[] anim = new Animator[4];

  2. anim[0] = ObjectAnimator.ofFloat(mTextView, "translationX", 0, 100);

  3. anim[1] = ObjectAnimator.ofFloat(mTextView, "translationY", 0, 100);

  4. anim[2] = ObjectAnimator.ofFloat(mTextView, "translationX", 100, 0);

  5. anim[3] = ObjectAnimator.ofFloat(mTextView, "translationY", 100, 0);

  6. for (Animator a : anim) {

  7. a.setDuration(600);

  8. }

  9. AnimatorSet set = new AnimatorSet();

  10. set.playSequentially(anim);

  11. set.start();

AnimatorSet.Builder

  1. Animator[] anim = new Animator[4];

  2. anim[0] = ObjectAnimator.ofFloat(mTextView, "translationX", 0, 100);

  3. anim[1] = ObjectAnimator.ofFloat(mTextView, "translationY", 0, 100);

  4. anim[2] = ObjectAnimator.ofFloat(mTextView, "translationX", 100, 0);

  5. anim[3] = ObjectAnimator.ofFloat(mTextView, "translationY", 100, 0);

  6. for (Animator a : anim) {

  7. a.setDuration(600);

  8. }

  9. AnimatorSet set1 = new AnimatorSet();

  10. AnimatorSet set2 = new AnimatorSet();

  11. set1.play(anim[0]).before(anim[1]);

  12. set1.play(anim[2]).after(anim[1]);

  13. set2.play(anim[3]).after(set1);

  14. set2.start();

  15. AnimatorSet.play方法,返回一个AnimatorSet.Builder实例,可以调用Builder的before、after、with,指定与其他动画之间的播放次序。

  16. AnimatorSet的播放可以嵌套。

注意:

  • AnimatorSet.play方法返回的是一个新的Builder实例

  • 而Builder的before、after、with方法返回的是这个Builder实例自身,要慎用Builder的连写方式。

  • 下面的代码表示anim[0]播放完后,同时播放anim[1]和anim[2],而不是依次播放三个动画。详见AnimatorSet.Builder的JavaDoc说明。

    set.play(anim[0]).before(anim[1]).before(anim[2]);

PropertyValuesHolder

前面利用AnimatorSet和多个Animator,可以实现让一个Target的多个属性同时动画的效果。而使用PropertyValuesHolder,只要用一个Animator即可实现同样的效果,且性能更好。

  • 每个PropertyValuesHolder包含一个Property和对应的Value
  • 一个ObjectAnimator可以包含多个PropertyValuesHolder
  1. PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 0, mTextView.getX());

  2. PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 0, mTextView.getY());

  3. PropertyValuesHolder pvhA = PropertyValuesHolder.ofFloat("alpha", 0, 1);

  4. ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTextView, pvhX, pvhY, pvhA);

  5. anim.setDuration(1000);

  6. anim.start();

KeyFrame

KeyFrame可以实现分段动画

  1. final float y = mTextView.getY();

  2. // fraction, value

  3. Keyframe kf[] = new Keyframe[]{

  4. Keyframe.ofFloat(0f, 0),

  5. Keyframe.ofFloat(0.2f, 0.4f * y),

  6. Keyframe.ofFloat(0.5f, 0.3f * y),

  7. Keyframe.ofFloat(0.8f, 0.8f * y),

  8. Keyframe.ofFloat(1f, y)

  9. };

  10. PropertyValuesHolder pvhK = PropertyValuesHolder.ofKeyframe("y", kf);

  11. ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTextView, pvhK);

  12. anim.setDuration(5000);

  13. anim.start();

  • 不使用KeyFrame时,随着fraction的均匀递增,value的值线性增长。配合LinearInterpolator可以实现View匀速运动。
  • 使用KeyFrame后,每两个关键帧之间成为一段动画,期间Value值线性变化。右图中每个点表示一个KeyFrame。
  • 使用KeyFrame时,也可以使用Interpolator。
  • **注意:**使用KeyFrame时,随着时间推移,Value的值不仅可以递增,也可以递减。
  • **注意:**即使使用的是LinearInterpolator,最后Value的值改变并不一定是均匀的,因为每两个KeyFrame之间连线的斜率不一样。

CustomTarget

ObjectAnimator执行时,是用反射设置Target属性。因此也可以自行定义任意Target。

  1. Object target = new CustomTarget(mTextView);

  2. ObjectAnimator anim = ObjectAnimator.ofFloat(target, "translation", 0, 1);

  3. anim.setDuration(2000);

  4. anim.start();

  5. class CustomTarget {

  6. private View mView;

  7. private final float x;

  8. private final float y;

  9. public CustomTarget(View view) {

  10. mView = view;

  11. x = view.getX();

  12. y = view.getY();

  13. }

  14. public void setTranslation(float translation) {

  15. mView.setX(translation * x);

  16. mView.setY(translation * y);

  17. }

  18. }

CustomProperty

可以通过自定义Property的形式,实现Target原本没有的属性的动画效果。

代码示例:TextView原先有一个float型的alpha属性,取值为0f ~ 1f,现在给TextView定义一个IntegerAlpha的属性,其取值为0 ~ 256的整型值。

  1. PropertyValuesHolder pvh = PropertyValuesHolder.ofInt(new CustomProperty(Integer.class, "integerAlpha"), 0, 256);

  2. ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTextView, pvh);

  3. anim.setDuration(2000);

  4. anim.start();

  5. class CustomProperty extends Property<TextView, Integer> {

  6. public CustomProperty(Class<Integer> type, String name) {

  7. super(type, name);

  8. }

  9. @Override

  10. public void set(TextView tv, Integer alpha) {

  11. tv.setAlpha((float) alpha / 256);

  12. }

  13. @Override

  14. public Integer get(TextView tv) {

  15. return (int) (tv.getAlpha() * 256);

  16. }

  17. }

ViewPropertyAnimator

ViewPropertyAnimator提供了一种快速便捷的方式,可以直接生成View的动画,多个属性动画同时执行。

  1. float x = mTextView.getX();

  2. float y = mTextView.getY();

  3. mTextView.setX(0);

  4. mTextView.setY(0);

  5. mTextView.setAlpha(0);

  6. ViewPropertyAnimator anim = mTextView.animate();

  7. anim.x(x);

  8. anim.y(y);

  9. anim.alpha(1);

  10. anim.setDuration(3000);

  11. anim.setInterpolator(new BounceInterpolator());

  12. anim.start();

LayoutTransition

LayoutTransition可以设置ViewGroup中布局发生变化时,ChildView的动画

  1. Object o = null;

  2. Animator animIn = ObjectAnimator.ofPropertyValuesHolder(

  3. o,

  4. PropertyValuesHolder.ofFloat("translationX", 200, 0),

  5. PropertyValuesHolder.ofFloat("alpha", 0, 1)

  6. );

  7. Animator animOut = ObjectAnimator.ofPropertyValuesHolder(

  8. o,

  9. PropertyValuesHolder.ofFloat("translationX", 0, 200),

  10. PropertyValuesHolder.ofFloat("alpha", 1, 0)

  11. );

  12. animIn.setDuration(500);

  13. animOut.setDuration(500);

  14. LayoutTransition lt = new LayoutTransition();

  15. mContainer.setLayoutTransition(lt);

  16. lt.setAnimator(LayoutTransition.APPEARING, animIn);

  17. lt.setAnimator(LayoutTransition.DISAPPEARING, animOut);

  18. Button bn = new Button(this);

  19. bn.setText("-");

  20. bn.setOnClickListener(new View.OnClickListener() {

  21. @Override

  22. public void onClick(View v) {

  23. mContainer.removeView(v);

  24. }

  25. });

  26. mContainer.addView(bn, index);

  27. transitionType

    • APPEARING: View出现

    • DISAPPEARING: View消失

    • CHANGE_APPEARING: 由于其他View出现而需要改变位置

    • CHANGE_DISAPPEARING: 由于其他View消失而需要改变位置

    • CHANGING: 由于Layout改变而需要改变位置

  28. 可以给每个TranslationType设置一个Animator,发生变化时,对应的ChildView就会执行相应的动画。

  29. 可以在XML中开启ViewGroup的LayoutTransition属性,布局发生变化时,会有一套默认的动画被执行。

  30. 注意一个比较特别的问题:

    这里执行动画的Target,是布局发生改变的ChildView,因此不需要在Animator中指定Target。但是实际试验发现:

    • 需要用ObjectAnimator实例,并且Target设置为null或者任意对象,动画才能正常执行;
    • 如果用没有Target的ValueAnimator,动画不能正常播放。

一个小坑

ValueAnimator中有一个常用来获取实例的静态方法public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values);

ObjectAnimator继承自ValueAnimator,其中又有一个同名不同参数的静态方法public static ObjectAnimator ofPropertyValuesHolder(Object target, PropertyValuesHolder... values);

调用ObjectAnimator中的ofPropertyValuesHolder且Target传入null时,会默认匹配到ValueAnimator中的同名静态方法。下面的代码本想创建一个Target为null的ObjectAnimator,但实际上创建的是ValueAnimator实例,导致动画不能执行。

  1. Animator animIn = ObjectAnimator.ofPropertyValuesHolder(
  2. null,
  3. PropertyValuesHolder.ofFloat("translationX", 200, 0),
  4. PropertyValuesHolder.ofFloat("alpha", 0, 1)
  5. );

为了让代码正确执行,可以这么写

  1. Object o = null;

  2. Animator animIn = ObjectAnimator.ofPropertyValuesHolder(

  3. o,

  4. PropertyValuesHolder.ofFloat("translationX", 200, 0),

  5. PropertyValuesHolder.ofFloat("alpha", 0, 1)

  6. );