給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.若是你能把我這個動畫系列博文都看一遍而且理解它,我認爲你對動畫絕對算得上精通,並且我不認爲有面試官可以在動畫上問倒你