属性动画

136 阅读3分钟

帧动画、补间动画、属性动画

补间动画的缺点有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();