RecyclerView爲咱們提供了相較於ListView算得上華麗的動畫特效。RecyclerView的特效,很是符合Material Design的風格,但有時候,咱們也但願可以自定義ItemAnimator。ide
咱們自定義一個類,並承繼SimpleItemAnimator。能夠獲得共9個須要實現的方法。post
@Override public boolean animateRemove(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { return false; } @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { return false; } @Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { return false; } @Override public void runPendingAnimations() { } @Override public void endAnimation(RecyclerView.ViewHolder item) { } @Override public void endAnimations() { } @Override public boolean isRunning() { return false; }
可見,這是一個比較複雜的功能。咱們一一來解剖。動畫
咱們自定義了一個FirstItemAnimator繼承了SimpleItemAnimator,將上面9個方法打上Log,而後作了一些小實驗。this
mRecyclerView.setItemAnimator(new FirstItemAnimator());
1.添加:spa
Runnable runnable = new Runnable() { @Override public void run() { FirstBean firstBean = new FirstBean(); firstBean.setTestContent("This position is = " + mDataManager.getDataList().size()); mDataManager.getDataList().add(firstBean); FirstBean firstBean2 = new FirstBean(); firstBean2.setTestContent("This position is = " + mDataManager.getDataList().size()); mDataManager.getDataList().add(firstBean2); mAdapter.notifyItemRangeInserted(mDataManager.getDataList().size()-1 , mDataManager.getDataList().size()); if (mDataManager.getDataList().size() < 10){ mRecyclerView.postDelayed(runnable, 2000); } } };
每兩秒添加一項。此時的Log是:設計
07-10 14:50:20.149 10387-10387/com.dream.fishbonelsy.tooldatamanager D/tag: this action is animateAdd 07-10 14:50:22.141 10387-10387/com.dream.fishbonelsy.tooldatamanager D/tag: this action is animateAdd
而後咱們將animateAdd方法中的返回改成true,則會獲得這樣的Log:code
07-11 14:53:19.197 11029-11029/com.dream.fishbonelsy.tooldatamanager D/tag: this action is animateAdd 07-11 14:53:19.197 11029-11029/com.dream.fishbonelsy.tooldatamanager D/tag: this action is animateAdd 07-11 14:53:19.209 11029-11029/com.dream.fishbonelsy.tooldatamanager D/tag: this action is runPendingAnimations
咱們能夠簡單地理解爲,在animateAdd中,咱們對view進行動畫前的初始化,在runPendingAnimations中,進行動畫。blog
咱們嘗試着作一個,透明度從0到1的動畫。初始狀態下,咱們要將view的透明度置0,並將view放進準備動畫的列表中:繼承
List<View> mAnimatorViewList = new ArrayList<>(); @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { Log.d("tag", "this action is animateAdd"); ViewCompat.setAlpha(holder.itemView, 0); mAnimatorViewList.add(holder.itemView); return true; }
最後,咱們在runPendingAnimations中執行動畫:rem
@Override public void runPendingAnimations() { for (View view : mAnimatorViewList) { final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); animation.alpha(1).setDuration(getAddDuration()).start(); } mAnimatorViewList.clear(); Log.d("tag", "this action is runPendingAnimations"); }
值得注意的是,最後咱們會將待執行動畫列表裏面的已經執行過動畫的view手動clear掉,不然它們下次還會再執行一次動畫。
當頁面關閉時,會執行:
07-10 14:50:24.317 10387-10387/com.dream.fishbonelsy.tooldatamanager D/tag: this action is endAnimations
可見,在notifyItemInserted時,主要是animateAdd方法在起做用。根據上面的思路,咱們發現ViewHolder的增刪改動畫,都是分兩部分執行的。第一部分是在animateAdd或animateRemove中,第二部分是在runPendingAnimations中。每次操做,animateAdd/animateRemove先調用,而且調用的次數與輸入item範圍一致,而runPendingAnimations只調用一次。
在這樣的思路下,我先設置了notifyItemInserted的動畫。
List<RecyclerView.ViewHolder> mAddAnimatorViewList = new ArrayList<>(); @Override public boolean animateAdd(RecyclerView.ViewHolder holder) { Log.d("tag", "this action is animateAdd"); ViewCompat.setAlpha(holder.itemView, 0); mAddAnimatorViewList.add(holder); return true; } @Override public void runPendingAnimations() { boolean needRemove = mRemoveAnimatorViewList.size() > 0; for (RecyclerView.ViewHolder holder : mAddAnimatorViewList) { final ViewPropertyAnimatorCompat animation = ViewCompat.animate(holder.itemView); animation.alpha(1).setDuration(getAddDuration()).start(); } mAddAnimatorViewList.clear(); }
由上面的代碼,咱們能夠很清晰地看到,咱們將每一個須要執行動畫的ViewHolder放入一個mAddAnimatorViewList。而後先將他它設爲透明。在runPendingAnimations中,再讓他們經過動畫的方式,漸漸變爲不透明。值得注意的是,全部動畫完成後,咱們須要clear掉mAddAnimatorViewList。
/*----------------------------------------------------------------分割線--------------------------------------------------------------------------*/
接下來,咱們再設計,一個notifyItemRemoved的動畫。
Remove的動畫,會相對來講比較複雜。由於,當Item的刪除時,默認的RecyclerView會立刻進行相應的移動。但不少狀況下,咱們但願等那個被remove的ViewHolder動畫完成以後,再移動其餘的ViewHolder來補它的位置。所以,咱們要按以下流程進行:
1.在remove的一瞬間,先將由於這項remove須要移動的ViewHolder反向移動,從視覺上保持它們的不變。
2.而後,執行remove的動畫。
3等到remove動畫執行完成後,再將那些須要移動的ViewHolder移回正確的位置。
依照上面的思路,這三個步驟的代碼分別是:
1.
List<MoveInfo> mMoveInfoAnimatorViewList = new ArrayList<>(); private static class MoveInfo { RecyclerView.ViewHolder holder; int fromX; int fromY; int toX; int toY; MoveInfo(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { this.holder = holder; this.fromX = fromX; this.fromY = fromY; this.toX = toX; this.toY = toY; } } @Override public boolean animateMove(RecyclerView.ViewHolder holder, int fromX, int fromY, int toX, int toY) { Log.d("tag", "this action is animateMove"); final View view = holder.itemView; fromX += ViewCompat.getTranslationX(holder.itemView); fromY += ViewCompat.getTranslationY(holder.itemView); AnimatorCompatHelper.clearInterpolator(holder.itemView); endAnimation(holder); int deltaX = toX - fromX; int deltaY = toY - fromY; if (deltaX == 0 && deltaY == 0) { dispatchMoveFinished(holder); return false; } if (deltaX != 0) { ViewCompat.setTranslationX(view, -deltaX); } if (deltaY != 0) { ViewCompat.setTranslationY(view, -deltaY); } mMoveInfoAnimatorViewList.add(new MoveInfo(holder , fromX, fromY, toX, toY)); return true; }
2.
List<RecyclerView.ViewHolder> mRemoveAnimatorViewList = new ArrayList<>(); @Override public boolean animateRemove(RecyclerView.ViewHolder holder) { Log.d("tag", "this action is animateRemove"); //ViewCompat.setAlpha(holder.itemView, 1); mRemoveAnimatorViewList.add(holder); return true; } @Override public void runPendingAnimations() { boolean needRemove = mRemoveAnimatorViewList.size() > 0; ... for (RecyclerView.ViewHolder holder : mRemoveAnimatorViewList) { final ViewPropertyAnimatorCompat animation = ViewCompat.animate(holder.itemView); animation.translationX(-1000).setDuration(getRemoveDuration() ).start(); } mRemoveAnimatorViewList.clear(); ... }
3.
@Override public void runPendingAnimations() { ... Runnable moveRunnable = new Runnable() { @Override public void run() { for (MoveInfo moveInfo : mMoveInfoAnimatorViewList) { final RecyclerView.ViewHolder holder = moveInfo.holder; final View view = moveInfo.holder.itemView; final int deltaX = moveInfo.toX - moveInfo.fromX; final int deltaY = moveInfo.toY - moveInfo.fromY; if (deltaX != 0) { ViewCompat.animate(view).translationX(0); } if (deltaY != 0) { ViewCompat.animate(view).translationY(0); } final ViewPropertyAnimatorCompat animation = ViewCompat.animate(view); animation.setDuration(getMoveDuration()).setListener(new ViewPropertyAnimatorListener() { @Override public void onAnimationStart(View view) { dispatchMoveStarting(holder); } @Override public void onAnimationCancel(View view) { if (deltaX != 0) { ViewCompat.setTranslationX(view, 0); } if (deltaY != 0) { ViewCompat.setTranslationY(view, 0); } } @Override public void onAnimationEnd(View view) { //animation.setListener(null); //dispatchMoveFinished(holder); } }).start(); } mMoveInfoAnimatorViewList.clear(); } }; if (needRemove && mMoveInfoAnimatorViewList.size() > 0){ mMoveInfoAnimatorViewList.get(0).holder.itemView.postDelayed(moveRunnable ,getRemoveDuration);
}else {
moveRunnable.run();
}
}
經過上面這樣的代碼結構,便可完成,item的添加與刪除的動畫。
/*----------------------------------------------------------------分割線--------------------------------------------------------------------------*/
內容更新的代碼則比較簡單,在animateChange方法中,API爲咱們提供了oldHolder和newHolder,咱們能夠分別控制它們的動畫。在此,只作一箇舊的漸隱,新的漸顯。
@Override public boolean animateChange(RecyclerView.ViewHolder oldHolder, RecyclerView.ViewHolder newHolder, int fromLeft, int fromTop, int toLeft, int toTop) { Log.d("tag", "this action is animateChange"); final ViewPropertyAnimatorCompat animation = ViewCompat.animate(oldHolder.itemView); animation.alpha(0).setDuration(getChangeDuration()).start(); ViewCompat.setAlpha(newHolder.itemView, 0); mChangeAnimatorViewList.add(newHolder); return true; } @Override public void runPendingAnimations() { ... for (RecyclerView.ViewHolder holder : mChangeAnimatorViewList) { final ViewPropertyAnimatorCompat animation = ViewCompat.animate(holder.itemView); animation.alpha(1).setDuration(getChangeDuration()).start(); } mChangeAnimatorViewList.clear(); }
以上,就完成了對RecyclerView的增刪改的動畫的自定義。可是目前,還有一些問題,好比若是動畫還未執行完,我就企圖銷燬控件,會出現一些沒法回收的問題。這些將在後面補充。
Done