zoukankan      html  css  js  c++  java
  • 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

      

  • 相关阅读:
    微信上传图片
    HTMl5的sessionStorage和localStorage
    ADO.NET使用using关闭数据库连接
    上传插件(WebUploader)
    jQuery文件分片上传
    SQL Server查询已锁的表及解锁
    Hadoop到底能做什么?怎么用hadoop?
    Hadoop使用场景
    WinForm中DataGridView的快速查找及将指定行显示到第一行
    WinForm中DataGridView的全选与取消全选
  • 原文地址:https://www.cnblogs.com/fishbone-lsy/p/5658824.html
Copyright © 2011-2022 走看看