屬性動(dòng)畫(Animator)是在API11(Android3.0)新加入的容诬。主要是針對(duì)一個(gè)對(duì)象的屬性變化加入動(dòng)畫效果。屬性動(dòng)畫的默認(rèn)時(shí)長(zhǎng)是300ms屹堰,默認(rèn)幀率是10ms/幀。其可以達(dá)到的效果就是街氢,在一定的時(shí)長(zhǎng)內(nèi)扯键,對(duì)象的某個(gè)屬性值連續(xù)發(fā)生變化。因此珊肃,屬性動(dòng)畫幾乎無(wú)所不能荣刑。只要對(duì)象有這個(gè)屬性(主要是有這個(gè)屬性的setter方法),它都能實(shí)現(xiàn)動(dòng)畫效果伦乔。但屬性動(dòng)畫是Android3.0以后才加入的厉亏,這就限制了屬性動(dòng)畫在API 11之前的系統(tǒng)上使用。不過烈和,使用開源動(dòng)畫庫(kù)nineoldandroids叶堆,可以在API 11之前的系統(tǒng)上使用屬性動(dòng)畫。
簡(jiǎn)單提一下斥杜,nineoldandroids開源動(dòng)畫庫(kù)虱颗,它的功能和android.animation.*中的類的功能完全一樣,使用方式也完全一致蔗喂。實(shí)現(xiàn)屬性動(dòng)畫比較常用的類有:ValueAnimator忘渔,ObjectAnimator,AnimatorSet缰儿。其中ObjectAnimator是ValueAnimator的子類畦粮,一個(gè)ObjectAnimator對(duì)象可以作用于同一個(gè)對(duì)象(如View)的一個(gè)或多個(gè)屬性。AnimatorSet是一組Animator的集合乖阵,主要用于以不同的順序控制多個(gè)屬性動(dòng)畫Animator的執(zhí)行宣赔。
下面是幾種屬性動(dòng)畫的簡(jiǎn)單實(shí)現(xiàn)方式:
//方式一
intentBtn.animate().translationXBy(800);
//方式二
ObjectAnimator.ofInt(intentBtn,"translationX",0,800).start();
//方式三
animate(intentBtn).setDuration(1000).rotationYBy(720).x(100).y(100);
AnimatorSet的簡(jiǎn)單用法:
AnimatorSet set = new AnimatorSet();
set.playTogether(
ObjectAnimator.ofFloat(myView, "rotationX", 0, 360),
ObjectAnimator.ofFloat(myView, "rotationY", 0, 180),
ObjectAnimator.ofFloat(myView, "rotation", 0, -90),
ObjectAnimator.ofFloat(myView, "translationX", 0, 90),
ObjectAnimator.ofFloat(myView, "translationY", 0, 90),
ObjectAnimator.ofFloat(myView, "scaleX", 1, 1.5f),
ObjectAnimator.ofFloat(myView, "scaleY", 1, 0.5f),
ObjectAnimator.ofFloat(myView, "alpha", 1, 0.25f, 1)
);
set.setDuration(5 * 1000).start();
ValueAnimator的用法
ValueAnimator valueAnimator = ValueAnimator.ofInt(1,100);
valueAnimator.addUpdateListener(new ValueAnimator.AnimatorUpdateListener() {
IntEvaluator evaluator = new IntEvaluator();
@Override
public void onAnimationUpdate(ValueAnimator animation) {
int currentCount = (int)animation.getAnimatedValue();
float fraction = currentCount/100f;
intentBtn.getLayoutParams().width = evaluator.evaluate(fraction,300,800);
intentBtn.requestLayout();
}
});
valueAnimator.setDuration(5000).start();
屬性動(dòng)畫的原理
屬性動(dòng)畫要求動(dòng)畫作用的對(duì)象能提供該屬性的get和set方法,屬性動(dòng)畫根據(jù)你設(shè)置的屬性的初始值和結(jié)束值瞪浸,以動(dòng)畫的效果多次調(diào)用該屬性的set方法儒将,每次傳遞給set方法的值都不一樣,確切地說(shuō)是对蒲,隨著時(shí)間的推移钩蚊,所傳遞的值越來(lái)越接近結(jié)束值。所以蹈矮,你對(duì)一個(gè)對(duì)象(如View)的屬性xxx做動(dòng)畫砰逻,如果想讓動(dòng)畫生效,要同時(shí)滿足兩個(gè)條件:
- 對(duì)象必須提供setXxx方法泛鸟,如果動(dòng)畫初始化時(shí)沒有設(shè)置起始值蝠咆,還需要提供getXxx方法,因?yàn)橄到y(tǒng)要去拿到xxx屬性的初始值北滥。(如果條件不滿足刚操,會(huì)導(dǎo)致crash)
- 對(duì)象的setXxx方法對(duì)屬性xxx的改變必須通過某種方式體現(xiàn)出來(lái)闸翅,比如會(huì)帶來(lái)UI的改變之類的,既然是動(dòng)畫赡茸,肯定是要有直觀的視覺變化(如果這條不滿足,動(dòng)畫效果提現(xiàn)不出來(lái)但不會(huì)crash)
如果對(duì)象沒有屬性的setter方法或者setter方法不能帶來(lái)UI上的改變祝闻,就需要其他的方法占卧。google告訴我們?nèi)N方法:
- 如果對(duì)象沒有setter和getter方法,就自己設(shè)置setter和getter方法联喘,前提是你有權(quán)限华蜒。
- 一個(gè)類來(lái)包裝原始對(duì)象,間接為其提供getter和settter方法豁遭。
- 采用ValueAnimator叭喜,監(jiān)聽動(dòng)畫過程,自己實(shí)現(xiàn)屬性的改變蓖谢。
以上的第一點(diǎn)很顯而易見捂蕴,第三點(diǎn)在上面ValueAnimator的用法中已經(jīng)講到。著重講講第二點(diǎn)闪幽。以FrameLayout的marginTop屬性為例啥辨。首先我們應(yīng)該知道m(xù)arginTop屬性對(duì)應(yīng)xml里面的android:layout_marginTop屬性,也就是說(shuō)它是FrameLayout控件的LayoutParams中的屬性LayoutParams.topMargin盯腌,F(xiàn)rameLayout類中并沒有setMarginTop方法溉知,根據(jù)上面說(shuō)的第二種方法,我們可以這樣實(shí)現(xiàn):
首先腕够,定義一個(gè)ViewWrapper類
class ViewWrapper{
View target;
public ViewWrapper(View view){
this.target = view;
}
public int getMarginTop(){
return ((FrameLayout.LayoutParams)target.getLayoutParams()).topMargin;
}
public void setMarginTop(int marginTop){
FrameLayout.LayoutParams lp = (FrameLayout.LayoutParams) target.getLayoutParams();
lp.topMargin = marginTop;
target.setLayoutParams(lp);
//注意此處只設(shè)置setLayoutParams并不能帶來(lái)UI上的改變级乍,必須在最后調(diào)用requestLayout
//這就是屬性動(dòng)畫原理中說(shuō)到的第二個(gè)條件
target.requestLayout();
}
}
然后,將這個(gè)包裝對(duì)象作為ObjectAnimator的作用對(duì)象
//此處的this帚湘,是一個(gè)自定義的FrameLayout子類
viewWrapper = new ViewWrapper(this);
ObjectAnimator animator = ObjectAnimator.ofInt(viewWrapper,"marginTop",-100);
animator.setDuration(1000);
animator.start();
以上就是針對(duì)對(duì)象屬性沒有setter方法玫荣,實(shí)現(xiàn)屬性動(dòng)畫的一種方法。
ViewPropertyAnimator
ViewPropertyAnimator的功能和ObjectAnimator一樣大诸,sdk給出的說(shuō)明是崇决,如果只是對(duì)view的一兩個(gè)屬性設(shè)置屬性動(dòng)畫,就直接用ObjectAnimator就好底挫;而如果是對(duì)一個(gè)view對(duì)象的多個(gè)屬性設(shè)置動(dòng)畫同時(shí)開啟恒傻,那么官方推薦用ViewPropertyAnimator,因?yàn)樗鼤?huì)對(duì)這些屬性動(dòng)畫進(jìn)行自動(dòng)優(yōu)化。同時(shí)茵乱,使用ViewPropertyAnimator設(shè)置屬性動(dòng)畫也是一種更方便的方式私植,它的API看起來(lái)更直觀艘蹋。
intentBtn.animate().translationXBy(800).scaleX(1.5f).alpha(0.8f);
animate()方法會(huì)創(chuàng)建并返回一個(gè)ViewPropertyAnimator對(duì)象沸手,這就是ViewPropertyAnimator的簡(jiǎn)單用法外遇。以下圖表列出的是ViewPropertyAnimator的一些API和與之對(duì)應(yīng)的View動(dòng)畫的相關(guān)的API的簡(jiǎn)單說(shuō)明
PropertyValuesHolder
PropertyValuesHolder的用途,最簡(jiǎn)單的就是契吉,在一個(gè)ObjectAnimator對(duì)象中跳仿,設(shè)置多個(gè)屬性動(dòng)畫。我們從PropertyValuesHolder的實(shí)例化函數(shù)可以看出來(lái)捐晶,它和對(duì)應(yīng)ObjectAnimator中對(duì)應(yīng)的同名函數(shù)菲语,唯一的區(qū)別就是少了一個(gè)參數(shù),也就是屬性動(dòng)畫的對(duì)象惑灵。
public static PropertyValuesHolder ofFloat(String propertyName, float... values)
public static PropertyValuesHolder ofInt(String propertyName, int... values)
public static PropertyValuesHolder ofObject(String propertyName, TypeEvaluator evaluator,Object... values)
同時(shí)我們也可以看到山上,ObjectAnimator類有一個(gè)實(shí)例化的方法是ofPropertyValuesHolder,可以傳一個(gè)或多個(gè)PropertyValuesHolder對(duì)象作為參數(shù)
public static ObjectAnimator ofPropertyValuesHolder(Object target,PropertyValuesHolder... values)
所以,PropertyValuesHolder和ObjectAnimator組合起來(lái)的用法就像這樣
PropertyValuesHolder rotationHolder = PropertyValuesHolder.ofFloat("Rotation", 60f, -60f, 40f, -40f, -20f, 20f, 10f, -10f, 0f);
PropertyValuesHolder colorHolder = PropertyValuesHolder.ofInt("BackgroundColor", 0xffffffff, 0xffff00ff, 0xffffff00, 0xffffffff);
ObjectAnimator animator = ObjectAnimator.ofPropertyValuesHolder(mTextView, rotationHolder, colorHolder);
animator.setDuration(3000);
animator.setInterpolator(new AccelerateInterpolator());
animator.start();
給一個(gè)ObjectAnimator設(shè)置了兩個(gè)屬性動(dòng)畫
KeyFrame
KeyFrame就是關(guān)鍵幀英支,從電影動(dòng)畫的原理上來(lái)說(shuō)佩憾,關(guān)鍵幀就是整個(gè)動(dòng)畫過程中,某個(gè)特定時(shí)刻的畫面圖像干花。那么妄帘,將這個(gè)理解放到屬性動(dòng)畫這里,關(guān)鍵幀就是某個(gè)特定階段點(diǎn)的屬性值池凄。這里說(shuō)的階段點(diǎn)寄摆,是指動(dòng)畫完成的進(jìn)度點(diǎn)。先來(lái)看看它的實(shí)例化函數(shù)
public static Keyframe ofFloat(float fraction, float value)
其中第一個(gè)參數(shù)fraction修赞,就是進(jìn)度或者說(shuō)比例婶恼,簡(jiǎn)單的理解就是動(dòng)畫進(jìn)行到多少進(jìn)度時(shí),對(duì)應(yīng)的屬性值是多少柏副。
KeyFrame的簡(jiǎn)單用法如下
Keyframe frame0 = Keyframe.ofFloat(0f, 0);
Keyframe frame1 = Keyframe.ofFloat(0.1f, -20f);
Keyframe frame2 = Keyframe.ofFloat(1, 0);
PropertyValuesHolder frameHolder = PropertyValuesHolder.ofKeyframe("rotation",frame0,frame1,frame2);
Animator animator = ObjectAnimator.ofPropertyValuesHolder(mImage,frameHolder);
animator.setDuration(1000);
animator.start();
需要說(shuō)明的是勾邦,給PropertyValuesHolder設(shè)置KeyFrame對(duì)象不能少于兩個(gè),只有一個(gè)KeyFrame形成不了動(dòng)畫割择。默認(rèn)情況下眷篇,如代碼中的frame0到frame1這段動(dòng)畫中,屬性值的變化是勻速的荔泳,frame1到frame2這段動(dòng)畫中蕉饼,屬性值的變化也是勻速的。但是這兩個(gè)速率是不相等的玛歌,明顯前一段的速率要大一些昧港,后一段的速率要小一些。
下面的小例子支子,演示了屬性動(dòng)畫為ViewGroup的子View的顯示和隱藏設(shè)置過渡動(dòng)畫:
llImageView = (LinearLayout) root.findViewById(R.id.ll_image);
LayoutTransition transition = new LayoutTransition();
transition.setStagger(LayoutTransition.CHANGE_APPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_APPEARING, 0);
ObjectAnimator appearingAnimator = ObjectAnimator
.ofPropertyValuesHolder(
(Object) null,
PropertyValuesHolder.ofFloat("scaleX", 0.0f, 1.0f),
PropertyValuesHolder.ofFloat("scaleY", 0.0f, 1.0f),
PropertyValuesHolder.ofFloat("alpha", 0, 1.0f));
transition.setAnimator(LayoutTransition.APPEARING, appearingAnimator);
transition.setDuration(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.APPEARING));
transition.setStartDelay(LayoutTransition.APPEARING, transition.getDuration(LayoutTransition.CHANGE_APPEARING));
ObjectAnimator disappearingAnimator = ObjectAnimator
.ofPropertyValuesHolder(
(Object) null,
PropertyValuesHolder.ofFloat("scaleX", 1.0f, 0.0f),
PropertyValuesHolder.ofFloat("scaleY", 1.0f, 0.0f),
PropertyValuesHolder.ofFloat("alpha", 1.0f, 0));
transition.setAnimator(LayoutTransition.DISAPPEARING, disappearingAnimator);
transition.setDuration(LayoutTransition.DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
transition.setStartDelay(LayoutTransition.DISAPPEARING, 0);
transition.setStagger(LayoutTransition.CHANGE_DISAPPEARING, 30);
transition.setDuration(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.CHANGE_DISAPPEARING));
transition.setStartDelay(LayoutTransition.CHANGE_DISAPPEARING, transition.getDuration(LayoutTransition.DISAPPEARING));
llImageView.setLayoutTransition(transition);
順帶解釋下過渡動(dòng)畫的四種類型CHANGE_APPEARING创肥、APPEARING、DISAPPEARING和CHANGE_DISAPPEARING
APPEARING
當(dāng)通過 設(shè)置子View的可見性為VISIBLE或者通過addView方法添加子View 來(lái)顯示子View時(shí),
子View就會(huì)執(zhí)行該類型的動(dòng)畫叹侄。
該類型動(dòng)畫的周期為300毫秒巩搏,默認(rèn)延遲為300毫秒。
DISAPPEARING
當(dāng)通過 設(shè)置子View的可見性為GONE或者通過removeView方法移除子View 來(lái)隱藏子View時(shí)趾代,
子View就會(huì)執(zhí)行該類型的動(dòng)畫贯底。
該類型動(dòng)畫的周期為300毫秒,默認(rèn)延遲為0毫秒撒强。
CHANGE_APPEARING
當(dāng)顯示子View時(shí)禽捆,所有的兄弟View就會(huì)立即依次執(zhí)行該類型動(dòng)畫并且兄弟View之間執(zhí)行動(dòng)畫的間隙默認(rèn)為0毫秒,然后才會(huì)執(zhí)行顯示子View的動(dòng)畫尿褪。
該類型動(dòng)畫的周期為300毫秒睦擂,默認(rèn)延遲為0毫秒得湘。
CHANGE_DISAPPEARING
當(dāng)隱藏子View的動(dòng)畫執(zhí)行完畢后杖玲,所有的兄弟View就會(huì)依次執(zhí)行該類型動(dòng)畫并且兄弟View之間執(zhí)行動(dòng)畫的間隙默認(rèn)為0毫秒。
該類型動(dòng)畫的周期為300毫秒淘正,默認(rèn)延遲為300毫秒摆马。
設(shè)置監(jiān)聽器
給屬性動(dòng)畫設(shè)置監(jiān)聽器,ViewPropertyAnimator和ObjectAnimator略微不一樣鸿吆;ViewPropertyAnimator用的是setListner和setUpdateListner方法囤采,可以設(shè)置一個(gè)監(jiān)聽器,移除監(jiān)聽器時(shí)惩淳,通過setListner(null)和setUpdateListner(null)蕉毯,來(lái)移除;而ObjetcAnimator用的是addListner和addUpdateListner方法思犁,可以添加一個(gè)或多個(gè)監(jiān)聽器代虾,移除監(jiān)聽器則是通過removeListner(listner)和removeUpdateListner(listner)來(lái)移除指定的監(jiān)聽器對(duì)象。
由于ObjectAnimator支持用pause()方法暫停動(dòng)畫激蹲,所以多了一對(duì)addPauseListner和removePauseListner方法的支持棉磨;而ViewPropertyAnimator獨(dú)有的withStartAction(action)和withEndAction(action),可以為其設(shè)置一次性的針對(duì)動(dòng)畫開始和結(jié)束的處理学辱。
ViewPropertyAnimator.setListener() / ObjectAnimator.addListener()
它們的參數(shù)類型都是Animator.AnimatorListener乘瓤,有4個(gè)回調(diào)方法:
@Override
public void onAnimationStart(Animator animation) {}
@Override
public void onAnimationEnd(Animator animation) {}
@Override
public void onAnimationCancel(Animator animation) {}
@Override
public void onAnimationRepeat(Animator animation) {}
其中值得注意的是:就算動(dòng)畫未執(zhí)行完被取消,onAnimationEnd()也會(huì)被回調(diào)策泣。也就是說(shuō)動(dòng)畫被取消時(shí)衙傀,會(huì)先回調(diào)onAnimationCancel(),再回調(diào)onAnimationEnd()萨咕。
由于ViewPropertyAnimator不支持重復(fù)執(zhí)行差油,所以ViewPropertyAnimator的監(jiān)聽器中的onAnimatorRepeat()方法相當(dāng)于無(wú)效。
ViewPropertyAnimator.setUpdateListener() / ObjectAnimator.addUpdateListener()
它們的參數(shù)類型都是Animator.AnimatorUpdateListener⌒罾回調(diào)方法只有一個(gè)
@Override
public void onAnimationUpdate(ValueAnimator animation) {}
當(dāng)動(dòng)畫的屬性更新時(shí)(不嚴(yán)謹(jǐn)?shù)恼f(shuō)发侵,即每過 10 毫秒,動(dòng)畫的完成度更新時(shí))妆偏,這個(gè)方法被調(diào)用刃鳄。
方法的參數(shù)是一個(gè) ValueAnimator,ValueAnimator 是 ObjectAnimator 的父類钱骂,也是 ViewPropertyAnimator 的內(nèi)部實(shí)現(xiàn)叔锐,所以這個(gè)參數(shù)其實(shí)就是 ViewPropertyAnimator 內(nèi)部的那個(gè) ValueAnimator,或者對(duì)于 ObjectAnimator 來(lái)說(shuō)就是它自己本身见秽。
ObjectAnimator.addPauseListener()
參數(shù)類型是Animator.AnimatorPauseListener愉烙,有兩個(gè)回調(diào)方法
@Override
public void onAnimationPause(Animator animation) {}
@Override
public void onAnimationResume(Animator animation) {}
ViewPropertyAnimator.withStartAction/EndAction()
這兩個(gè)方法是 ViewPropertyAnimator 的獨(dú)有方法。它們和 set/addListener() 中回調(diào)的 onAnimationStart() / onAnimationEnd() 相比起來(lái)的不同主要有兩點(diǎn):
withStartAction() / withEndAction() 是一次性的(只會(huì)執(zhí)行一次)解取,在動(dòng)畫執(zhí)行結(jié)束后就自動(dòng)棄掉了步责,就算之后再重用 ViewPropertyAnimator 來(lái)做別的動(dòng)畫,用它們?cè)O(shè)置的回調(diào)也不會(huì)再被調(diào)用禀苦。而 set/addListener() 所設(shè)置的 AnimatorListener 是持續(xù)有效的蔓肯,當(dāng)動(dòng)畫重復(fù)執(zhí)行時(shí),回調(diào)總會(huì)被調(diào)用振乏。
withEndAction() 設(shè)置的回調(diào)只有在動(dòng)畫正常結(jié)束時(shí)才會(huì)被調(diào)用蔗包,而在動(dòng)畫被取消時(shí)不會(huì)被執(zhí)行。這點(diǎn)和 AnimatorListener.onAnimationEnd() 的行為是不一致的慧邮。
本文參考:
https://blog.csdn.net/singwhatiwanna/article/details/17841165
http://www.reibang.com/p/b117c974deaf
https://juejin.im/post/5b5ac6eef265da0f6f1aad86
https://hencoder.com/ui-1-6/