文章轉(zhuǎn)載至郭神的博客
大家好,歡迎繼續(xù)回到Android屬性動畫完全解析。在上一篇文章當(dāng)中我們學(xué)習(xí)了屬性動畫的一些進(jìn)階技巧,包括ValueAnimator和ObjectAnimator的高級用法,那么除了這些之外,當(dāng)然還有一些其它的高級技巧在等著我們學(xué)習(xí),因此本篇文章就對整個屬性動畫完全解析系列收個尾,來學(xué)習(xí)一下剩下的非常重要的高級技巧。
另外,本篇文章中使用的代碼是建立在上篇文章基礎(chǔ)之上的,如果你還沒有閱讀過前面的文章,建議先去參考閱讀一下 Android屬性動畫完全解析(中),ValueAnimator和ObjectAnimator的高級用法 。
Interpolator的用法
Interpolator這個東西很難進(jìn)行翻譯,直譯過來的話是補(bǔ)間器的意思,它的主要作用是可以控制動畫的變化速率,比如去實現(xiàn)一種非線性運(yùn)動的動畫效果。那么什么叫做非線性運(yùn)動的動畫效果呢?就是說動畫改變的速率不是一成不變的,像加速運(yùn)動以及減速運(yùn)動都屬于非線性運(yùn)動。
不過Interpolator并不是屬性動畫中新增的技術(shù),實際上從Android 1.0版本開始就一直存在Interpolator接口了,而之前的補(bǔ)間動畫當(dāng)然也是支持這個功能的。只不過在屬性動畫中新增了一個TimeInterpolator接口,這個接口是用于兼容之前的Interpolator的,這使得所有過去的Interpolator實現(xiàn)類都可以直接拿過來放到屬性動畫當(dāng)中使用,那么我們來看一下現(xiàn)在TimeInterpolator接口的所有實現(xiàn)類,如下圖所示:
可以看到,TimeInterpolator接口已經(jīng)有非常多的實現(xiàn)類了,這些都是Android系統(tǒng)內(nèi)置好的并且我們可以直接使用的Interpolator。每個Interpolator都有它各自的實現(xiàn)效果,比如說AccelerateInterpolator就是一個加速運(yùn)動的Interpolator,而DecelerateInterpolator就是一個減速運(yùn)動的Interpolator。
我覺得細(xì)心的朋友應(yīng)該早已經(jīng)發(fā)現(xiàn)了,在前面兩篇文章當(dāng)中我們所學(xué)到的所有屬性動畫,其實都不是在進(jìn)行一種線程運(yùn)動。比如說在“上”篇文章中使用ValueAnimator所打印的值如下所示:
可以看到,一開始的值變化速度明顯比較慢,僅0.0開頭的就打印了4次,之后開始加速,最后階段又開始減速,因此我們可以很明顯地看出這一個先加速后減速的Interpolator。
那么再來看一下在“中”篇文章中完成的小球移動加變色的功能,如下圖所示:
從上圖中我們明顯可以看出,小球一開始運(yùn)動速度比較慢,然后逐漸加速,中間的部分運(yùn)動速度就比較快,接下來開始減速,最后緩緩?fù)W?。另外顏色變化也是這種規(guī)律,一開始顏色變化的比較慢,中間顏色變化的很快,最后階段顏色變化的又比較慢。
從以上幾點我們就可以總結(jié)出一個結(jié)論了,使用屬性動畫時,系統(tǒng)默認(rèn)的Interpolator其實就是一個先加速后減速的Interpolator,對應(yīng)的實現(xiàn)類就是AccelerateDecelerateInterpolator。
當(dāng)然,我們也可以很輕松地修改這一默認(rèn)屬性,將它替換成任意一個系統(tǒng)內(nèi)置好的Interpolator。就拿“中”篇文章中的代碼來舉例吧,MyAnimView中的startAnimation()方法是開啟動畫效果的入口,這里我們對Point對象的坐標(biāo)稍做一下修改,讓它變成一種垂直掉落的效果,代碼如下所示:
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setDuration(2000);
anim.start();
}
這里主要是對Point構(gòu)造函數(shù)中的坐標(biāo)值進(jìn)行了一下改動,那么現(xiàn)在小球運(yùn)動的動畫效果應(yīng)該是從屏幕正中央的頂部掉落到底部。但是現(xiàn)在默認(rèn)情況下小球的下降速度肯定是先加速后減速的,這不符合物理的常識規(guī)律,如果把小球視為一個自由落體的話,那么下降的速度應(yīng)該是越來越快的。我們怎樣才能改變這一默認(rèn)行為呢?其實很簡單,調(diào)用Animator的setInterpolator()方法就可以了,這個方法要求傳入一個實現(xiàn)TimeInterpolator接口的實例,那么比如說我們想要實現(xiàn)小球下降越來越快的效果,就可以使用AccelerateInterpolator,代碼如下所示:
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(new AccelerateInterpolator(2f));
anim.setDuration(2500);
anim.start();
}
代碼很簡單,這里調(diào)用了setInterpolator()方法,然后傳入了一個AccelerateInterpolator的實例,注意AccelerateInterpolator的構(gòu)建函數(shù)可以接收一個float類型的參數(shù),這個參數(shù)是用于控制加速度的?,F(xiàn)在運(yùn)行一下代碼,效果如下圖所示:
OK,效果非常明顯,說明我們已經(jīng)成功替換掉了默認(rèn)的Interpolator,AccelerateInterpolator確實是生效了。但是現(xiàn)在的動畫效果看上去仍然是怪怪的,因為一個小球從很高的地方掉落到地面上直接就靜止了,這也是不符合物理規(guī)律的,小球撞擊到地面之后應(yīng)該要反彈起來,然后再次落下,接著再反彈起來,又再次落下,以此反復(fù),最后靜止。這個功能我們當(dāng)然可以自己去寫,只不過比較復(fù)雜,所幸的是,Android系統(tǒng)中已經(jīng)提供好了這樣一種Interpolator,我們只需要簡單地替換一下就可以完成上面的描述的效果,代碼如下所示:
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(new BounceInterpolator());
anim.setDuration(3000);
anim.start();
}
可以看到,我們只是將設(shè)置的Interpolator換成了BounceInterpolator的實例,而BounceInterpolator就是一種可以模擬物理規(guī)律,實現(xiàn)反復(fù)彈起效果的Interpolator。另外還將整體的動畫時間稍微延長了一點,因為小球反復(fù)彈起需要比之前更長的時間?,F(xiàn)在重新運(yùn)行一下代碼,效果如下圖所示:
OK!效果還是非常不錯的。那么這里我們只是選了幾個系統(tǒng)實現(xiàn)好的Interpolator,由于內(nèi)置Interpolator非常多,就不一一進(jìn)行講解了,大家可以自己去使用一下其它的幾種Interpolator來看一看效果。
但是,只會用一下系統(tǒng)提供好的Interpolator,我們顯然對自己的要求就太低了,既然是學(xué)習(xí)屬性動畫的高級用法,那么自然要將它研究透了。下面我們就來看一下Interpolator的內(nèi)部實現(xiàn)機(jī)制是什么樣的,并且來嘗試寫一個自定義的Interpolator。
首先看一下TimeInterpolator的接口定義,代碼如下所示:
/**
* A time interpolator defines the rate of change of an animation. This allows animations
* to have non-linear motion, such as acceleration and deceleration.
*/
public interface TimeInterpolator {
/**
* Maps a value representing the elapsed fraction of an animation to a value that represents
* the interpolated fraction. This interpolated value is then multiplied by the change in
* value of an animation to derive the animated value at the current elapsed animation time.
*
* @param input A value between 0 and 1.0 indicating our current point
* in the animation where 0 represents the start and 1.0 represents
* the end
* @return The interpolation value. This value can be more than 1.0 for
* interpolators which overshoot their targets, or less than 0 for
* interpolators that undershoot their targets.
*/
float getInterpolation(float input);
}
OK,接口還是非常簡單的,只有一個getInterpolation()方法。大家有興趣可以通過注釋來對這個接口進(jìn)行詳解的了解,這里我就簡單解釋一下,getInterpolation()方法中接收一個input參數(shù),這個參數(shù)的值會隨著動畫的運(yùn)行而不斷變化,不過它的變化是非常有規(guī)律的,就是根據(jù)設(shè)定的動畫時長勻速增加,變化范圍是0到1。也就是說當(dāng)動畫一開始的時候input的值是0,到動畫結(jié)束的時候input的值是1,而中間的值則是隨著動畫運(yùn)行的時長在0到1之間變化的。
說到這個input的值,我覺得有不少朋友可能會聯(lián)想到我們在“中”篇文章中使用過的fraction值。那么這里的input和fraction有什么關(guān)系或者區(qū)別呢?答案很簡單,input的值決定了fraction的值。input的值是由系統(tǒng)經(jīng)過計算后傳入到getInterpolation()方法中的,然后我們可以自己實現(xiàn)getInterpolation()方法中的算法,根據(jù)input的值來計算出一個返回值,而這個返回值就是fraction了。
因此,最簡單的情況就是input值和fraction值是相同的,這種情況由于input值是勻速增加的,因而fraction的值也是勻速增加的,所以動畫的運(yùn)動情況也是勻速的。系統(tǒng)中內(nèi)置的LinearInterpolator就是一種勻速運(yùn)動的Interpolator,那么我們來看一下它的源碼是怎么實現(xiàn)的:
/**
* An interpolator where the rate of change is constant
*/
@HasNativeInterpolator
public class LinearInterpolator extends BaseInterpolator implements NativeInterpolatorFactory {
public LinearInterpolator() {
}
public LinearInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return input;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createLinearInterpolator();
}
}
這里我們只看getInterpolation()方法,這個方法沒有任何邏輯,就是把參數(shù)中傳遞的input值直接返回了,因此fraction的值就是等于input的值的,這就是勻速運(yùn)動的Interpolator的實現(xiàn)方式。
當(dāng)然這是最簡單的一種Interpolator的實現(xiàn)了,我們再來看一個稍微復(fù)雜一點的。既然現(xiàn)在大家都知道了系統(tǒng)在默認(rèn)情況下使用的是AccelerateDecelerateInterpolator,那我們就來看一下它的源碼吧,如下所示:
/**
* An interpolator where the rate of change starts and ends slowly but
* accelerates through the middle.
*
*/
@HasNativeInterpolator
public class AccelerateDecelerateInterpolator implements Interpolator, NativeInterpolatorFactory {
public AccelerateDecelerateInterpolator() {
}
@SuppressWarnings({"UnusedDeclaration"})
public AccelerateDecelerateInterpolator(Context context, AttributeSet attrs) {
}
public float getInterpolation(float input) {
return (float)(Math.cos((input + 1) * Math.PI) / 2.0f) + 0.5f;
}
/** @hide */
@Override
public long createNativeInterpolator() {
return NativeInterpolatorFactoryHelper.createAccelerateDecelerateInterpolator();
}
}
代碼雖然沒有變長很多,但是getInterpolation()方法中的邏輯已經(jīng)明顯變復(fù)雜了,不再是簡單地將參數(shù)中的input進(jìn)行返回,而是進(jìn)行了一個較為復(fù)雜的數(shù)學(xué)運(yùn)算。那這里我們來分析一下它的算法實現(xiàn),可以看到,算法中主要使用了余弦函數(shù),由于input的取值范圍是0到1,那么cos函數(shù)中的取值范圍就是π到2π。而cos(π)的結(jié)果是-1,cos(2π)的結(jié)果是1,那么這個值再除以2加上0.5之后,getInterpolation()方法最終返回的結(jié)果值還是在0到1之間。只不過經(jīng)過了余弦運(yùn)算之后,最終的結(jié)果不再是勻速增加的了,而是經(jīng)歷了一個先加速后減速的過程。我們可以將這個算法的執(zhí)行情況通過曲線圖的方式繪制出來,結(jié)果如下圖所示:
可以看到,這是一個S型的曲線圖,當(dāng)橫坐標(biāo)從0變化到0.2的時候,縱坐標(biāo)的變化幅度很小,但是之后就開始明顯加速,最后橫坐標(biāo)從0.8變化到1的時候,縱坐標(biāo)的變化幅度又變得很小。
OK,通過分析LinearInterpolator和AccelerateDecelerateInterpolator的源碼,我們已經(jīng)對Interpolator的內(nèi)部實現(xiàn)機(jī)制有了比較清楚的認(rèn)識了,那么接下來我們就開始嘗試編寫一個自定義的Interpolator。
編寫自定義Interpolator最主要的難度都是在于數(shù)學(xué)計算方面的,由于我數(shù)學(xué)并不是很好,因此這里也就寫一個簡單點的Interpolator來給大家演示一下。既然屬性動畫默認(rèn)的Interpolator是先加速后減速的一種方式,這里我們就對它進(jìn)行一個簡單的修改,讓它變成先減速后加速的方式。新建DecelerateAccelerateInterpolator類,讓它實現(xiàn)TimeInterpolator接口,代碼如下所示:
public class DecelerateAccelerateInterpolator implements TimeInterpolator{
@Override
public float getInterpolation(float input) {
float result;
if (input <= 0.5) {
result = (float) (Math.sin(Math.PI * input)) / 2;
} else {
result = (float) (2 - Math.sin(Math.PI * input)) / 2;
}
return result;
}
}
這段代碼是使用正弦函數(shù)來實現(xiàn)先減速后加速的功能的,因為正弦函數(shù)初始弧度的變化值非常大,剛好和余弦函數(shù)是相反的,而隨著弧度的增加,正弦函數(shù)的變化值也會逐漸變小,這樣也就實現(xiàn)了減速的效果。當(dāng)弧度大于π/2之后,整個過程相反了過來,現(xiàn)在正弦函數(shù)的弧度變化值非常小,漸漸隨著弧度繼續(xù)增加,變化值越來越大,弧度到π時結(jié)束,這樣從0過度到π,也就實現(xiàn)了先減速后加速的效果。
同樣我們可以將這個算法的執(zhí)行情況通過曲線圖的方式繪制出來,結(jié)果如下圖所示:
可以看到,這也是一個S型的曲線圖,只不過曲線的方向和剛才是相反的。從上圖中我們可以很清楚地看出來,一開始縱坐標(biāo)的變化幅度很大,然后逐漸變小,橫坐標(biāo)到0.5的時候縱坐標(biāo)變化幅度趨近于零,之后隨著橫坐標(biāo)繼續(xù)增加縱坐標(biāo)的變化幅度又開始變大,的確是先減速后加速的效果。
那么現(xiàn)在我們將DecelerateAccelerateInterpolator在代碼中進(jìn)行替換,如下所示:
private void startAnimation() {
Point startPoint = new Point(getWidth() / 2, RADIUS);
Point endPoint = new Point(getWidth() / 2, getHeight() - RADIUS);
ValueAnimator anim = ValueAnimator.ofObject(new PointEvaluator(), startPoint, endPoint);
anim.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
@Override
public void onAnimationUpdate(ValueAnimator animation) {
currentPoint = (Point) animation.getAnimatedValue();
invalidate();
}
});
anim.setInterpolator(new DecelerateAccelerateInterpolator());
anim.setDuration(3000);
anim.start();
}
非常簡單,就是將DecelerateAccelerateInterpolator的實例傳入到setInterpolator()方法當(dāng)中。重新運(yùn)行一下代碼,效果如下圖所示:
OK!小球的運(yùn)動確實是先減速后加速的效果,說明我們自定義的Interpolator已經(jīng)可以正常工作了。通過這樣一個程度的學(xué)習(xí),相信大家對屬性動畫Interpolator的理解和使用都達(dá)到了一個比較深刻的層次了。
ViewPropertyAnimator的用法
ViewPropertyAnimator其實算不上什么高級技巧,它的用法格外的簡單,只不過和前面所學(xué)的所有屬性動畫的知識不同,它并不是在3.0系統(tǒng)當(dāng)中引入的,而是在3.1系統(tǒng)當(dāng)中附增的一個新的功能,因此這里我們把它作為整個屬性動畫系列的收尾部分。
我們都知道,屬性動畫的機(jī)制已經(jīng)不是再針對于View而進(jìn)行設(shè)計的了,而是一種不斷地對值進(jìn)行操作的機(jī)制,它可以將值賦值到指定對象的指定屬性上。但是,在絕大多數(shù)情況下,我相信大家主要都還是對View進(jìn)行動畫操作的。Android開發(fā)團(tuán)隊也是意識到了這一點,沒有為View的動畫操作提供一種更加便捷的用法確實是有點太不人性化了,于是在Android 3.1系統(tǒng)當(dāng)中補(bǔ)充了ViewPropertyAnimator這個機(jī)制。
那我們先來回顧一下之前的用法吧,比如我們想要讓一個TextView從常規(guī)狀態(tài)變成透明狀態(tài),就可以這樣寫:
ObjectAnimator animator = ObjectAnimator.ofFloat(textview, "alpha", 0f);
animator.start();
看上去復(fù)雜嗎?好像也不怎么復(fù)雜,但確實也不怎么容易理解。我們要將操作的view、屬性、變化的值都一起傳入到ObjectAnimator.ofFloat()方法當(dāng)中,雖然看上去也沒寫幾行代碼,但這不太像是我們平時使用的面向?qū)ο蟮乃季S。
那么下面我們就來看一下如何使用ViewPropertyAnimator來實現(xiàn)同樣的效果,ViewPropertyAnimator提供了更加易懂、更加面向?qū)ο蟮腁PI,如下所示:
textview.animate().alpha(0f);
果然非常簡單!不過textview.animate()這個方法是怎么回事呢?animate()方法就是在Android 3.1系統(tǒng)上新增的一個方法,這個方法的返回值是一個ViewPropertyAnimator對象,也就是說拿到這個對象之后我們就可以調(diào)用它的各種方法來實現(xiàn)動畫效果了,這里我們調(diào)用了alpha()方法并轉(zhuǎn)入0,表示將當(dāng)前的textview變成透明狀態(tài)。
怎么樣?比起使用ObjectAnimator,ViewPropertyAnimator的用法明顯更加簡單易懂吧。除此之外,ViewPropertyAnimator還可以很輕松地將多個動畫組合到一起,比如我們想要讓textview運(yùn)動到500,500這個坐標(biāo)點上,就可以這樣寫:
textview.animate().x(500).y(500);
可以看出,ViewPropertyAnimator是支持連綴用法的,我們想讓textview移動到橫坐標(biāo)500這個位置上時調(diào)用了x(500)這個方法,然后讓textview移動到縱坐標(biāo)500這個位置上時調(diào)用了y(500)這個方法,將所有想要組合的動畫通過這種連綴的方式拼接起來,這樣全部動畫就都會一起被執(zhí)行。
那么怎樣去設(shè)定動畫的運(yùn)行時長呢?很簡單,也是通過連綴的方式設(shè)定即可,比如我們想要讓動畫運(yùn)行5秒鐘,就可以這樣寫:
textview.animate().x(500).y(500).setDuration(5000);
除此之外,本篇文章第一部分所學(xué)的Interpolator技術(shù)我們也可以應(yīng)用在ViewPropertyAnimator上面,如下所示:
textview.animate().x(500).y(500).setDuration(5000)
.setInterpolator(new BounceInterpolator());
用法很簡單,同樣也是使用連綴的方式。相信大家現(xiàn)在都已經(jīng)體驗出來了,ViewPropertyAnimator其實并沒有什么太多的技巧可言,用法基本都是大同小異的,需要用到什么功能就連綴一下,因此更多的用法大家只需要去查閱一下文檔,看看還支持哪些功能,有哪些接口可以調(diào)用就可以了。
那么除了用法之外,關(guān)于ViewPropertyAnimator有幾個細(xì)節(jié)還是值得大家注意一下的:
- 整個ViewPropertyAnimator的功能都是建立在View類新增的animate()方法之上的,這個方法會創(chuàng)建并返回一個ViewPropertyAnimator的實例,之后的調(diào)用的所有方法,設(shè)置的所有屬性都是通過這個實例完成的。
- 大家注意到,在使用ViewPropertyAnimator時,我們自始至終沒有調(diào)用過start()方法,這是因為新的接口中使用了隱式啟動動畫的功能,只要我們將動畫定義完成之后,動畫就會自動啟動。并且這個機(jī)制對于組合動畫也同樣有效,只要我們不斷地連綴新的方法,那么動畫就不會立刻執(zhí)行,等到所有在ViewPropertyAnimator上設(shè)置的方法都執(zhí)行完畢后,動畫就會自動啟動。當(dāng)然如果不想使用這一默認(rèn)機(jī)制的話,我們也可以顯式地調(diào)用start()方法來啟動動畫。
- ViewPropertyAnimator的所有接口都是使用連綴的語法來設(shè)計的,每個方法的返回值都是它自身的實例,因此調(diào)用完一個方法之后可以直接連綴調(diào)用它的另一個方法,這樣把所有的功能都串接起來,我們甚至可以僅通過一行代碼就完成任意復(fù)雜度的動畫功能。