1 背景
不能只分析源碼呀,分析的同時(shí)也要整理歸納基礎(chǔ)知識,剛好有人微博私信讓全面說說Android的動(dòng)畫,所以今天來一發(fā)Android應(yīng)用的各種Animation大集合。英文厲害的請直接移步參考Android Developer。
Android系統(tǒng)提供了很多豐富的API去實(shí)現(xiàn)UI的2D與3D動(dòng)畫,最主要的劃分可以分為如下幾類:
View Animation:視圖動(dòng)畫在古老的Android版本系統(tǒng)中就已經(jīng)提供了,只能被用來設(shè)置View的動(dòng)畫。
Drawable Animation:這種動(dòng)畫(也叫Frame動(dòng)畫、幀動(dòng)畫)其實(shí)可以劃分到視圖動(dòng)畫的類別,專門用來一個(gè)一個(gè)的顯示Drawable的resources,就像放幻燈片一樣。
Property Animation:屬性動(dòng)畫只對Android 3.0(API 11)以上版本的Android系統(tǒng)才有效,這種動(dòng)畫可以設(shè)置給任何Object,包括那些還沒有渲染到屏幕上的對象。這種動(dòng)畫是可擴(kuò)展的,可以讓你自定義任何類型和屬性的動(dòng)畫。
可以看見,當(dāng)前應(yīng)用程序開發(fā)涉及的主要?jiǎng)赢嬕簿瓦@三大類,我們接下來以類別為基礎(chǔ)來慢慢展開說明。
2 View Animation(視圖動(dòng)畫)使用詳解
視圖動(dòng)畫,也叫Tween(補(bǔ)間)動(dòng)畫可以在一個(gè)視圖容器內(nèi)執(zhí)行一系列簡單變換(位置、大小、旋轉(zhuǎn)、透明度)。譬如,如果你有一個(gè)TextView對象,您可以移動(dòng)、旋轉(zhuǎn)、縮放、透明度設(shè)置其文本,當(dāng)然,如果它有一個(gè)背景圖像,背景圖像會隨著文本變化。
補(bǔ)間動(dòng)畫通過XML或Android代碼定義,建議使用XML文件定義,因?yàn)樗呖勺x性、可重用性。
如下是視圖動(dòng)畫相關(guān)的類繼承關(guān)系:
java類名xml關(guān)鍵字描述信息
AlphaAnimation放置在res/anim/目錄下漸變透明度動(dòng)畫效果
RotateAnimation放置在res/anim/目錄下畫面轉(zhuǎn)移旋轉(zhuǎn)動(dòng)畫效果
ScaleAnimation放置在res/anim/目錄下漸變尺寸伸縮動(dòng)畫效果
TranslateAnimation放置在res/anim/目錄下畫面轉(zhuǎn)換位置移動(dòng)動(dòng)畫效果
AnimationSet放置在res/anim/目錄下一個(gè)持有其它動(dòng)畫元素alpha、scale、translate、rotate或者其它set元素的容器
通過上圖和上表可以直觀的看出來補(bǔ)間動(dòng)畫的關(guān)系及種類了吧,接下來我們就詳細(xì)一個(gè)一個(gè)的介紹一下各種補(bǔ)間動(dòng)畫。
可以看出來Animation抽象類是所有補(bǔ)間動(dòng)畫類的基類,所以基類會提供一些通用的動(dòng)畫屬性方法,如下我們就來詳細(xì)看看這些屬性,關(guān)于這些屬性詳細(xì)官方解釋FQ點(diǎn)擊我或者FQ點(diǎn)擊我。
xml屬性java方法解釋
android:detachWallpapersetDetachWallpaper(boolean)是否在壁紙上運(yùn)行
android:durationsetDuration(long)動(dòng)畫持續(xù)時(shí)間,毫秒為單位
android:fillAftersetFillAfter(boolean)控件動(dòng)畫結(jié)束時(shí)是否保持動(dòng)畫最后的狀態(tài)
android:fillBeforesetFillBefore(boolean)控件動(dòng)畫結(jié)束時(shí)是否還原到開始動(dòng)畫前的狀態(tài)
android:fillEnabledsetFillEnabled(boolean)與android:fillBefore效果相同
android:interpolatorsetInterpolator(Interpolator)設(shè)定插值器(指定的動(dòng)畫效果,譬如回彈等)
android:repeatCountsetRepeatCount(int)重復(fù)次數(shù)
android:repeatModesetRepeatMode(int)重復(fù)類型有兩個(gè)值,reverse表示倒序回放,restart表示從頭播放
android:startOffsetsetStartOffset(long)調(diào)用start函數(shù)之后等待開始運(yùn)行的時(shí)間,單位為毫秒
android:zAdjustmentsetZAdjustment(int)表示被設(shè)置動(dòng)畫的內(nèi)容運(yùn)行時(shí)在Z軸上的位置(top/bottom/normal),默認(rèn)為normal
也就是說,無論我們補(bǔ)間動(dòng)畫的哪一種都已經(jīng)具備了這種屬性,也都可以設(shè)置使用這些屬性中的一個(gè)或多個(gè)。
那接下來我們就看看每種補(bǔ)間動(dòng)畫特有的一些屬性說明吧。
xml屬性java方法解釋
android:fromAlphaAlphaAnimation(float fromAlpha, …)動(dòng)畫開始的透明度(0.0到1.0,0.0是全透明,1.0是不透明)
android:toAlphaAlphaAnimation(…, float toAlpha)動(dòng)畫結(jié)束的透明度,同上
xml屬性java方法解釋
android:fromDegreesRotateAnimation(float fromDegrees, …)旋轉(zhuǎn)開始角度,正代表順時(shí)針度數(shù),負(fù)代表逆時(shí)針度數(shù)
android:toDegreesRotateAnimation(…, float toDegrees, …)旋轉(zhuǎn)結(jié)束角度,正代表順時(shí)針度數(shù),負(fù)代表逆時(shí)針度數(shù)
android:pivotXRotateAnimation(…, float pivotX, …)縮放起點(diǎn)X坐標(biāo)(數(shù)值、百分?jǐn)?shù)、百分?jǐn)?shù)p,譬如50表示以當(dāng)前View左上角坐標(biāo)加50px為初始點(diǎn)、50%表示以當(dāng)前View的左上角加上當(dāng)前View寬高的50%做為初始點(diǎn)、50%p表示以當(dāng)前View的左上角加上父控件寬高的50%做為初始點(diǎn))
android:pivotYRotateAnimation(…, float pivotY)縮放起點(diǎn)Y坐標(biāo),同上規(guī)律
xml屬性java方法解釋
android:fromXScaleScaleAnimation(float fromX, …)初始X軸縮放比例,1.0表示無變化
android:toXScaleScaleAnimation(…, float toX, …)結(jié)束X軸縮放比例
android:fromYScaleScaleAnimation(…, float fromY, …)初始Y軸縮放比例
android:toYScaleScaleAnimation(…, float toY, …)結(jié)束Y軸縮放比例
android:pivotXScaleAnimation(…, float pivotX, …)縮放起點(diǎn)X軸坐標(biāo)(數(shù)值、百分?jǐn)?shù)、百分?jǐn)?shù)p,譬如50表示以當(dāng)前View左上角坐標(biāo)加50px為初始點(diǎn)、50%表示以當(dāng)前View的左上角加上當(dāng)前View寬高的50%做為初始點(diǎn)、50%p表示以當(dāng)前View的左上角加上父控件寬高的50%做為初始點(diǎn))
android:pivotYScaleAnimation(…, float pivotY)縮放起點(diǎn)Y軸坐標(biāo),同上規(guī)律
xml屬性java方法解釋
android:fromXDeltaTranslateAnimation(float fromXDelta, …)起始點(diǎn)X軸坐標(biāo)(數(shù)值、百分?jǐn)?shù)、百分?jǐn)?shù)p,譬如50表示以當(dāng)前View左上角坐標(biāo)加50px為初始點(diǎn)、50%表示以當(dāng)前View的左上角加上當(dāng)前View寬高的50%做為初始點(diǎn)、50%p表示以當(dāng)前View的左上角加上父控件寬高的50%做為初始點(diǎn))
android:fromYDeltaTranslateAnimation(…, float fromYDelta, …)起始點(diǎn)Y軸從標(biāo),同上規(guī)律
android:toXDeltaTranslateAnimation(…, float toXDelta, …)結(jié)束點(diǎn)X軸坐標(biāo),同上規(guī)律
android:toYDeltaTranslateAnimation(…, float toYDelta)結(jié)束點(diǎn)Y軸坐標(biāo),同上規(guī)律
AnimationSet繼承自Animation,是上面四種的組合容器管理類,沒有自己特有的屬性,他的屬性繼承自Animation,所以特別注意,當(dāng)我們對set標(biāo)簽使用Animation的屬性時(shí)會對該標(biāo)簽下的所有子控件都產(chǎn)生影響。
通過上面對于動(dòng)畫的屬性介紹之后我們來看看在Android中這些動(dòng)畫如何使用(PS:這里直接演示xml方式,至于Java方式太簡單了就不說了),如下:
? ? ? ? ...
ImageView spaceshipImage = (ImageView) findViewById(R.id.spaceshipImage);Animation hyperspaceJumpAnimation = AnimationUtils.loadAnimation(this, R.anim.hyperspace_jump);
spaceshipImage.startAnimation(hyperspaceJumpAnimation);
上面就是一個(gè)標(biāo)準(zhǔn)的使用我們定義的補(bǔ)間動(dòng)畫的模板。至于補(bǔ)間動(dòng)畫的使用,Animation還有如下一些比較實(shí)用的方法介紹:
Animation類的方法解釋
reset()重置Animation的初始化
cancel()取消Animation動(dòng)畫
start()開始Animation動(dòng)畫
setAnimationListener(AnimationListener listener)給當(dāng)前Animation設(shè)置動(dòng)畫監(jiān)聽
hasStarted()判斷當(dāng)前Animation是否開始
hasEnded()判斷當(dāng)前Animation是否結(jié)束
既然補(bǔ)間動(dòng)畫只能給View使用,那就來看看View中和動(dòng)畫相關(guān)的幾個(gè)常用方法吧,如下:
View類的常用動(dòng)畫操作方法解釋
startAnimation(Animation animation)對當(dāng)前View開始設(shè)置的Animation動(dòng)畫
clearAnimation()取消當(dāng)View在執(zhí)行的Animation動(dòng)畫
到此整個(gè)Android的補(bǔ)間動(dòng)畫常用詳細(xì)屬性及方法全部介紹完畢,如有特殊的屬性需求可以訪問Android Developer查閱即可。如下我們就來個(gè)綜合大演練。
關(guān)于視圖動(dòng)畫(補(bǔ)間動(dòng)畫)的例子我就不介紹了,網(wǎng)上簡直多的都泛濫了。只是強(qiáng)調(diào)在使用補(bǔ)間動(dòng)畫時(shí)注意如下一點(diǎn)即可:
特別特別注意:補(bǔ)間動(dòng)畫執(zhí)行之后并未改變View的真實(shí)布局屬性值。切記這一點(diǎn),譬如我們在Activity中有一個(gè) Button在屏幕上方,我們設(shè)置了平移動(dòng)畫移動(dòng)到屏幕下方然后保持動(dòng)畫最后執(zhí)行狀態(tài)呆在屏幕下方,這時(shí)如果點(diǎn)擊屏幕下方動(dòng)畫執(zhí)行之后的Button是沒 有任何反應(yīng)的,而點(diǎn)擊原來屏幕上方?jīng)]有Button的地方卻響應(yīng)的是點(diǎn)擊Button的事件。
2-5 視圖動(dòng)畫Interpolator插值器詳解
介紹補(bǔ)間動(dòng)畫插值器之前我們先來看一幅圖,如下:
可以看見其實(shí)各種插值器都是實(shí)現(xiàn)了Interpolator接口而已,同時(shí)可以看見系統(tǒng)提供了許多已經(jīng)實(shí)現(xiàn)OK的插值器,具體如下:
java類xml id值描述
AccelerateDecelerateInterpolator@android:anim/accelerate_decelerate_interpolator動(dòng)畫始末速率較慢,中間加速
AccelerateInterpolator@android:anim/accelerate_interpolator動(dòng)畫開始速率較慢,之后慢慢加速
AnticipateInterpolator@android:anim/anticipate_interpolator開始的時(shí)候從后向前甩
AnticipateOvershootInterpolator@android:anim/anticipate_overshoot_interpolator類似上面AnticipateInterpolator
BounceInterpolator@android:anim/bounce_interpolator動(dòng)畫結(jié)束時(shí)彈起
CycleInterpolator@android:anim/cycle_interpolator循環(huán)播放速率改變?yōu)檎仪€
DecelerateInterpolator@android:anim/decelerate_interpolator動(dòng)畫開始快然后慢
LinearInterpolator@android:anim/linear_interpolator動(dòng)畫勻速改變
OvershootInterpolator@android:anim/overshoot_interpolator向前彈出一定值之后回到原來位置
PathInterpolator新增,定義路徑坐標(biāo)后按照路徑坐標(biāo)來跑。
如上就是系統(tǒng)提供的一些插值器,下面我們來看看怎么使用他們。
插值器的使用比較簡答,如下:
? ? ...
有時(shí)候你會發(fā)現(xiàn)系統(tǒng)提供的插值器不夠用,可能就像View一樣需要自定義。所以接下來我們來看看插值器的自定義,關(guān)于插值器的自定義分為兩種實(shí)現(xiàn)方式,xml自定義實(shí)現(xiàn)(其實(shí)就是對現(xiàn)有的插值器的一些屬性修改)或者java代碼實(shí)現(xiàn)方式。如下我們來說說。
先看看XML自定義插值器的步驟:
在res/anim/目錄下創(chuàng)建filename.xml文件。
修改你準(zhǔn)備自定義的插值器如下:
/>
在你的補(bǔ)間動(dòng)畫文件中引用該文件即可。
可以看見上面第二步修改的是現(xiàn)有插值器的一些屬性,但是有些插值器卻不具備修改屬性,具體如下:
無可自定義的attribute。
android:factor 浮點(diǎn)值,加速速率(默認(rèn)值為1)。
android:tension 浮點(diǎn)值,起始點(diǎn)后拉的張力數(shù)(默認(rèn)值為2)。
android:tension 浮點(diǎn)值,起始點(diǎn)后拉的張力數(shù)(默認(rèn)值為2)。
android:extraTension 浮點(diǎn)值,拉力的倍數(shù)(默認(rèn)值為1.5)。
無可自定義的attribute。
android:cycles 整形,循環(huán)的個(gè)數(shù)(默認(rèn)為1)。
android:factor 浮點(diǎn)值,減速的速率(默認(rèn)為1)。
無可自定義的attribute。
android:tension 浮點(diǎn)值,超出終點(diǎn)后的張力(默認(rèn)為2)。
再來看看Java自定義插值器的(Java自定義插值器其實(shí)是xml自定義的升級,也就是說如果我們修改xml的屬性還不能滿足需求,那就可以選擇通過Java來實(shí)現(xiàn))方式。
可以看見上面所有的Interpolator都實(shí)現(xiàn)了Interpolator接口,而Interpolator接口又繼承自 TimeInterpolator,TimeInterpolator接口定義了一個(gè)float getInterpolation(float input);方法,這個(gè)方法是由系統(tǒng)調(diào)用的,其中的參數(shù)input代表動(dòng)畫的時(shí)間,在0和1之間,也就是開始和結(jié)束之間。
如下就是一個(gè)動(dòng)畫始末速率較慢、中間加速的AccelerateDecelerateInterpolator插值器:
publicclassAccelerateDecelerateInterpolatorextendsBaseInterpolatorimplementsNativeInterpolatorFactory {? ? ......publicfloatgetInterpolation(float input) {return (float)(Math.cos((input +1) * Math.PI) /2.0f) +0.5f;
}
......
}
到此整個(gè)補(bǔ)間動(dòng)畫與補(bǔ)間動(dòng)畫的插值器都分析完畢了,接下來看下別的動(dòng)畫。
3 Drawable Animation(Drawable動(dòng)畫)使用詳解
Drawable動(dòng)畫其實(shí)就是Frame動(dòng)畫(幀動(dòng)畫),它允許你實(shí)現(xiàn)像播放幻燈片一樣的效果,這種動(dòng)畫的實(shí)質(zhì)其實(shí)是Drawable,所以這種動(dòng)畫的XML定義方式文件一般放在res/drawable/目錄下。具體關(guān)于幀動(dòng)畫的xml使用方式FQ點(diǎn)擊我查看,java方式FQ點(diǎn)擊我查看。
如下圖就是幀動(dòng)畫的源碼文件:
可以看見實(shí)際的真實(shí)父類就是Drawable。
我們依舊可以使用xml或者java方式實(shí)現(xiàn)幀動(dòng)畫。但是依舊推薦使用xml,具體如下:
必須是根節(jié)點(diǎn),包含一個(gè)或者多個(gè)元素,屬性有:
android:oneshottrue代表只執(zhí)行一次,false循環(huán)執(zhí)行。
類似一幀的動(dòng)畫資源。
animation-list的子項(xiàng),包含屬性如下:
android:drawable一個(gè)frame的Drawable資源。
android:duration一個(gè)frame顯示多長時(shí)間。
關(guān)于幀動(dòng)畫相對來說比較簡單,這里給出一個(gè)常規(guī)使用框架,如下:
ImageView rocketImage = (ImageView) findViewById(R.id.rocket_image);
rocketImage.setBackgroundResource(R.drawable.rocket_thrust);
rocketAnimation = (AnimationDrawable) rocketImage.getBackground();
rocketAnimation.start();
特別注意,AnimationDrawable的start()方法不能在Activity的onCreate方法中調(diào)運(yùn),因?yàn)锳nimationDrawable還未完全附著到window上,所以最好的調(diào)運(yùn)時(shí)機(jī)是onWindowFocusChanged()方法中。
至此幀動(dòng)畫也就說明完成了。讓我們接下來進(jìn)入Android更牛叉的動(dòng)畫類型。
4 Property Animation(屬性動(dòng)畫)使用詳解
在使用屬性動(dòng)畫之前先來看幾個(gè)常用的View屬性成員:
translationX,translationY:控制View的位置,值是相對于View容器左上角坐標(biāo)的偏移。
rotationX,rotationY:控制相對于軸心旋轉(zhuǎn)。
x,y:控制View在容器中的位置,即左上角坐標(biāo)加上translationX和translationY的值。
alpha:控制View對象的alpha透明度值。
這幾個(gè)常用的屬性相信大家都很熟悉,接下來的屬性動(dòng)畫我們就從這里展開。
Android 3.0以后引入了屬性動(dòng)畫,屬性動(dòng)畫可以輕而易舉的實(shí)現(xiàn)許多View動(dòng)畫做不到的事,上面也看見了,View動(dòng)畫無非也就做那幾種事情,別的也搞不定,而 屬性動(dòng)畫就可以的,譬如3D旋轉(zhuǎn)一張圖片。其實(shí)說白了,你記住一點(diǎn)就行,屬性動(dòng)畫實(shí)現(xiàn)原理就是修改控件的屬性值實(shí)現(xiàn)的動(dòng)畫。
具體先看下類關(guān)系:
/** * This is the superclass for classes which provide basic support for animations which can be * started, ended, and have AnimatorListeners
added to them. */publicabstractclassAnimatorimplementsCloneable {
......
}
所有的屬性動(dòng)畫的抽象基類就是他。我們看下他的實(shí)現(xiàn)子類:
其實(shí)可以看見,屬性動(dòng)畫的實(shí)現(xiàn)有7個(gè)類(PS,之所以類繼承關(guān)系列表會出來那么多是因?yàn)槲蚁螺d了所有版本的SDK,你只用關(guān)注我紅點(diǎn)標(biāo)注的就行,妹 的,ubuntu下圖片處理工具怎么都這么難用),進(jìn)去粗略分析可以發(fā)現(xiàn),好幾個(gè)是hide的類,而其他可用的類繼承關(guān)系又如下:
java類名xml關(guān)鍵字描述信息
ValueAnimator放置在res/animator/目錄下在一個(gè)特定的時(shí)間里執(zhí)行一個(gè)動(dòng)畫
TimeAnimator不支持/點(diǎn)我查看原因時(shí)序監(jiān)聽回調(diào)工具
ObjectAnimator放置在res/animator/目錄下一個(gè)對象的一個(gè)屬性動(dòng)畫
AnimatorSet放置在res/animator/目錄下動(dòng)畫集合
所以可以看見,我們平時(shí)使用屬性動(dòng)畫的重點(diǎn)就在于AnimatorSet、ObjectAnimator、TimeAnimator、ValueAnimator。所以接下來我們就來依次說說如何使用。
參看Android官方文檔,英文原版詳情點(diǎn)我查看!
Android屬性動(dòng)畫(注意最低兼容版本,不過可以使用開源項(xiàng)目來替代低版本問題)提供了以下屬性:
Duration:動(dòng)畫的持續(xù)時(shí)間;
TimeInterpolation:定義動(dòng)畫變化速率的接口,所有插值器都必須實(shí)現(xiàn)此接口,如線性、非線性插值器;
TypeEvaluator:用于定義屬性值計(jì)算方式的接口,有int、float、color類型,根據(jù)屬性的起始、結(jié)束值和插值一起計(jì)算出當(dāng)前時(shí)間的屬性值;
Animation sets:動(dòng)畫集合,即可以同時(shí)對一個(gè)對象應(yīng)用多個(gè)動(dòng)畫,這些動(dòng)畫可以同時(shí)播放也可以對不同動(dòng)畫設(shè)置不同的延遲;
Frame refreash delay:多少時(shí)間刷新一次,即每隔多少時(shí)間計(jì)算一次屬性值,默認(rèn)為10ms,最終刷新時(shí)間還受系統(tǒng)進(jìn)程調(diào)度與硬件的影響;
Repeat Country and behavoir:重復(fù)次數(shù)與方式,如播放3次、5次、無限循環(huán),可以讓此動(dòng)畫一直重復(fù),或播放完時(shí)向反向播放;
接下來先來看官方為了解釋原理給出的兩幅圖(其實(shí)就是初中物理題,不解釋):
上面就是一個(gè)線性勻速動(dòng)畫,描述了一個(gè)Object的X屬性運(yùn)動(dòng)動(dòng)畫,該對象的X坐標(biāo)在40ms內(nèi)從0移動(dòng)到40,每10ms刷新一次,移動(dòng)4次,每次移動(dòng)為40/4=10pixel。
上面是一個(gè)非勻速動(dòng)畫,描述了一個(gè)Object的X屬性運(yùn)動(dòng)動(dòng)畫,該對象的X坐標(biāo)在40ms內(nèi)從0移動(dòng)到40,每10ms刷新一次,移動(dòng)4次,但是速率不同,開始和結(jié)束的速度要比中間部分慢,即先加速后減速。
接下來我們來詳細(xì)的看一下,屬性動(dòng)畫系統(tǒng)的重要組成部分是如何計(jì)算動(dòng)畫值的,下圖描述了如上面所示動(dòng)畫的實(shí)現(xiàn)作用過程。
其中的ValueAnimator是動(dòng)畫的執(zhí)行類,跟蹤了當(dāng)前動(dòng)畫的執(zhí)行時(shí)間和當(dāng)前時(shí)間下的屬性值;ValueAnimator封裝了動(dòng)畫的 TimeInterpolator時(shí)間插值器和一個(gè)TypeEvaluator類型估值,用于設(shè)置動(dòng)畫屬性的值,就像上面圖2非線性動(dòng)畫 里,TimeInterpolator使用了AccelerateDecelerateInterpolator、TypeEvaluator使用了 IntEvaluator。
為了執(zhí)行一個(gè)動(dòng)畫,你需要?jiǎng)?chuàng)建一個(gè)ValueAnimator,并且指定目標(biāo)對象屬性的開始、結(jié)束值和持續(xù)時(shí)間。在調(diào)用start后,整個(gè)動(dòng)畫過程 中, ValueAnimator會根據(jù)已經(jīng)完成的動(dòng)畫時(shí)間計(jì)算得到一個(gè)0到1之間的分?jǐn)?shù),代表該動(dòng)畫的已完成動(dòng)畫百分比。0表示0%,1表示100%,譬如上 面圖一線性勻速動(dòng)畫中總時(shí)間 t = 40 ms,t = 10 ms的時(shí)候是 0.25。
當(dāng)ValueAnimator計(jì)算完已完成動(dòng)畫分?jǐn)?shù)后,它會調(diào)用當(dāng)前設(shè)置的TimeInterpolator,去計(jì)算得到一個(gè) interpolated(插值)分?jǐn)?shù),在計(jì)算過程中,已完成動(dòng)畫百分比會被加入到新的插值計(jì)算中。如上圖2非線性動(dòng)畫中,因?yàn)閯?dòng)畫的運(yùn)動(dòng)是緩慢加速的, 它的插值分?jǐn)?shù)大約是 0.15,小于t = 10ms時(shí)的已完成動(dòng)畫分?jǐn)?shù)0.25。而在上圖1中,這個(gè)插值分?jǐn)?shù)一直和已完成動(dòng)畫分?jǐn)?shù)是相同的。
當(dāng)插值分?jǐn)?shù)計(jì)算完成后,ValueAnimator會根據(jù)插值分?jǐn)?shù)調(diào)用合適的 TypeEvaluator去計(jì)算運(yùn)動(dòng)中的屬性值。
好了,現(xiàn)在我們來看下代碼就明白這段話了,上面圖2非線性動(dòng)畫里,TimeInterpolator使用了 AccelerateDecelerateInterpolator、TypeEvaluator使用了IntEvaluator。所以這些類都是標(biāo)準(zhǔn)的 API,我們來看下標(biāo)準(zhǔn)API就能類比自己寫了,如下:
首先計(jì)算已完成動(dòng)畫時(shí)間分?jǐn)?shù)(以10ms為例):t=10ms/40ms=0.25。
接著看如下源碼如何實(shí)現(xiàn)計(jì)算差值分?jǐn)?shù)的:
publicclassAccelerateDecelerateInterpolatorextendsBaseInterpolatorimplementsNativeInterpolatorFactory {publicAccelerateDecelerateInterpolator() {? ? }? ? ......//這是我們關(guān)注重點(diǎn),可以發(fā)現(xiàn)如下計(jì)算公式計(jì)算后(input即為時(shí)間因子)插值大約為0.15。publicfloatgetInterpolation(float input) {return (float)(Math.cos((input +1) * Math.PI) /2.0f) +0.5f;
}
......
}
其實(shí)AccelerateDecelerateInterpolator的基類接口就是TimeInterpolator,如下,他只有g(shù)etInterpolation方法,也就是上面我們關(guān)注的方法。
publicinterfaceTimeInterpolator {float getInterpolation(float input);
}
接著ValueAnimator會根據(jù)插值分?jǐn)?shù)調(diào)用合適的TypeEvaluator(IntEvaluator)去計(jì)算運(yùn)動(dòng)中的屬性值,如下,因?yàn)閟tartValue = 0,所以屬性值:0+0.15*(40-0)= 6。
publicclassIntEvaluatorimplementsTypeEvaluator {public Integerevaluate(float fraction, Integer startValue, Integer endValue) {int startInt = startValue;return (int)(startInt + fraction * (endValue - startInt));
}
}
這就是官方給的一個(gè)關(guān)于屬性動(dòng)畫實(shí)現(xiàn)的過程及基本原理解釋,相信你看到這里是會有些迷糊的,沒關(guān)系,你先有個(gè)大致概念就行,接下來我們會慢慢進(jìn)入實(shí)戰(zhàn),因?yàn)锳ndroid的屬性動(dòng)畫相對于其他動(dòng)畫來說涉及的知識點(diǎn)本來就比較復(fù)雜,所以我們慢慢來。
在xml中可直接用的屬性動(dòng)畫節(jié)點(diǎn)有ValueAnimator、ObjectAnimator、AnimatorSet。如下是官方的一個(gè)例子和解釋(詳情點(diǎn)我):
? ? ? ? ...
屬性解釋:
xml屬性解釋
android:ordering控制子動(dòng)畫啟動(dòng)方式是先后有序的還是同時(shí)進(jìn)行。sequentially:動(dòng)畫按照先后順序;together(默認(rèn)):動(dòng)畫同時(shí)啟動(dòng);
屬性解釋:
xml屬性解釋
android:propertyNameString類型,必須要設(shè)置的節(jié)點(diǎn)屬性,代表要執(zhí)行動(dòng)畫的屬性(通過名字引用),辟如你可以指定了一個(gè)View的”alpha” 或者 “backgroundColor” ,這個(gè)objectAnimator元素沒有對外說明target屬性,所以你不能在XML中設(shè)置執(zhí)行這個(gè)動(dòng)畫,必須通過調(diào)用 loadAnimator()方法加載你的XML動(dòng)畫資源,然后調(diào)用setTarget()應(yīng)用到具備這個(gè)屬性的目標(biāo)對象上(譬如TextView)。
android:valueTofloat、int或者color類型,必須要設(shè)置的節(jié)點(diǎn)屬性,表明動(dòng)畫結(jié)束的點(diǎn);如果是顏色的話,由6位十六進(jìn)制的數(shù)字表示。
android:valueFrom相對應(yīng)valueTo,動(dòng)畫的起始點(diǎn),如果沒有指定,系統(tǒng)會通過屬性的get方法獲取,顏色也是6位十六進(jìn)制的數(shù)字表示。
android:duration動(dòng)畫的時(shí)長,int類型,以毫秒為單位,默認(rèn)為300毫秒。
android:startOffset動(dòng)畫延遲的時(shí)間,從調(diào)用start方法后開始計(jì)算,int型,毫秒為單位。
android:repeatCount一個(gè)動(dòng)畫的重復(fù)次數(shù),int型,”-1“表示無限循環(huán),”1“表示動(dòng)畫在第一次執(zhí)行完成后重復(fù)執(zhí)行一次,也就是兩次,默認(rèn)為0,不重復(fù)執(zhí)行。
android:repeatMode重復(fù)模式:int型,當(dāng)一個(gè)動(dòng)畫執(zhí)行完的時(shí)候應(yīng)該如何處理。該值必須是正數(shù)或者是-1,“reverse”會使得按照動(dòng)畫向相反的方向執(zhí)行,可實(shí)現(xiàn)類似鐘擺效果。“repeat”會使得動(dòng)畫每次都從頭開始循環(huán)。
android:valueType關(guān)鍵參數(shù),如果該value是一個(gè)顏色,那么就不需要指定,因?yàn)閯?dòng)畫框架會自動(dòng)的處理顏色值。有intType和floatType(默認(rèn))兩種:分別說明動(dòng)畫值為int和float型。
屬性解釋:
同上屬性,不多介紹。
XML屬性動(dòng)畫使用方法:
AnimatorSetset = (AnimatorSet) AnimatorInflater.loadAnimator(myContext,? ? R.animtor.property_animator);set.setTarget(myObject);set.start();
1、ObjectAnimator:繼承自ValueAnimator,允許你指定要進(jìn)行動(dòng)畫的對象以及該對象 的一個(gè)屬性。該類會根據(jù)計(jì)算得到的新值自動(dòng)更新屬性。大多數(shù)的情況使用ObjectAnimator就足夠了,因?yàn)樗沟媚繕?biāo)對象動(dòng)畫值的處理過程變得足 夠簡單,不用像ValueAnimator那樣自己寫動(dòng)畫更新的邏輯,但是ObjectAnimator有一定的限制,比如它需要目標(biāo)對象的屬性提供指定 的處理方法(譬如提供getXXX,setXXX方法),這時(shí)候你就需要根據(jù)自己的需求在ObjectAnimator和ValueAnimator中看 哪種實(shí)現(xiàn)更方便了。
ObjectAnimator類提供了ofInt、ofFloat、ofObject這個(gè)三個(gè)常用的方法,這些方法都是設(shè)置動(dòng)畫作用的元素、屬性、開始、結(jié)束等任意屬性值。當(dāng)屬性值(上面方法的參數(shù))只設(shè)置一個(gè)時(shí)就把通過getXXX反射獲取的值作為起點(diǎn),設(shè)置的值作為終點(diǎn);如果設(shè)置兩個(gè)(參數(shù)),那么一個(gè)是開始、另一個(gè)是結(jié)束。
特別注意:ObjectAnimator的動(dòng)畫原理是不停的調(diào)用setXXX方法更新屬性值,所有使用ObjectAnimator更新屬性時(shí)的前提是Object必須聲明有g(shù)etXXX和setXXX方法。
我們通常使用ObjectAnimator設(shè)置View已知的屬性來生成動(dòng)畫,而一般View已知屬性變化時(shí)都會主動(dòng)觸發(fā)重繪圖操作,所以動(dòng)畫會自 動(dòng)實(shí)現(xiàn);但是也有特殊情況,譬如作用Object不是View,或者作用的屬性沒有觸發(fā)重繪,或者我們在重繪時(shí)需要做自己的操作,那都可以通過如下方法手 動(dòng)設(shè)置:
ObjectAnimator mObjectAnimator= ObjectAnimator.ofInt(view,"customerDefineAnyThingName",0,1).setDuration(2000);mObjectAnimator.addUpdateListener(new AnimatorUpdateListener()? ? ? ? {@OverridepublicvoidonAnimationUpdate(ValueAnimator animation)? ? ? ? ? ? {//int value = animation.getAnimatedValue();? 可以獲取當(dāng)前屬性值//view.postInvalidate();? 可以主動(dòng)刷新//view.setXXX(value);//view.setXXX(value);//......可以批量修改屬性
}
});
如下是一個(gè)我在項(xiàng)目中的Y軸3D旋轉(zhuǎn)動(dòng)畫實(shí)現(xiàn)實(shí)例:
ObjectAnimator.ofFloat(view,"rotationY",0.0f,360.0f).setDuration(1000).start();
2、PropertyValuesHolder:多屬性動(dòng)畫同時(shí)工作管理類。有時(shí)候我們需要同時(shí)修改多個(gè)屬性,那就可以用到此類,具體如下:
PropertyValuesHolder a1 = PropertyValuesHolder.ofFloat("alpha",0f,1f);? PropertyValuesHolder a2 = PropertyValuesHolder.ofFloat("translationY",0, viewWidth);? ......ObjectAnimator.ofPropertyValuesHolder(view, a1, a2, ......).setDuration(1000).start();
如上代碼就可以實(shí)現(xiàn)同時(shí)修改多個(gè)屬性的動(dòng)畫啦。
3、ValueAnimator:屬性動(dòng)畫中的時(shí)間驅(qū)動(dòng),管理著動(dòng)畫時(shí)間的開始、結(jié)束屬性值,相應(yīng)時(shí)間屬性值計(jì)算方法等。包含所有計(jì)算動(dòng)畫值的核心函數(shù)以及每一個(gè)動(dòng)畫時(shí)間節(jié)點(diǎn)上的信息、一個(gè)動(dòng)畫是否重復(fù)、是否監(jiān)聽更新事件等,并且還可以設(shè)置自定義的計(jì)算類型。
特別注意:ValueAnimator只是動(dòng)畫計(jì)算管理驅(qū)動(dòng),設(shè)置了作用目標(biāo),但沒有設(shè)置屬性,需要通過updateListener里設(shè)置屬性才會生效。
ValueAnimator animator = ValueAnimator.ofFloat(0, mContentHeight);//定義動(dòng)畫animator.setTarget(view);//設(shè)置作用目標(biāo)animator.setDuration(5000).start();animator.addUpdateListener(new AnimatorUpdateListener() {@OverridepublicvoidonAnimationUpdate(ValueAnimator animation){float value = (float) animation.getAnimatedValue();? ? ? ? view.setXXX(value);//必須通過這里設(shè)置屬性值才有效? ? ? ? view.mXXX = value;//不需要setXXX屬性方法
}
});
大眼看上去可以發(fā)現(xiàn)和ObjectAnimator沒啥區(qū)別,實(shí)際上正是由于ValueAnimator不直接操作屬性值,所以要操作對象的屬性可以不需要setXXX與getXXX方法,你完全可以通過當(dāng)前動(dòng)畫的計(jì)算去修改任何屬性。
4、AnimationSet:動(dòng)畫集合,提供把多個(gè)動(dòng)畫組合成一個(gè)組合的機(jī)制,并可設(shè)置動(dòng)畫的時(shí)序關(guān)系,如同時(shí)播放、順序播放或延遲播放。具體使用方法比較簡單,如下:
ObjectAnimator a1 = ObjectAnimator.ofFloat(view,"alpha",1.0f,0f);? ObjectAnimator a2 = ObjectAnimator.ofFloat(view,"translationY",0f, viewWidth);? ......AnimatorSet animSet =new AnimatorSet();? animSet.setDuration(5000);? animSet.setInterpolator(new LinearInterpolator());//animSet.playTogether(a1, a2, ...); //兩個(gè)動(dòng)畫同時(shí)執(zhí)行? animSet.play(a1).after(a2);//先后執(zhí)行......//其他組合方式
animSet.start();
5、Evaluators相關(guān)類解釋:Evaluators就是屬性動(dòng)畫系統(tǒng)如何去計(jì)算一個(gè)屬性值。它們通過Animator提供的動(dòng)畫的起始和結(jié)束值去計(jì)算一個(gè)動(dòng)畫的屬性值。
IntEvaluator:整數(shù)屬性值。
FloatEvaluator:浮點(diǎn)數(shù)屬性值。
ArgbEvaluator:十六進(jìn)制color屬性值。
TypeEvaluator:用戶自定義屬性值接口,譬如對象屬性值類型不是int、float、color類型,你必須實(shí)現(xiàn)這個(gè)接口去定義自己的數(shù)據(jù)類型。
既然說到這了,那就來個(gè)例子吧,譬如我們需要實(shí)現(xiàn)一個(gè)自定義屬性類型和計(jì)算規(guī)則的屬性動(dòng)畫,如下類型float[]:
ValueAnimator valueAnimator =new ValueAnimator();valueAnimator.setDuration(5000);valueAnimator.setObjectValues(newfloat[2]);//設(shè)置屬性值類型valueAnimator.setInterpolator(new LinearInterpolator());valueAnimator.setEvaluator(new TypeEvaluator(){@Overridepublicfloat[]evaluate(float fraction,float[] startValue,float[] endValue)? ? {//實(shí)現(xiàn)自定義規(guī)則計(jì)算的float[]類型的屬性值float[] temp =newfloat[2];? ? ? ? temp[0] = fraction *2;? ? ? ? temp[1] = (float)Math.random() *10 * fraction;return temp;? ? }});valueAnimator.start();valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener(){@OverridepublicvoidonAnimationUpdate(ValueAnimator animation)? ? {float[] xyPos = (float[]) animation.getAnimatedValue();? ? ? ? view.setHeight(xyPos[0]);//通過屬性值設(shè)置View屬性動(dòng)畫? ? ? ? view.setWidth(xyPos[1]);//通過屬性值設(shè)置View屬性動(dòng)畫
}
});
6、Interpolators相關(guān)類解釋:
AccelerateDecelerateInterolator:先加速后減速。
AccelerateInterpolator:加速。
DecelerateInterpolator:減速。
AnticipateInterpolator:先向相反方向改變一段再加速播放。
AnticipateOvershootInterpolator:先向相反方向改變,再加速播放,會超出目標(biāo)值然后緩慢移動(dòng)至目標(biāo)值,類似于彈簧回彈。
BounceInterpolator:快到目標(biāo)值時(shí)值會跳躍。
CycleIinterpolator:動(dòng)畫循環(huán)一定次數(shù),值的改變?yōu)橐徽液瘮?shù):Math.sin(2 * mCycles * Math.PI * input)。
LinearInterpolator:線性均勻改變。
OvershottInterpolator:最后超出目標(biāo)值然后緩慢改變到目標(biāo)值。
TimeInterpolator:一個(gè)允許自定義Interpolator的接口,以上都實(shí)現(xiàn)了該接口。
舉個(gè)例子,就像系統(tǒng)提供的標(biāo)準(zhǔn)API一樣,如下就是加速插值器的實(shí)現(xiàn)代碼,我們自定義時(shí)也可以類似實(shí)現(xiàn):
//開始很慢然后不斷加速的插值器。publicclassAccelerateInterpolatorimplementsInterpolator {privatefinalfloat mFactor;privatefinaldouble mDoubleFactor;publicAccelerateInterpolator() {? ? ? ? mFactor =1.0f;? ? ? ? mDoubleFactor =2.0;? ? }? ? ......//input? 0到1.0。表示動(dòng)畫當(dāng)前點(diǎn)的值,0表示開頭,1表示結(jié)尾。//return? 插值。值可以大于1超出目標(biāo)值,也可以小于0突破低值。@OverridepublicfloatgetInterpolation(float input) {//實(shí)現(xiàn)核心代碼塊if (mFactor ==1.0f) {return input * input;? ? ? ? }else {return (float)Math.pow(input, mDoubleFactor);
}
}
}
綜上可以發(fā)現(xiàn),我們可以使用現(xiàn)有系統(tǒng)提供標(biāo)準(zhǔn)的東東實(shí)現(xiàn)屬性動(dòng)畫,也可以通過自定義繼承相關(guān)接口實(shí)現(xiàn)自己的動(dòng)畫,只要實(shí)現(xiàn)上面提到的那些主要方法即可。
4-2-4 Java屬性動(dòng)畫拓展之ViewPropertyAnimator動(dòng)畫
在Android API 12時(shí),View中添加了animate方法,具體如下:
publicclassViewimplementsDrawable.Callback,KeyEvent.Callback,AccessibilityEventSource {? ? ....../**? ? * This method returns a ViewPropertyAnimator object, which can be used to animate? ? * specific properties on this View.? ? *? ? *@return ViewPropertyAnimator The ViewPropertyAnimator associated with this View.? ? */public ViewPropertyAnimatoranimate() {if (mAnimator ==null) {? ? ? ? ? ? mAnimator =new ViewPropertyAnimator(this);? ? ? ? }return mAnimator;
}
......
}
可以看見通過View的animate()方法可以得到一個(gè)ViewPropertyAnimator的屬性動(dòng)畫(有人說他沒有繼承Animator類,是的,他是成員關(guān)系,不是之前那種繼承關(guān)系)。
ViewPropertyAnimator提供了一種非常方便的方法為View的部分屬性設(shè)置動(dòng)畫(切記,是部分屬性),它可以直接使用一個(gè) Animator對象設(shè)置多個(gè)屬性的動(dòng)畫;在多屬性設(shè)置動(dòng)畫時(shí),它比 上面的ObjectAnimator更加牛逼、高效,因?yàn)樗麜芾矶鄠€(gè)屬性的invalidate方法統(tǒng)一調(diào)運(yùn)觸發(fā),而不像上面分別調(diào)用,所以還會有一些 性能優(yōu)化。如下就是一個(gè)例子:
myView.animate().x(0f).y(100f).start();
4-2-5 Java屬性動(dòng)畫拓展之LayoutAnimator容器布局動(dòng)畫
Property動(dòng)畫系統(tǒng)還提供了對ViewGroup中View添加時(shí)的動(dòng)畫功能,我們可以用LayoutTransition對 ViewGroup中的View進(jìn)行動(dòng)畫設(shè)置顯示。LayoutTransition的動(dòng)畫效果都是設(shè)置給ViewGroup,然后當(dāng)被設(shè)置動(dòng)畫的 ViewGroup中添加刪除View時(shí)體現(xiàn)出來。該類用于當(dāng)前布局容器中有View添加、刪除、隱藏、顯示等時(shí)候定義布局容器自身的動(dòng)畫和View的動(dòng) 畫,也就是說當(dāng)在一個(gè)LinerLayout中隱藏一個(gè)View的時(shí)候,我們可以自定義 整個(gè)由于LinerLayout隱藏View而改變的動(dòng)畫,同時(shí)還可以自定義被隱藏的View自己消失時(shí)候的動(dòng)畫等。
我們可以發(fā)現(xiàn)LayoutTransition類中主要有五種容器轉(zhuǎn)換動(dòng)畫類型,具體如下:
LayoutTransition.APPEARING:當(dāng)View出現(xiàn)或者添加的時(shí)候View出現(xiàn)的動(dòng)畫。
LayoutTransition.CHANGE_APPEARING:當(dāng)添加View導(dǎo)致布局容器改變的時(shí)候整個(gè)布局容器的動(dòng)畫。
LayoutTransition.DISAPPEARING:當(dāng)View消失或者隱藏的時(shí)候View消失的動(dòng)畫。
LayoutTransition.CHANGE_DISAPPEARING:當(dāng)刪除或者隱藏View導(dǎo)致布局容器改變的時(shí)候整個(gè)布局容器的動(dòng)畫。
LayoutTransition.CHANGE:當(dāng)不是由于View出現(xiàn)或消失造成對其他View位置造成改變的時(shí)候整個(gè)布局容器的動(dòng)畫。
XML方式使用系統(tǒng)提供的默認(rèn)LayoutTransition動(dòng)畫:
我們可以通過如下方式使用系統(tǒng)提供的默認(rèn)ViewGroup的LayoutTransition動(dòng)畫:
android:animateLayoutChanges=”true”
在ViewGroup添加如上xml屬性默認(rèn)是沒有任何動(dòng)畫效果的,因?yàn)榍懊嬲f了,該動(dòng)畫針對于ViewGroup內(nèi)部東東發(fā)生改變時(shí)才有效,所以當(dāng)我們設(shè)置如上屬性然后調(diào)運(yùn)ViewGroup的addView、removeView方法時(shí)就能看見系統(tǒng)默認(rèn)的動(dòng)畫效果了。
還有一種就是通過如下方式設(shè)置:
android:layoutAnimation=”@anim/customer_anim”
通過這種方式就能實(shí)現(xiàn)很多吊炸天的動(dòng)畫。
Java方式使用系統(tǒng)提供的默認(rèn)LayoutTransition動(dòng)畫:
在使用LayoutTransition時(shí),你可以自定義這幾種事件類型的動(dòng)畫,也可以使用默認(rèn)的動(dòng)畫,總之最終都是通過 setLayoutTransition(LayoutTransition lt)方法把這些動(dòng)畫以一個(gè)LayoutTransition對象設(shè)置給一個(gè)ViewGroup。
譬如實(shí)現(xiàn)如上Xml方式的默認(rèn)系統(tǒng)LayoutTransition動(dòng)畫如下:
mTransitioner =new LayoutTransition();
mViewGroup.setLayoutTransition(mTransitioner);
稍微再高端一點(diǎn)吧,我們來自定義這幾類事件的動(dòng)畫,分別實(shí)現(xiàn)他們,那么你可以像下面這么處理:
mTransitioner =new LayoutTransition();......ObjectAnimator anim = ObjectAnimator.ofFloat(this,"scaleX",0,1);......//設(shè)置更多動(dòng)畫mTransition.setAnimator(LayoutTransition.APPEARING, anim);......//設(shè)置更多類型的動(dòng)畫? ? ? ? ? ? ? ? mViewGroup.setLayoutTransition(mTransitioner);
到此通過LayoutTransition你就能實(shí)現(xiàn)類似小米手機(jī)計(jì)算器切換普通型和科學(xué)型的炫酷動(dòng)畫了。
到此Android動(dòng)畫基本已經(jīng)描述OK了,也就這么三大類,尤其是屬性動(dòng)畫更加一籌。但是特別說一句,上面基本都沒有提及到各種動(dòng)畫的 Listener接口,原因是這個(gè)玩意太簡單,所以不提了,相信你會監(jiān)聽View的onClickListener就一定會觸類旁通動(dòng)畫的 Listener方法的。有了這些基礎(chǔ)相信無論是自定義控件時(shí)還是自定義動(dòng)畫時(shí)都會起到直接的指導(dǎo)參考作用。其實(shí)對于Android的動(dòng)畫實(shí)現(xiàn)遠(yuǎn)遠(yuǎn)不止現(xiàn) 在提到的這些,但是這些又是基礎(chǔ),所以后面還會寫文章說說Android提供的其他動(dòng)畫參考工具類的。
現(xiàn)在我們繼續(xù)沿用官方的對比,翻譯一下這些動(dòng)畫的區(qū)別,具體如下(點(diǎn)我參看原文How Property Animation Differs from View Animation):
View動(dòng)畫:
View動(dòng)畫只能夠?yàn)閂iew添加動(dòng)畫,如果想為非View對象添加動(dòng)畫須自己實(shí)現(xiàn);且View動(dòng)畫支持的種類很少;尤其是他改變的是View的繪制效果,View的屬性沒有改變,其位置與大小都不變;View動(dòng)畫代碼量少,使用簡單方便。
Property動(dòng)畫:
彌補(bǔ)了View動(dòng)畫的缺陷,你可以為一個(gè)對象的任意屬性添加動(dòng)畫,對象自己的屬性會被真的改變;當(dāng)對象的屬性變化的時(shí)候,屬性動(dòng)畫會自動(dòng)刷新屏幕;屬性動(dòng)畫改變的是對象的真實(shí)屬性,而且屬性動(dòng)畫不止用于View,還可以用于任何對象。
好了,太不容易了!這一篇文章差點(diǎn)難產(chǎn)了,斷斷續(xù)續(xù)各種事,終于追求完美OK了,接下來博客恢復(fù)正常更新。