7.3.4 對(duì)任意屬性做動(dòng)畫(huà)
這里先提出一個(gè)問(wèn)題:給Button
加一個(gè)動(dòng)畫(huà)与帆,讓這個(gè)Button
的寬度從當(dāng)前寬度增加到500px
了赌。也許你會(huì)說(shuō),這很簡(jiǎn)單玄糟,用View
動(dòng)畫(huà)就可以搞定勿她。很快你就會(huì)恍然大悟,原來(lái)View
動(dòng)畫(huà)根本不支持對(duì)寬度進(jìn)行動(dòng)畫(huà)茶凳。沒(méi)錯(cuò)嫂拴,View
動(dòng)畫(huà)只支持四種類型:平移,旋轉(zhuǎn)贮喧,縮放筒狠,不透明度。當(dāng)然用X
方向縮放(scaleX)
可以讓Button
在X
方向放大箱沦,看起來(lái)好像是寬度增加了辩恼,實(shí)際上不是,只是Button
被放大了而已,而且由于只X
方向被放大灶伊,這個(gè)時(shí)候Button
的背景以及上面的文本都被拉伸了疆前,甚至有可能Button
會(huì)超出屏幕。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
ObjectAnimator.ofInt(button,"width",500).setDuration(2000).start();
break;
}
}
上述代碼運(yùn)行后發(fā)現(xiàn)沒(méi)效果聘萨,其實(shí)沒(méi)效果是對(duì)的竹椒,如果隨便傳遞一個(gè)屬性過(guò)去,輕則沒(méi)動(dòng)畫(huà)效果米辐,重則程序直接Crash
胸完。
屬性動(dòng)畫(huà)的原理:屬性動(dòng)畫(huà)要求動(dòng)畫(huà)作用的對(duì)象提供該屬性的get
和set
方法,屬性動(dòng)畫(huà)根據(jù)外界傳遞的該屬性的初始值和最終值翘贮,以動(dòng)畫(huà)的效果多次去調(diào)用set
方法赊窥,每次傳遞給set
方法的值都不一樣,確切來(lái)說(shuō)狸页,隨著時(shí)間的推移锨能,所傳遞的值越來(lái)越接近最終值。總結(jié)一下芍耘,我們對(duì)object
的屬性abc
做動(dòng)畫(huà)址遇,如果想讓動(dòng)畫(huà)生效,要同時(shí)滿足兩個(gè)條件齿穗。
object
必須要提供setAbc
方法傲隶,如果動(dòng)畫(huà)的時(shí)候沒(méi)有傳遞初始值,那么還要提供getAbc
方法窃页,因?yàn)橄到y(tǒng)要去取abc
屬性的初始值(如果這條不滿足,程序直接Crash
)复濒。object
的setAbc
對(duì)屬性abc
所做的改變必須能夠通過(guò)某種方法反應(yīng)出來(lái)脖卖,比如會(huì)帶來(lái)UI
的改變之類的(如果這條不滿足,動(dòng)畫(huà)無(wú)效果但不會(huì)Crash
)巧颈。
以上條件缺一不可畦木,Button
內(nèi)部雖然提供了getWidth
和setWidth
方法,但是這個(gè)setWidth
方法并不是改變視圖的大小砸泛,它是TextView
新添加的方法十籍,View
是沒(méi)有這個(gè)setWidth
方法的,由于Button
繼承了TextView
唇礁,所以Button
也就有了setWidth
方法勾栗。
打開(kāi)TextView
的源碼,可以發(fā)現(xiàn)getWidth
的確是獲取View
的寬度的盏筐,但是setWidth
是設(shè)置TextView
的最大寬度和最小寬度的围俘,這個(gè)和TextView
的寬度不是一個(gè)東西。具體來(lái)說(shuō),TextView
的寬度對(duì)應(yīng)XML
中的android:layout_width
屬性界牡,而TextView
還有一個(gè)屬性android:width
簿寂,這個(gè)android:width
屬性就對(duì)應(yīng)了TextView
的setWidth
方法∷尥觯總之常遂,TextView
和Button
的setWidth
,getWidth
干的不是同一件事情挽荠,通過(guò)setWidth
無(wú)法改變控件的寬度克胳,所以對(duì)width
做屬性動(dòng)畫(huà)沒(méi)有效果。對(duì)應(yīng)于屬性動(dòng)畫(huà)的兩個(gè)條件來(lái)說(shuō)坤按,本例中動(dòng)畫(huà)不生效的原因是只滿足了條件1
而未滿足條件2
毯欣。
針對(duì)上述問(wèn)題,官方文檔上告訴我們有3種解決方法:
- 給你的對(duì)象加上
get
和set
方法臭脓,如果你有權(quán)限的話酗钞。 - 用一個(gè)類來(lái)包裝原始對(duì)象,間接為其提供
get
和set
方法来累。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
ViewWrapper wrapper = new ViewWrapper(button);
ObjectAnimator.ofInt(wrapper,"width",500).setDuration(2000).start();
break;
}
}
private static class ViewWrapper {
private View mTarget;
public ViewWrapper(View mTarget) {
this.mTarget = mTarget;
}
public int getWidth() {
return mTarget.getLayoutParams().width;
}
public void setWidth(int width) {
mTarget.getLayoutParams().width = width;
mTarget.requestLayout();
}
}
提供了ViewWrapper
類專門(mén)用于包裝View
砚作,然后我們對(duì)ViewWrapper
的width
屬性做動(dòng)畫(huà),并且在setWidth
方法中修改其內(nèi)部的target
的寬度嘹锁,而target
實(shí)際上就是我們包裝的Button
葫录。
- 采用
ValueAnimator
,監(jiān)聽(tīng)動(dòng)畫(huà)過(guò)程领猾,自己實(shí)現(xiàn)屬性的改變米同。
ValueAnimator
本身不作用于任何對(duì)象,也就是說(shuō)直接使用它沒(méi)有任何動(dòng)畫(huà)效果摔竿。它可以對(duì)一個(gè)值做動(dòng)畫(huà)面粮,然后我們可以監(jiān)聽(tīng)其動(dòng)畫(huà)過(guò)程。在動(dòng)畫(huà)過(guò)程中修改我們的對(duì)象的屬性值继低,這樣也就相當(dāng)于我們的對(duì)象做了動(dòng)畫(huà)熬苍。
@Override
public void onClick(View v) {
switch (v.getId()) {
case R.id.btn:
performAnimate(button,button.getWidth(),500);
break;
}
}
private void performAnimate(final View target,final int start,final int end) {
ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
// 持有一個(gè)IntEvaluator對(duì)象,方便下面估值的時(shí)候使用
private IntEvaluator mEvaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
// 獲得當(dāng)前動(dòng)畫(huà)的進(jìn)度值袁翁,整型柴底,1~100之間
int currentValue = (int) animation.getAnimatedValue();
// 獲得當(dāng)前進(jìn)度占整個(gè)動(dòng)畫(huà)過(guò)程的比例,浮點(diǎn)型 0~1之間
float fraction = animation.getAnimatedFraction();
// 直接調(diào)用整型估值器粱胜,通過(guò)比例計(jì)算出寬度柄驻,然后在設(shè)給Button
target.getLayoutParams().width = mEvaluator.evaluate(fraction,start,end);
target.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
}
它會(huì)在5000ms
內(nèi)將一個(gè)數(shù)從1
變到100
,然后動(dòng)畫(huà)的每一幀會(huì)回調(diào)onAnimationUpdate
方法年柠。在這個(gè)方法里凿歼,我們可以獲取當(dāng)前的值(1~100)
和當(dāng)前值所占的比例褪迟,我們可以計(jì)算出Button
現(xiàn)在的寬度應(yīng)該是多少。比如時(shí)間過(guò)了一半答憔,當(dāng)前值是50
味赃,比例為0.5
,假設(shè)Button
的起始寬度是100px
虐拓,最終寬度是500px
心俗,那么Button
應(yīng)該增加的寬度是400 * 0.5 = 200
,那么當(dāng)前Button
的寬度應(yīng)該為初始寬度 + 增加寬度(100 + 200 = 300)
蓉驹。
7.3.5 屬性動(dòng)畫(huà)的工作原理
屬性動(dòng)畫(huà)要求動(dòng)畫(huà)作用的對(duì)象提供該屬性的set
方法城榛,屬性動(dòng)畫(huà)根據(jù)你傳遞的該屬性的初始值和最終值,以動(dòng)畫(huà)的效果多次去調(diào)用set
方法态兴。每次傳遞給set
方法的值都不一樣狠持,確切來(lái)說(shuō)是隨著時(shí)間的推移,所傳遞的值越來(lái)越接近最終值瞻润。如果動(dòng)畫(huà)的時(shí)候沒(méi)有傳遞初始值喘垂,那么還要提供get
方法,因?yàn)橄到y(tǒng)要去獲取屬性的初始值绍撞。對(duì)于屬性動(dòng)畫(huà)來(lái)說(shuō)正勒,其動(dòng)畫(huà)過(guò)程中所做的就是這么多。
7.4 使用動(dòng)畫(huà)的注意事項(xiàng)
通過(guò)動(dòng)畫(huà)可以實(shí)現(xiàn)一些比較絢麗的效果傻铣,但是在使用過(guò)程中章贞,也需要注意一些事情,主要分為下面幾類:
1. OOM問(wèn)題
這個(gè)問(wèn)題主要出現(xiàn)在幀動(dòng)畫(huà)中非洲,當(dāng)圖片數(shù)量較多且圖片較大時(shí)就極易出現(xiàn)OOM鸭限,這個(gè)在實(shí)際的開(kāi)發(fā)中要尤其注意,盡量避免使用幀動(dòng)畫(huà)两踏。
2. 內(nèi)存泄漏
在屬性動(dòng)畫(huà)中有一類無(wú)限循環(huán)的動(dòng)畫(huà)里覆,這類動(dòng)畫(huà)需要在Activity
退出時(shí)及時(shí)停止,否則將導(dǎo)致Activity
無(wú)法釋放從而造成內(nèi)存泄漏缆瓣,通過(guò)驗(yàn)證后發(fā)現(xiàn)View
動(dòng)畫(huà)并不存在此問(wèn)題景醇。
3. 兼容性問(wèn)題
動(dòng)畫(huà)在3.0
以下的系統(tǒng)上有兼容性問(wèn)題瑟俭,在某些特殊場(chǎng)景可能無(wú)法正常工作躺同,因此要做好適配工作进苍。
4. View動(dòng)畫(huà)的問(wèn)題
View
動(dòng)畫(huà)是對(duì)View
的影像做動(dòng)畫(huà)赔桌,并不是真正地改變View
的狀態(tài)甘耿,因此有時(shí)候會(huì)出現(xiàn)動(dòng)畫(huà)完成后View
無(wú)法隱藏的現(xiàn)象砾赔,即setVisibility(View.GONE)
失效了旦事,這個(gè)時(shí)候只要調(diào)用view.clearAnimation()
清除View
動(dòng)畫(huà)即可解決此問(wèn)題忧便。
5. 不要使用px
在進(jìn)行動(dòng)畫(huà)的過(guò)程中族吻,要盡量使用dp
,使用px
會(huì)導(dǎo)致在不同的設(shè)備上有不同的效果。
6. 動(dòng)畫(huà)元素的交互
將View
移動(dòng)(平移)后超歌,在Android3.0
以前的系統(tǒng)上砍艾,不管是View
動(dòng)畫(huà)還是屬性動(dòng)畫(huà),新位置均無(wú)法觸發(fā)單擊事件巍举,同時(shí)脆荷,老位置仍然可以觸發(fā)單擊事件。盡管View
已經(jīng)在視覺(jué)上不存在了懊悯,將View
移回原位置以后蜓谋,原位置的單擊事件繼續(xù)生效。從3.0
開(kāi)始炭分,屬性動(dòng)畫(huà)的單擊事件觸發(fā)位置為移動(dòng)后的位置桃焕,但是View
動(dòng)畫(huà)仍然在原位置。
7. 硬件加速
使用動(dòng)畫(huà)的過(guò)程中捧毛,建議開(kāi)啟硬件加速观堂,這樣會(huì)提高動(dòng)畫(huà)的流暢性。