给Button加一个动画,让这个Button的宽度从当前宽度增长到500px。android
也许你会说,这很简单,用渐变更画就能够搞定,咱们能够来试试,你能写出来吗?很快你就会恍然大悟,原来渐变更画根本不支持对宽度进行动画啊,没错,渐变更画只支持四种类型:平移(Translate)、旋转(Rotate)、缩放(Scale)、不透明度(Alpha)。固然你用x方向缩放(scaleX)可让Button在x方向放大,看起来好像是宽度增长了,实际上不是,只是Button被放大了而已,并且因为只在x方向被放大,这个时候Button的背景以及上面的文本都被拉伸了,甚至有可能Button会超出屏幕。下面是效果图面试
上述效果显然是不好的,并且也不是真正地对宽度作动画,不过,所幸咱们还有属性动画,咱们用属性动画试试app
看demoide
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作动画,若是想让动画生效,要同时知足两个条件:ui
1. object必需要提供setXxx方法,若是动画的时候没有传递初始值,那么还要提供getXxx方法,由于系统要去拿xxx属性的初始值(若是这条不知足,程序直接Crash)this
2. object的setXxx对属性xxx所作的改变必须可以经过某种方法反映出来,好比会带来ui的改变啥的(若是这条不知足,动画无效果但不会Crash)lua
以上条件缺一不可spa
那么为何咱们对Button的width属性作动画没有效果?这是由于Button内部虽然提供了getWidth和setWidth方法,可是这个setWidth方法并非改变视图的大小,它是TextView新添加的方法,View是没有这个setWidth方法的,因为Button继承了TextView,因此Button也就有了setWidth方法。下面看一下这个getWidth和setWidth方法的源码:.net
/** * 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就搞定了,可是不少时候咱们没权限去这么作,好比本文开头所提到的问题,你没法给Button加上一个合乎要求的setWidth方法,由于这是Android SDK内部实现的。这个方法最简单,可是每每是不可行的,这里就不对其进行更多分析了。
这是一个颇有用的解决方法,是我最喜欢用的,由于用起来很方便,也很好理解,下面将经过一个具体的例子来介绍它
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,这样一个间接属性动画就搞定了。上述代码一样适用于一个对象的其余属性。下面看效果
ok,效果达到了,真正实现了对宽度作动画。
首先说说啥是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.若是你能把我这个动画系列博文都看一遍而且理解它,我认为你对动画绝对算得上精通,并且我不认为有面试官可以在动画上问倒你