Android自定义view和动画

Android动画分析(对任意属性做动画效果)

2017-01-23  本文已影响62人  ReturnYHH

上篇文章中,我们曾经提到过,ObjectAnimator实现动画效果是,可以这样写:

  ObjectAnimator
                .ofFloat(myView, "rotationX", 0.0F, 360.0F)
                .setDuration(500)//
                .start();

上篇文章说过,第二个参数是指属性的名字,例如rotationX,但是如果我们随便写个值的话,轻者则无动画效果,重者可能会崩溃,为什么呢?今天就来说说原因


举个例子,给Button加一个动画,让这个Button的宽度从当前宽度增加到500,也许你可能会这样想:

  ObjectAnimator
                .ofFloat(mButton, "width", 500)
                .setDuration(500)//
                .start();

可是,当运行时,你会发现没有任何效果,为什么呢?其实,我们如果知道属性动画的原理,就很容易理解了


属性动画原理

属性动画要求做动画效果的属性需要提供get和set的方法,属性动画根据外界传递的该属性的初始值和最终值,以动画的效果多次去调用set方法,每次传递set方法的值都不一样,随着时间的推移,越来越接近最终值,所以,总的来说,需要动画生效,需满足两个条件:

(1)对象的属性必须提供set和get方法
(2)set方法可以带来效果,比如我setWidth后,宽度会带来UI的变化,否则你也看不出变化是吧

在回到我们的例子上,有人可能会说,Button提供了setWidth方法啊,为什么还是不行呢?别着急,我们来看看setWidth的源码:

/**
     * Makes the TextView exactly this many pixels wide.
     * You could do the same thing by specifying this number in the
     * LayoutParams.
     *
     * @see #setMaxWidth(int)
     * @see #setMinWidth(int)
     * @see #getMinWidth()
     * @see #getMaxWidth()
     *
     * @attr ref android.R.styleable#TextView_width
     */
    @android.view.RemotableViewMethod
    public void setWidth(int pixels) {
        mMaxWidth = mMinWidth = pixels;
        mMaxWidthMode = mMinWidthMode = PIXELS;

        requestLayout();
        invalidate();
    }

Button是继承TextView,setWidth是TextView和子类专属的方法,我们从源码中不难看出,它的作用不是设置宽度,而是设置最大宽度和最小宽度,也就是说跟我们想要设置Button宽度是两个东西,具体来说,TextView的宽度对应的是xml中的android:layout_width,而setWidth对应的是xml中的android:width,所以没有动画效果也是正常的,那如果我的需求就是类似这样的情况,我们怎么办呢?别着急,谷歌早就帮我们想好了

类似这样的情况,官方文档告诉我们有3种解决方法:
(1)给属性加上get和set的方法
(2)用一个类来封装原始对象,并提供get和set方法
(3)采用ValueAnimator,监听动画过程,自己实现属性的变化

首先我们来分别看看这几个方法:

方法1:

我们首先就可以排除了,我们不可能去修改Button的源码,给它加上get和set方法

方法2:

是一个很有用的解决方法,也是最普遍的,很方便也很好理解,我们看看例子

public class MyButton {
    private View mView;

    public MyButton(View view) {
        this.mView = view;
    }

    public int getWidth() {
        return mView.getWidth();
    }

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

我们先定义一个类来封装我们的Button,然后提供get和set方法,再来我们就可以调用了:

 MyButton button =new MyButton(new Button(this));
 ObjectAnimator.ofInt(button,"width",500).setDuration(500).start();

没错,就是如此简单

方法3

如果大家对ValueAnimator熟悉的话就知道,它不作用于任何对象,也就是说直接使用它是没有动画效果的,我们需要它对一个值做动画,然后监听变化过程,在变化过程中修改我们对象的属性值,就相当于我们对象做动画了

ValueAnimator animator = ValueAnimator.ofInt(1, 100);
animator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
            @Override
            public void onAnimationUpdate(ValueAnimator animation) {
                //获取当前动画进度值,1-100之间
                int  value = (int) animation.getAnimatedValue();
                //获取当前进度占整个动画过程比例,0-1之间
                float fraction = animation.getAnimatedFraction();
                //直接调用估值器,通过比例计算宽度,然后再赋值给Button
                IntEvaluator evaluator =new IntEvaluator();
                mButton.getLayoutParams().width=evaluator.evaluate(fraction,mButton.getWidth(),500);
                mButton.requestLayout();
            }
        });
animator.setDuration(500).start();

它在500ms内将一个数从1到100,然后动画的每一帧都会回调onAnimationUpdate方法,因此,我们可以获取当前的值和当前值所占的比例,在计算出Button当前的宽度应该是多少,比如时间过了一半,当前值是50,比例是0.5,假设Button的起始宽度是100,最终宽度是500,那么Button增加的宽度也应该占总增加宽度的一半,总增加的宽度是500-100=400,所以这时候Button应该增加的宽度是400*0.5=200,那么当前Button的宽度应该为初始宽度+增加宽度(100+200=300),这个计算IntEvaluator 估值器已经帮我们实现了,因此我们直接用就可以了


注意事项

动画可以为我们带来很多炫酷的效果,但同时也有很多需要注意的地方,主要分为这几类:

OOM

这个问题主要出现在帧动画中,因为帧动画是用多张图片轮播实现动画效果,那么如果图片过大就很容易出现OOM

内存泄漏

在属性动画中,有一类无限循环的动画,这类动画需要在Activity退出的时候及时停止,否则会导致无法释放从而导致内存泄漏,View动画没存在这问题

兼容性

动画是3.0以下的系统兼容性会存在问题,因此需要做好兼容工作

View动画的问题

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

不要使用px

在进行动画过程中,单位尽量用dp,使用px可能导致在不同设备上效果不一样

动画过后交互

view动画还是属性动画效果完成后,view的位置是不会发生改变的,也就是说view的位置还是在原来的位置,尽管view的视觉已经改变,位置是不发生变化,仅仅只是视觉的变化


好了,这篇文章到这就结束了

上一篇下一篇

猜你喜欢

热点阅读