帧动画、补间动画、属性动画
补间动画的缺点有3点:
1、只有平移、旋转、缩放、透明度及这四种的组合;
2、只能作用于View;
3、View的实际位置、大小并没有变化。如View被移动后,接受点击还在原位置。
属性动画是后来新增的一种动画,就是针对这些缺点的解决方案。
属性动画的使用范例:
tv1Animator = ObjectAnimator.ofFloat(tv2, "ratation", 0f, 180f);
tv1Animator.start();
属性动画 多种动画组合使用的范例:
ObjectAnimator moveIn = ObjectAnimator.ofFloat(tv3, "translationX", 0f, 200f);
ObjectAnimator scale = ObjectAnimator.ofFloat(tv3, "scaleX", 1f, 2f);
ObjectAnimator rotate = ObjectAnimator.ofFloat(tv3, "rotation", 0f, 180f);
ObjectAnimator fadeInOut = ObjectAnimator.ofFloat(tv3, "alpha", 1f, 0f, 1f);
AnimatorSet animSet = new AnimatorSet();
/* play、with同时执行,且都待after完毕之后才开始 */
animSet.play(rotate).with(fadeInOut).with(scale).after(moveIn);
animSet.setDuration(2000);
animSet.start();
待动画结束后,在View新的位置点击,可以触发View的事件,在原位置反而不能触发,这是动画对View的改变是持久化的生效了。View存在两个状态(位置),动画开始前的初始位置和结束后的结束位置。View是以结束位置生效的,但重新执行动画,还是以初始位置为开始,而不是以现有的结束位置作为新的开始。
前面说了,属性动画的对象可以不是View,也可以不是那4种,实现这个的核心是TypeEvalutor。其实前面用的ofFloat也是使用的内置的一个TypeEvalutor的子类。我们看下TypeEvalutor的代码:
public interface TypeEvaluator<T> {
public T evaluate(float fraction, T startValue, T endValue);
}
这个接口只有一个方法,它有3个参数。第一个参数是因子,取值范围是[0,1],后面的两个参数是开始状态和结束状态(ofFloat后面数组参数,就是对应这里的状态)。默认的4种动画的变化过程是线性的,入参是数字,所以可以计算出来。但是我们自定义的状态,就需要我们计算了。计算的思想,在接口的注释中有说明:result = x0 + t * (x1 - x0), where x0 is startValue, x1 is endValue, and t is fraction. 这里t可以理解为采样率,单次采样,就是取开始状态的百分比,剩余部分取结束状态,即得到一个中间状态。这个中间状态就是返回结果。return和param是同一个类型的。 这个动画还需要添加updateListner,用于将中间状态更新到目标上去。 下面的例子,就是将颜色渐变为另一种颜色的过程:
int[] start = {0x11, 0x33, 0xee};
int[] end = {0xee, 0xbb, 0x11};
TypeEvaluator e = new TypeEvaluator() {
/**
* @param fraction 0~1
*/
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
int[] start = (int[]) startValue;
int[] end = (int[]) endValue;
int rr = (int) (start[0] * fraction + end[0] * (1 - fraction));
int gg = (int) (start[1] * fraction + end[1] * (1 - fraction));
int bb = (int) (start[2] * fraction + end[2] * (1 - fraction));
return new int[]{rr, gg, bb};
}
};
ValueAnimator a = ValueAnimator.ofObject(e, start, end);
a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int[] array = (int[]) animation.getAnimatedValue();
int color = Color.rgb(array[0], array[1], array[2]);
Logs.i("tv6 onAnimationUpdate: " + Integer.toHexString(color));
tv6.setTextColor(color);
}
});
动画还有速率的变化,默认的速率是先加速,中间匀速,后减速的过程。我们可以用TimeInterpolator来设置自己的速率。
public interface TimeInterpolator {
float getInterpolation(float input);
}
通过这个接口的return,影响evalute的因子fraction的变化增幅,来控制动画的幅度。下面是例子,是一个View加速变宽,直到屏幕宽度的过程。
TypeEvaluator e = new TypeEvaluator() {
@Override
public Object evaluate(float fraction, Object startValue, Object endValue) {
Bean start = (Bean) startValue;
Bean end = (Bean) endValue;
int i = (int) (start.i * (1 - fraction) + end.i * fraction);
return new Bean(i);
}
};
Bean start = new Bean(50);
Bean end = new Bean(ScreenUtils.getScreenShortSize());
ValueAnimator a = ValueAnimator.ofObject(e, start, end);
if (custom) {
/* 这个算法的设置,影响TypeEvaluator中fraction的增长幅度 */
a.setInterpolator(new TimeInterpolator() {
@Override
public float getInterpolation(float input) {
float f = 2 * input * input;
Logs.i("tv7 TimeInterpolator: " + input + ", " + f);
return f;
}
});
}
a.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
ViewGroup.MarginLayoutParams params = (ViewGroup.MarginLayoutParams) tv7Target.getLayoutParams();
params.width = ((Bean) animation.getAnimatedValue()).i;
tv7Target.setLayoutParams(params);
}
});
a.setDuration(3000);
a.start();