第七章(5) 属性动画

第七章(5) 属性动画_第1张图片
7.3.4 对任意属性做动画

这里先提出一个问题:给Button加一个动画,让这个Button的宽度从当前宽度增加到500px。也许你会说,这很简单,用View动画就可以搞定。很快你就会恍然大悟,原来View动画根本不支持对宽度进行动画。没错,View动画只支持四种类型:平移,旋转,缩放,不透明度。当然用X方向缩放(scaleX)可以让ButtonX方向放大,看起来好像是宽度增加了,实际上不是,只是Button被放大了而已,而且由于只X方向被放大,这个时候Button的背景以及上面的文本都被拉伸了,甚至有可能Button会超出屏幕。

@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn:
            ObjectAnimator.ofInt(button,"width",500).setDuration(2000).start();
            break;
    }
}

上述代码运行后发现没效果,其实没效果是对的,如果随便传递一个属性过去,轻则没动画效果,重则程序直接Crash

属性动画的原理:属性动画要求动画作用的对象提供该属性的getset方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递给set方法的值都不一样,确切来说,随着时间的推移,所传递的值越来越接近最终值。总结一下,我们对object的属性abc做动画,如果想让动画生效,要同时满足两个条件。

  • object必须要提供setAbc方法,如果动画的时候没有传递初始值,那么还要提供getAbc方法,因为系统要去取abc属性的初始值(如果这条不满足,程序直接Crash)。

  • objectsetAbc对属性abc所做的改变必须能够通过某种方法反应出来,比如会带来UI的改变之类的(如果这条不满足,动画无效果但不会Crash)。

以上条件缺一不可,Button内部虽然提供了getWidthsetWidth方法,但是这个setWidth方法并不是改变视图的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,由于Button继承了TextView,所以Button也就有了setWidth方法。

打开TextView的源码,可以发现getWidth的确是获取View的宽度的,但是setWidth是设置TextView的最大宽度和最小宽度的,这个和TextView的宽度不是一个东西。具体来说,TextView的宽度对应XML中的android:layout_width属性,而TextView还有一个属性android:width,这个android:width属性就对应了TextViewsetWidth方法。总之,TextViewButtonsetWidthgetWidth干的不是同一件事情,通过setWidth无法改变控件的宽度,所以对width做属性动画没有效果。对应于属性动画的两个条件来说,本例中动画不生效的原因是只满足了条件1而未满足条件2

针对上述问题,官方文档上告诉我们有3种解决方法:

  • 给你的对象加上getset方法,如果你有权限的话。
  • 用一个类来包装原始对象,间接为其提供getset方法。
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn:
            ViewWrapper wrapper = new ViewWrapper(button);
            ObjectAnimator.ofInt(wrapper,"width",500).setDuration(2000).start();
            break;
    }
}

private static class ViewWrapper {

    private View mTarget;

    public ViewWrapper(View mTarget) {
        this.mTarget = mTarget;
    }

    public int getWidth() {
        return mTarget.getLayoutParams().width;
    }

    public void setWidth(int width) {
        mTarget.getLayoutParams().width = width;
        mTarget.requestLayout();
    }
}

提供了ViewWrapper 类专门用于包装View,然后我们对ViewWrapperwidth属性做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button

  • 采用ValueAnimator,监听动画过程,自己实现属性的改变。
    ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程。在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。
@Override
public void onClick(View v) {
    switch (v.getId()) {
        case R.id.btn:
            performAnimate(button,button.getWidth(),500);
            break;
    }
}

private void performAnimate(final View target,final int start,final int end) {
    ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
    valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {

        // 持有一个IntEvaluator对象,方便下面估值的时候使用
        private IntEvaluator mEvaluator = new IntEvaluator();

        @Override
        public void onAnimationUpdate(ValueAnimator animation) {
            
            // 获得当前动画的进度值,整型,1~100之间
            int currentValue = (int) animation.getAnimatedValue();
            
            // 获得当前进度占整个动画过程的比例,浮点型 0~1之间
            float fraction = animation.getAnimatedFraction();
            
            // 直接调用整型估值器,通过比例计算出宽度,然后在设给Button
            target.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
            target.requestLayout();
        }
    });
    valueAnimator.setDuration(5000).start();
}

它会在5000ms内将一个数从1变到100,然后动画的每一帧会回调onAnimationUpdate方法。在这个方法里,我们可以获取当前的值(1~100)和当前值所占的比例,我们可以计算出Button现在的宽度应该是多少。比如时间过了一半,当前值是50,比例为0.5,假设Button的起始宽度是100px,最终宽度是500px,那么Button应该增加的宽度是400 * 0.5 = 200,那么当前Button的宽度应该为初始宽度 + 增加宽度(100 + 200 = 300)

7.3.5 属性动画的工作原理

属性动画要求动画作用的对象提供该属性的set方法,属性动画根据你传递的该属性的初始值和最终值,以动画的效果多次去调用set方法。每次传递给set方法的值都不一样,确切来说是随着时间的推移,所传递的值越来越接近最终值。如果动画的时候没有传递初始值,那么还要提供get方法,因为系统要去获取属性的初始值。对于属性动画来说,其动画过程中所做的就是这么多。

7.4 使用动画的注意事项

通过动画可以实现一些比较绚丽的效果,但是在使用过程中,也需要注意一些事情,主要分为下面几类:

1. OOM问题

这个问题主要出现在帧动画中,当图片数量较多且图片较大时就极易出现OOM,这个在实际的开发中要尤其注意,尽量避免使用帧动画。

2. 内存泄漏

在属性动画中有一类无限循环的动画,这类动画需要在Activity退出时及时停止,否则将导致Activity无法释放从而造成内存泄漏,通过验证后发现View动画并不存在此问题。

3. 兼容性问题

动画在3.0以下的系统上有兼容性问题,在某些特殊场景可能无法正常工作,因此要做好适配工作。

4. View动画的问题

View动画是对View的影像做动画,并不是真正地改变View的状态,因此有时候会出现动画完成后View无法隐藏的现象,即setVisibility(View.GONE)失效了,这个时候只要调用view.clearAnimation()清除View动画即可解决此问题。

5. 不要使用px

在进行动画的过程中,要尽量使用dp,使用px会导致在不同的设备上有不同的效果。

6. 动画元素的交互

View移动(平移)后,在Android3.0以前的系统上,不管是View动画还是属性动画,新位置均无法触发单击事件,同时,老位置仍然可以触发单击事件。尽管View已经在视觉上不存在了,将View移回原位置以后,原位置的单击事件继续生效。从3.0开始,属性动画的单击事件触发位置为移动后的位置,但是View动画仍然在原位置。

7. 硬件加速

使用动画的过程中,建议开启硬件加速,这样会提高动画的流畅性。

你可能感兴趣的:(第七章(5) 属性动画)