zoukankan      html  css  js  c++  java
  • Android属性动画

    原文地址: http://www.2cto.com/kf/201401/270169.html

    问题:

    给Button加一个动画,让这个Button的宽度从当前宽度增加到500px。

    也许你会说,这很简单,用渐变动画就可以搞定,我们可以来试试,你能写出来吗?很快你就会恍然大悟,原来渐变动画根本不支持对宽度进行动画啊,没错,渐变动画只支持四种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、不透明度(Alpha)。当然你用x方向缩放(scaleX)可以让Button在x方向放大,看起来好像是宽度增加了,实际上不是,只是Button被放大了而已,而且由于只在x方向被放大,这个时候Button的背景以及上面的文本都被拉伸了,甚至有可能Button会超出屏幕。下面是效果图

    data-cke-saved-src=http://www.2cto.com/uploadfile/Collfiles/20140105/2014010509443915.gif

    上述效果显然是很差的,而且也不是真正地对宽度做动画,不过,所幸我们还有属性动画,我们用属性动画试试

    看demo

        private void performAnimate() {
            ObjectAnimator.ofInt(mButton, width, 500).setDuration(5000).start();
        }
    
        @Override
        public void onClick(View v) {
            if (v == mButton) {
                performAnimate();
            }
        }

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

    下面分析下属性动画的原理:

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

    1. object必须要提供setXxx方法,如果动画的时候没有传递初始值,那么还要提供getXxx方法,因为系统要去拿xxx属性的初始值(如果这条不满足,程序直接Crash)

    2. object的setXxx对属性xxx所做的改变必须能够通过某种方法反映出来,比如会带来ui的改变啥的(如果这条不满足,动画无效果但不会Crash)

    以上条件缺一不可

    那么为什么我们对Button的width属性做动画没有效果?这是因为Button内部虽然提供了getWidth和setWidth方法,但是这个setWidth方法并不是改变视图的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,由于Button继承了TextView,所以Button也就有了setWidth方法。下面看一下这个getWidth和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();
        }
    
        /**
         * Return the width of the your view.
         *
         * @return The width of your view, in pixels.
         */
        @ViewDebug.ExportedProperty(category = layout)
        public final int getWidth() {
            return mRight - mLeft;
        }

    从源码可以看出,getWidth的确是获取View的宽度的,而setWidth是TextView和其子类的专属方法,它的作用不是设置View的宽度,而是设置TextView的最大宽度和最小宽度的,这个和TextView的宽度不是一个东西,具体来说,TextView的宽度对应Xml中的android:layout_width属性,而TextView还有一个属性android:width,这个android:width属性就对应了TextView的setWidth方法。好吧,我承认我的这段描述有点混乱,但事情的确是这个样子的,而且我目前还没发现这个android:width属性有啥重要的用途,感觉好像没用似的,这里就不深究了,不然就偏离主题了。总之,TextView和Button的setWidth和getWidth干的不是同一件事情,通过setWidth无法改变控件的宽度,所以对width做属性动画没有效果,对应于属性动画的两个条件来说,本例中动画不生效的原因是只满足了条件1未满足条件2。

    针对上述问题,Google告诉我们有3中解决方法:

    1. 给你的对象加上get和set方法,如果你有权限的话

    2. 用一个类来包装原始对象,间接为其提供get和set方法

    3. 采用ValueAnimator,监听动画过程,自己实现属性的改变

    看起来有点抽象,不过不用担心,下面我会一一介绍。

    对任何属性做动画

    针对上面提出的三种解决方法,这里会给出具体的介绍:

    给你的对象加上get和set方法,如果你有权限的话

    这个的意思很好理解,如果你有权限的话,加上get和set就搞定了,但是很多时候我们没权限去这么做,比如本文开头所提到的问题,你无法给Button加上一个合乎要求的setWidth方法,因为这是Android SDK内部实现的。这个方法最简单,但是往往是不可行的,这里就不对其进行更多分析了。

    用一个类来包装原始对象,间接为其提供get和set方法

    这是一个很有用的解决方法,是我最喜欢用的,因为用起来很方便,也很好理解,下面将通过一个具体的例子来介绍它

        private void performAnimate() {
            ViewWrapper wrapper = new ViewWrapper(mButton);
            ObjectAnimator.ofInt(wrapper, width, 500).setDuration(5000).start();
        }
    
        @Override
        public void onClick(View v) {
            if (v == mButton) {
                performAnimate();
            }
        }
    
        private static class ViewWrapper {
            private View mTarget;
    
            public ViewWrapper(View target) {
                mTarget = target;
            }
    
            public int getWidth() {
                return mTarget.getLayoutParams().width;
            }
    
            public void setWidth(int width) {
                mTarget.getLayoutParams().width = width;
                mTarget.requestLayout();
            }
        }

    上述代码5s内让Button的宽度增加到500px,为了达到这个效果,我们提供了ViewWrapper类专门用于包装View,具体到本例是包装Button,然后我们对ViewWrapper的width熟悉做动画,并且在setWidth方法中修改其内部的target的宽度,而target实际上就是我们包装的Button,这样一个间接熟悉动画就搞定了。上述代码同样适用于一个对象的其他属性。下面看效果

    data-cke-saved-src=http://www.2cto.com/uploadfile/Collfiles/20140105/2014010509444017.gif

    ok,效果达到了,真正实现了对宽度做动画。

    采用ValueAnimator,监听动画过程,自己实现属性的改变

    首先说说啥是ValueAnimator,ValueAnimator本身不作用于任何对象,也就是说直接使用它没有任何动画效果。它可以对一个值做动画,然后我们可以监听其动画过程,在动画过程中修改我们的对象的属性值,这样也就相当于我们的对象做了动画。还是不太明白?没关系,下面用例子说明

        private void performAnimate(final View target, final int start, final int end) {
            ValueAnimator valueAnimator = ValueAnimator.ofInt(1, 100);
    
            valueAnimator.addUpdateListener(new AnimatorUpdateListener() {
    
                //持有一个IntEvaluator对象,方便下面估值的时候使用
                private IntEvaluator mEvaluator = new IntEvaluator();
    
                @Override
                public void onAnimationUpdate(ValueAnimator animator) {
                    //获得当前动画的进度值,整型,1-100之间
                    int currentValue = (Integer)animator.getAnimatedValue();
                    Log.d(TAG, current value:  + currentValue);
    
                    //计算当前进度占整个动画过程的比例,浮点型,0-1之间
                    float fraction = currentValue / 100f;
    
                    //这里我偷懒了,不过有现成的干吗不用呢
                    //直接调用整型估值器通过比例计算出宽度,然后再设给Button
                    target.getLayoutParams().width = mEvaluator.evaluate(fraction, start, end);
                    target.requestLayout();
                }
            });
    
            valueAnimator.setDuration(5000).start();
        }
    
        @Override
        public void onClick(View v) {
            if (v == mButton) {
                performAnimate(mButton, mButton.getWidth(), 500);
            }
        }

    上述代码的动画效果图和采用ViewWrapper是一样的,请参看上图。关于这个ValueAnimator我要再说一下,拿上例来说,它会在5000ms内将一个数从1变到100,然后动画的每一帧会回调onAnimationUpdate方法,在这个方法里,我们可以获取当前的值(1-100),根据当前值所占的比例(当前值/100),我们可以计算出Button现在的宽度应该是多少,比如时间过了一半,当前值是50,比例为0.5,假设Button的起始宽度是100px,最终宽度是500px,那么Button增加的宽度也应该占总增加宽度的一半,总增加宽度是500-100=400,所以这个时候Button应该增加宽度400*0.5=200,那么当前Button的宽度应该为初始宽度+ 增加宽度(100+200=300)。上述计算过程很简单,其实它就是整型估值器IntEvaluator的内部实现,所有我们不用自己写了,直接用吧。

    写在后面的话

    到此为止,本文的分析基本完成,有几点是我想再说一下的。

    1.View动画(渐变动画)的功能是有限的,大家可以尝试使用属性动画

    2.为了在各种安卓版本上使用属性动画,你需要采用nineoldandroids,它是GitHub开源项目,jar包和源码都可以在网上下到,如果下不到jar包,我可以发给大家

    3.再复杂的动画都是简单动画的合理组合,再加上本文介绍的方法,可以对任何属性作用动画效果,也就是说你几乎可以做出任何动画

    4.属性动画中的插值器(Interpolator)和估值器(TypeEvaluator)很重要,它是实现非匀速动画的重要手段,你应该试着搞懂它,最好你还能够自定义它们

    5.如果你能把我这个动画系列博文都看一遍并且理解它,我认为你对动画绝对算得上精通,而且我不认为有面试官能够在动画上问倒你

  • 相关阅读:
    self 和 super 关键字
    NSString类
    函数和对象方法的区别
    求两个数是否互质及最大公约数
    TJU Problem 1644 Reverse Text
    TJU Problem 2520 Quicksum
    TJU Problem 2101 Bullseye
    TJU Problem 2548 Celebrity jeopardy
    poj 2586 Y2K Accounting Bug
    poj 2109 Power of Cryptography
  • 原文地址:https://www.cnblogs.com/zyandroid/p/4468324.html
Copyright © 2011-2022 走看看