示例代码、思维导图可在此下载
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可以实现类似支付宝中账户余额渐变的动画效果。
使用示例
-
ValueAnimator anim = ValueAnimator.ofInt(1, 100);
-
anim.setDuration(3000);
-
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
@Override
-
public void onAnimationUpdate(ValueAnimator animation) {
-
Integer val = (Integer) animation.getAnimatedValue();
-
mTextView.setText(String.valueOf(val));
-
}
-
});
-
anim.addListener(new Animator.AnimatorListener() {
-
@Override
-
public void onAnimationStart(Animator animation) {
-
}
-
@Override
-
public void onAnimationEnd(Animator animation) {
-
mTextView.setText("HelloWorld");
-
}
-
@Override
-
public void onAnimationCancel(Animator animation) {
-
}
-
@Override
-
public void onAnimationRepeat(Animator animation) {
-
}
-
});
-
// API Level 19 (Android 4.4) 才可以使用
-
anim.addPauseListener(new Animator.AnimatorPauseListener() {
-
@Override
-
public void onAnimationPause(Animator animation) {
-
}
-
@Override
-
public void onAnimationResume(Animator animation) {
-
}
-
});
-
anim.start();
实例化
ValueAnimator有四个静态方法可用于实例化:
public static ValueAnimator ofInt(int... values);
public static ValueAnimator ofFloat(float... values);
public static ValueAnimator ofPropertyValuesHolder(PropertyValuesHolder... values);
public static ValueAnimator ofObject(TypeEvaluator evaluator, Object... values);
-
其中前两个方法可以根据设定的起始值和终止值生成int、float序列。
-
第三个方法用于从
PropertyValuesHolder
获取数据生成动画,后面介绍。 -
ofObject
方法配合自定义的TypeEvaluator
,可以计算任意类型的数据序列。
Listener
AnimatorListener, AnimatorPauseListener, AnimatorUpdateListener
TypeEvaluator
ValueAnimator默认只能产生int和float型数据动画序列。而使用自定义TypeEvaluator可以创建任意类型的动画序列。
-
class MyTypeEvaluator implements TypeEvaluator<PointF> {
-
/**
-
* @param fraction 当前帧 0 ~ 1f
-
* @param startValue 起始值
-
* @param endValue 终止值
-
* @return 当前帧的value插值计算结果
-
*/
-
@Override
-
public PointF evaluate(float fraction, PointF startValue, PointF endValue) {
-
return new PointF(
-
startValue.x + fraction * (endValue.x - startValue.x),
-
startValue.y + fraction * (endValue.y - startValue.y)
-
);
-
}
-
}
-
PointF start = new PointF(0, 0);
-
PointF end = new PointF(mTextView.getX(), mTextView.getY());
-
ValueAnimator anim = ValueAnimator.ofObject(new MyTypeEvaluator(), start, end);
-
anim.setDuration(2000);
-
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
-
@Override
-
public void onAnimationUpdate(ValueAnimator animation) {
-
PointF val = (PointF) animation.getAnimatedValue();
-
mTextView.setX(val.x);
-
mTextView.setY(val.y);
-
}
-
});
-
anim.start();
ObjectAnimator、AnimatorSet
ValueAnimator只能生成Value值的动画序列,而ObjectAnimator则可以指定Target(目标)和Property(属性),利用反射中的Setter,将Value序列设置给Target的Property。
AnimatorSet可以组合多个Animator,使其一起播放或按照指定的先后顺序播放。
用法示例
-
Animator[] anim = new Animator[3];
-
anim[0] = ObjectAnimator.ofFloat(mTextView, "alpha", 0, 1);
-
anim[1] = ObjectAnimator.ofFloat(mTextView, "scaleX", 2f, 1f);
-
anim[2] = ObjectAnimator.ofFloat(mTextView, "translationY", -300, 0);
-
for (Animator a : anim) {
-
a.setDuration(1000);
-
}
-
AnimatorSet set = new AnimatorSet();
-
set.playTogether(anim);
-
set.start();
方法ofFloat中的参数:
- 第一个为Target,即要对哪个对象执行动画;
- 第二个为属性,这里用的是字符串,执行动画时会用反射,自动调用字符串对应属性的setter;
- 后面的参数,用于设置属性的值。
AnimatorSet
AnimatorSet.playTogether
方法,指定同时播放每个Animator动画。因为AnimatorSet继承自Animator,所以该方法的参数也可以为其他AnimatorSet
。
而AnimatorSet.playSequentially
方法,指定依次执行每个Animator动画。
-
Animator[] anim = new Animator[4];
-
anim[0] = ObjectAnimator.ofFloat(mTextView, "translationX", 0, 100);
-
anim[1] = ObjectAnimator.ofFloat(mTextView, "translationY", 0, 100);
-
anim[2] = ObjectAnimator.ofFloat(mTextView, "translationX", 100, 0);
-
anim[3] = ObjectAnimator.ofFloat(mTextView, "translationY", 100, 0);
-
for (Animator a : anim) {
-
a.setDuration(600);
-
}
-
AnimatorSet set = new AnimatorSet();
-
set.playSequentially(anim);
-
set.start();
AnimatorSet.Builder
-
Animator[] anim = new Animator[4];
-
anim[0] = ObjectAnimator.ofFloat(mTextView, "translationX", 0, 100);
-
anim[1] = ObjectAnimator.ofFloat(mTextView, "translationY", 0, 100);
-
anim[2] = ObjectAnimator.ofFloat(mTextView, "translationX", 100, 0);
-
anim[3] = ObjectAnimator.ofFloat(mTextView, "translationY", 100, 0);
-
for (Animator a : anim) {
-
a.setDuration(600);
-
}
-
AnimatorSet set1 = new AnimatorSet();
-
AnimatorSet set2 = new AnimatorSet();
-
set1.play(anim[0]).before(anim[1]);
-
set1.play(anim[2]).after(anim[1]);
-
set2.play(anim[3]).after(set1);
-
set2.start();
-
AnimatorSet.play方法,返回一个AnimatorSet.Builder实例,可以调用Builder的before、after、with,指定与其他动画之间的播放次序。
-
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
-
PropertyValuesHolder pvhX = PropertyValuesHolder.ofFloat("x", 0, mTextView.getX());
-
PropertyValuesHolder pvhY = PropertyValuesHolder.ofFloat("y", 0, mTextView.getY());
-
PropertyValuesHolder pvhA = PropertyValuesHolder.ofFloat("alpha", 0, 1);
-
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTextView, pvhX, pvhY, pvhA);
-
anim.setDuration(1000);
-
anim.start();
KeyFrame
KeyFrame可以实现分段动画
-
final float y = mTextView.getY();
-
// fraction, value
-
Keyframe kf[] = new Keyframe[]{
-
Keyframe.ofFloat(0f, 0),
-
Keyframe.ofFloat(0.2f, 0.4f * y),
-
Keyframe.ofFloat(0.5f, 0.3f * y),
-
Keyframe.ofFloat(0.8f, 0.8f * y),
-
Keyframe.ofFloat(1f, y)
-
};
-
PropertyValuesHolder pvhK = PropertyValuesHolder.ofKeyframe("y", kf);
-
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTextView, pvhK);
-
anim.setDuration(5000);
-
anim.start();
- 不使用KeyFrame时,随着fraction的均匀递增,value的值线性增长。配合LinearInterpolator可以实现View匀速运动。
- 使用KeyFrame后,每两个关键帧之间成为一段动画,期间Value值线性变化。右图中每个点表示一个KeyFrame。
- 使用KeyFrame时,也可以使用Interpolator。
- **注意:**使用KeyFrame时,随着时间推移,Value的值不仅可以递增,也可以递减。
- **注意:**即使使用的是LinearInterpolator,最后Value的值改变并不一定是均匀的,因为每两个KeyFrame之间连线的斜率不一样。
CustomTarget
ObjectAnimator执行时,是用反射设置Target属性。因此也可以自行定义任意Target。
-
Object target = new CustomTarget(mTextView);
-
ObjectAnimator anim = ObjectAnimator.ofFloat(target, "translation", 0, 1);
-
anim.setDuration(2000);
-
anim.start();
-
class CustomTarget {
-
private View mView;
-
private final float x;
-
private final float y;
-
public CustomTarget(View view) {
-
mView = view;
-
x = view.getX();
-
y = view.getY();
-
}
-
public void setTranslation(float translation) {
-
mView.setX(translation * x);
-
mView.setY(translation * y);
-
}
-
}
CustomProperty
可以通过自定义Property的形式,实现Target原本没有的属性的动画效果。
代码示例:TextView原先有一个float型的alpha属性,取值为0f ~ 1f,现在给TextView定义一个IntegerAlpha的属性,其取值为0 ~ 256的整型值。
-
PropertyValuesHolder pvh = PropertyValuesHolder.ofInt(new CustomProperty(Integer.class, "integerAlpha"), 0, 256);
-
ObjectAnimator anim = ObjectAnimator.ofPropertyValuesHolder(mTextView, pvh);
-
anim.setDuration(2000);
-
anim.start();
-
class CustomProperty extends Property<TextView, Integer> {
-
public CustomProperty(Class<Integer> type, String name) {
-
super(type, name);
-
}
-
@Override
-
public void set(TextView tv, Integer alpha) {
-
tv.setAlpha((float) alpha / 256);
-
}
-
@Override
-
public Integer get(TextView tv) {
-
return (int) (tv.getAlpha() * 256);
-
}
-
}
ViewPropertyAnimator
ViewPropertyAnimator提供了一种快速便捷的方式,可以直接生成View的动画,多个属性动画同时执行。
-
float x = mTextView.getX();
-
float y = mTextView.getY();
-
mTextView.setX(0);
-
mTextView.setY(0);
-
mTextView.setAlpha(0);
-
ViewPropertyAnimator anim = mTextView.animate();
-
anim.x(x);
-
anim.y(y);
-
anim.alpha(1);
-
anim.setDuration(3000);
-
anim.setInterpolator(new BounceInterpolator());
-
anim.start();
LayoutTransition
LayoutTransition可以设置ViewGroup中布局发生变化时,ChildView的动画
-
Object o = null;
-
Animator animIn = ObjectAnimator.ofPropertyValuesHolder(
-
o,
-
PropertyValuesHolder.ofFloat("translationX", 200, 0),
-
PropertyValuesHolder.ofFloat("alpha", 0, 1)
-
);
-
Animator animOut = ObjectAnimator.ofPropertyValuesHolder(
-
o,
-
PropertyValuesHolder.ofFloat("translationX", 0, 200),
-
PropertyValuesHolder.ofFloat("alpha", 1, 0)
-
);
-
animIn.setDuration(500);
-
animOut.setDuration(500);
-
LayoutTransition lt = new LayoutTransition();
-
mContainer.setLayoutTransition(lt);
-
lt.setAnimator(LayoutTransition.APPEARING, animIn);
-
lt.setAnimator(LayoutTransition.DISAPPEARING, animOut);
-
Button bn = new Button(this);
-
bn.setText("-");
-
bn.setOnClickListener(new View.OnClickListener() {
-
@Override
-
public void onClick(View v) {
-
mContainer.removeView(v);
-
}
-
});
-
mContainer.addView(bn, index);
-
transitionType
-
APPEARING: View出现
-
DISAPPEARING: View消失
-
CHANGE_APPEARING: 由于其他View出现而需要改变位置
-
CHANGE_DISAPPEARING: 由于其他View消失而需要改变位置
-
CHANGING: 由于Layout改变而需要改变位置
-
-
可以给每个TranslationType设置一个Animator,发生变化时,对应的ChildView就会执行相应的动画。
-
可以在XML中开启ViewGroup的LayoutTransition属性,布局发生变化时,会有一套默认的动画被执行。
-
注意一个比较特别的问题:
这里执行动画的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实例,导致动画不能执行。
Animator animIn = ObjectAnimator.ofPropertyValuesHolder(
null,
PropertyValuesHolder.ofFloat("translationX", 200, 0),
PropertyValuesHolder.ofFloat("alpha", 0, 1)
);
为了让代码正确执行,可以这么写
-
Object o = null;
-
Animator animIn = ObjectAnimator.ofPropertyValuesHolder(
-
o,
-
PropertyValuesHolder.ofFloat("translationX", 200, 0),
-
PropertyValuesHolder.ofFloat("alpha", 0, 1)
-
);