RecyclerView(三)——ItemAnimator


RecyclerView為我們提供了相較於ListView算得上華麗的動畫特效。RecyclerView的特效,非常符合Material Design的風格,但有時候,我們也希望能夠自定義ItemAnimator。

我們自定義一個類,並承繼SimpleItemAnimator。可以得到共9個需要實現的方法。

 

    @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,然后做了一些小實驗。

mRecyclerView.setItemAnimator(new FirstItemAnimator());

 

 

1.添加:

    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:

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中,進行動畫。

 

我們嘗試着做一個,透明度從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中執行動畫:

    @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

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM