Item Animation的基本原理
我們都知道环凿,給RecyclerView
添加Item Animation的方法是setItemAnimator(ItemAnimator animator)
骂蓖。因此我們可以以抽象類ItemAnimator
為切入點浑测,看看Recyclerview
的Item Animation是怎樣實現(xiàn)的精肃。
首先列舉一下ItemAnimator
中幾個比較重要的方法簽名:
-
public ItemHolderInfo recordPreLayoutInformation(State state,ViewHolder viewHolder,int changeFlags,List<Object> payloads)
public ItemHolderInfo recordPostLayoutInformation(State state, ViewHolder viewHolder)
這兩個方法是成對出現(xiàn)的,分別用于記錄同一個ViewHolder在layout執(zhí)行前后view位置等信息,分別對應(yīng)了動畫開始前的view狀態(tài)和動畫結(jié)束后的view狀態(tài)。
-
public abstract boolean animateDisappearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
public abstract boolean animateAppearance(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
public abstract boolean animatePersistence(ViewHolder viewHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
public abstract boolean animateChange(ViewHolder oldHolder, ViewHolder newHolder, ItemHolderInfo preLayoutInfo, ItemHolderInfo postLayoutInfo)
這四個方法都是由
RecyclerView
調(diào)用郎哭,調(diào)用時機分別為在執(zhí)行l(wèi)ayout過程之后viewHolder
從RecyclerView
中移除(從有到無)、出現(xiàn)(從無到有)菇存、不變(同一個viewHolder
但是尺寸夸研、位置可能發(fā)生變化)、改變(從oldHolder
變?yōu)?code>newHolder撰筷,viewHolder
發(fā)生了變化)陈惰。
這四個方法中都包含preLayoutInfo
和postLayoutInfo
畦徘,這兩個參數(shù)就是由前面recordPreLayoutInformation
和recordPostLayoutInformation
方法計算出來的viewHolder
信息毕籽。我們就可以根據(jù)viewHolder
的初態(tài)(preLayoutInfo
)和終態(tài)(postLayoutInfo
)實現(xiàn)自定義的動畫。但是動畫并不是在這個方法中啟動井辆,在這四個方法中我們僅僅是根據(jù)preLayoutInfo
和postLayoutInfo
判斷是否需要動畫和需要什么動畫关筒。如果不需要執(zhí)行動畫,就必須在這四個方法中調(diào)用dispatchAnimationFinished(ViewHolder)
方法并且最后的return值必須為false
杯缺;反之如果需要執(zhí)行動畫return值就必須為true
蒸播,之后系統(tǒng)就會調(diào)用runPendingAnimations()
方法,并在runPendingAnimations()
中啟動各種動畫萍肆,在動畫執(zhí)行完畢后也必須要調(diào)用dispatchAnimationFinished(ViewHolder)
方法袍榆。
abstract public void runPendingAnimations()
如果上面四個方法中有一個的返回值是true
,在下一幀的時候就會執(zhí)行此方法塘揣,所有的動畫都可以在此方法中啟動包雀。
abstract public void endAnimation(ViewHolder item)
-
abstract public void endAnimations()
結(jié)束動畫,并確保各個viewHolder都處于最終態(tài)亲铡。
到此為止才写,我們可以縷一下整個過程。
首先RecyclerView
在執(zhí)行l(wèi)ayout前后會調(diào)用recordPreLayoutInformation
和recordPostLayoutInformation
方法奖蔓,并保存layout前后viewHodler
的位置等信息赞草;然后根據(jù)layout的情況執(zhí)行animateXXX
方法,我們在animateXXX
方法中決定是否執(zhí)行動畫和執(zhí)行何種動畫吆鹤;如果需要執(zhí)行動畫厨疙,則可以在runPendingAnimations
方法中啟動動畫;最后在endAnimation
和endAnimations
方法中確保viewHolder
處于最終態(tài)疑务。
快速實現(xiàn)自定義Item Animation
問當(dāng)今如何code最快轰异,那必然是ctrl-C加上ctrl-V岖沛。(開個玩笑~~~)
不過現(xiàn)在講的是如何快速的自定義Item Animation,基于學(xué)習(xí)的目的搭独,那當(dāng)然還是需要copy一些代碼的嘛婴削。至于以后項目中需要或者興趣使然要深入自定義Item Animation就需要自己參照原有實現(xiàn)進行封裝了。
廢話不多說牙肝,下面進入正題0λ住!配椭!
android support v7包里已經(jīng)包含了一個Item Animation的默認實現(xiàn):android.support.v7.widget.DefaultItemAnimator
虫溜,DefaultItemAnimator
繼承了SimpleItemAnimator
,而SimpleItemAnimator又繼承了RecyclerView.ItemAnimator
股缸。DefaultItemAnimator
實現(xiàn)了在添加衡楞、移除item時View的透明度發(fā)生變化,在移動item時view的位置發(fā)生變化敦姻。我們要做的僅僅是對DefaultItemAnimator
進行局部的修改~~~
recordPreLayoutInformation和recordPostLayoutInformation
在ItemAnimator
中已經(jīng)實現(xiàn)瘾境,animateDisappearance
、animateAppearance
镰惦、animatePersistence
和animateChange
在SimpleItemAnimator
中也已實現(xiàn)迷守,runPendingAnimations
在DefaultItemAnimator
中已經(jīng)實現(xiàn),我們用這些默認實現(xiàn)就可以了旺入。以添加Item動畫為例兑凿,我們只要修改如下3個地方就可以實現(xiàn)Item旋轉(zhuǎn)進入動畫(僅列出部分關(guān)鍵代碼,注釋為原代碼茵瘾,////中間為修改后的代碼):
-
設(shè)置動畫的初始值礼华,將view設(shè)置為旋轉(zhuǎn)180度
@Override public boolean animateAdd(final ViewHolder holder) { resetAnimation(holder); /**ViewCompat.setAlpha(holder.itemView, 0);**/ // ViewCompat.setRotation(holder.itemView, 180); // mPendingAdditions.add(holder); return true; }
2. 啟動動畫,讓view再轉(zhuǎn)180度拗秘。并在動畫取消時將view設(shè)置為終態(tài)圣絮,也就是旋轉(zhuǎn)0度
```
private void animateAddImpl(final RecyclerView.ViewHolder holder) {
...
/**animation.alpha(1)**/
//
animation.rotationBy(180)
//
.setDuration(getAddDuration()).
setListener(new VpaListenerAdapter() {
...
@Override
public void onAnimationCancel(View view) {
/**ViewCompat.setAlpha(view, 1);**/
//
ViewCompat.setRotation(view, 0);
//
}
...
}).start();
}
-
結(jié)束動畫,確保view在動畫結(jié)束或者取消后能回到終態(tài)聘殖,也就是旋轉(zhuǎn)0度晨雳。(這里省略了endAnimations()方法的修改)
@Override public void endAnimation(RecyclerView.ViewHolder item) { ... if (mPendingAdditions.remove(item)) { /**ViewCompat.setAlpha(view, 1);**/ // ViewCompat.setRotation(view, 0); // dispatchAddFinished(item); } ... for (int i = mAdditionsList.size() - 1; i >= 0; i--) { ArrayList<RecyclerView.ViewHolder> additions = mAdditionsList.get(i); if (additions.remove(item)) { /**ViewCompat.setAlpha(view, 1);**/ // ViewCompat.setRotation(view, 0); // dispatchAddFinished(item); if (additions.isEmpty()) { mAdditionsList.remove(i); } } } ... }
通過以上3步,我們就是實現(xiàn)了Item旋轉(zhuǎn)進入的動畫奸腺,以下是演示效果餐禁。
![Demo 演示效果](http://upload-images.jianshu.io/upload_images/289461-7b20ae37379c1199.gif?imageMogr2/auto-orient/strip)
>[Demo地址](https://github.com/lileibuaa/CustomItemAnimator)
**再次強調(diào),以上步驟僅適用于學(xué)習(xí)突照。在項目中使用還需自己參照實現(xiàn)自己想要的效果帮非。**
PS:順帶說下如何解決調(diào)用`notifyDataSetChanged`后,沒有觸發(fā)Item Animation的問題。末盔。
要想在調(diào)用`notifyDataSetChanged`之后觸發(fā)Item Animation筑舅,必須要在自定義adapter中調(diào)用方法`setHasStableIds(true)`;并實現(xiàn)`public long getItemId(int position)`方法返回各Item的id。陨舱。至于為啥翠拣,留待以后再探究吧。游盲。误墓。請原諒我這個拖延癥重度患者。