美文网首页Android 碎片
android ItemTouchHelper 的使用和遇到的问

android ItemTouchHelper 的使用和遇到的问

作者: HuZC | 来源:发表于2018-07-31 15:13 被阅读597次

    转载请注明出处:https://www.jianshu.com/p/05680f83a471

    # ItemTouchHelper 简介:

    这是一个RecyclerView的工具,提供了drag & swipe 的功能,可以帮助我们处理RecyclerView中的Item的拖拽和滑动事件。

    一、创建ItemTouchHelper:

    //创建helper对象,callback监听recyclerView item 的各种状态
    ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
    //关联recyclerView,一个helper对象只能对应一个recyclerView
    itemTouchHelper.attachToRecyclerView(recyclerView);
    

    二、创建callback的两种方式:

    1. ItemTouchHelper.Callback :
    new Callback() {
            @Override
            public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
            // 有6个值来控制方向
                //控制拖拽的方向(一般是上下左右)
                 int dragFlags= ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
                //控制快速滑动的方向(一般是左右)
                 int swipeFlags = ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
    //还有两个flag 分别是 ItemTouchHelper.START 和 ItemTouchHelper.END ,原文的解释是:
    //Horizontal direction. Resolved to LEFT or RIGHT depending on RecyclerView's layout direction. Used for swipe & drag control.
    // 横向方向,取决于 RecyclerView 的方向,与 LinearLayoutManager 的 layoutReverse 有关(暂时没有验证)
                return makeMovementFlags(dragFlags, swipeFlags);//计算movement flag值
            }
    
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
              // 拖拽时,每移动一个位置就会调用一次。
              // 在此改变 dataList 被移动 item 的位置,并且刷新adapter
               if (recyclerView == null) return false;
                    RecyclerView.Adapter adapter = recyclerView.getAdapter();
                    if (adapter == null) return false;
                    if (dataList != null) {
                        int from = viewHolder.getAdapterPosition();
                        endPosition = target.getAdapterPosition();//在这里我一直在刷新最后移动到的位置,以便接下来做其他操作
                        Collections.swap(dataList, from, endPosition);//数据交换位置
                        // 使用notifyItemMoved可以表现得更平滑,问题是 from ~ endPosition 间的item position 不会更新,并引发一系列角标混乱的问题,
                        //这个问题可以在后面的 onSelectedChanged()方法中解决。
                        // 在此做notifyItemMoved操作就足够了,notifyDataSetChanged() 和 notifyItemRangeChanged() 会打断 drag 操作。
                        adapter.notifyItemMoved(from, endPosition);
                        //其他操作
                        if (onMovedListener != null) {
                            onMovedListener.onMoved(dataList, from, endPosition);
                        }
                    }
                return true; // true 可以拖拽,false 不能。
            }
    
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
            //滑动处理
            }
        }
    

    要想能够拖拽还得将 isLongPressDragEnabled() 这个方法返回值置为 true,同理,滑动是 isItemViewSwipeEnabled()。这两个回调默认返回 true,如果不小心重写返回了false,就改过来吧。

    1. ItemTouchHelper.SimpleCallback :
    new SimpleCallback(int dragFlags,int swipeFlags) {
            @Override
            public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
                return false;
            }
    
            @Override
            public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
    
            }
    
        }
    

    ItemTouchHelper.SimpleCallback 继承自 ItemTouchHelper.Callback,仅仅是对两个触摸事件 flag 值设置的封装 , 提供了 set、get 方法,其他无异。

     public SimpleCallback(int dragDirs, int swipeDirs) {
                mDefaultSwipeDirs = swipeDirs;
                mDefaultDragDirs = dragDirs;
     }
    
    //不需要再写 getMovementFlags() 方法
     @Override
     public int getMovementFlags(RecyclerView recyclerView, ViewHolder viewHolder) {
                return makeMovementFlags(getDragDirs(recyclerView, viewHolder),
                        getSwipeDirs(recyclerView, viewHolder));
    }
    

    三、 处理拖拽后position错乱问题:

    解决问题的根本就是在拖拽操作结束后,刷新 adapter。

    • 不能在 onMove 回调中直接刷新,会打断 drag 操作。
    • 在 onSelectedChanged() 或 clearView() 回调刷新。
    • 建议使用 notifyItemRangeChanged(int start,int end) 方法刷新,可以减少不必要的刷新。
    1. onSelectedChanged() , 只会回调两次:手势事件产生 和 手势抬起并且动画结束。
    int startPosition;
    int endPosition;
     @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
          super.onSelectedChanged(viewHolder, actionState);
          
           if (viewHolder != null && actionState != ACTION_STATE_IDLE) {
               // 非闲置状态下,记录下起始 position
                startPosition = viewHolder.getAdapterPosition();
            }
            if(actionState == ACTION_STATE_IDLE){
               // 当手势抬起时刷新,endPosition 是在 onMove() 回调中记录下来的
                RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
                if (adapter != null) {
                    adapter.notifyItemRangeChanged(Math.min(startPosition, endPosition), Math.abs(startPosition - endPosition) + 1);
                 }
            }
    }
    

    在这里如果同时有 drag 和 swipe 两种情况,判断一下是 drag 还是 swipe 的结束好一些。

    1. clearView() , 只会触发一次:当手势抬起并且动画结束后回调,刷新操作放在这里也可以。
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
    }
    
    • Tips:onSelectedChanged() 比 clearView() 后调用,所以在手势抬起后,actionState 变为 ACTION_STATE_IDLE,然后 viewHolder 会在 clearView 中被置为null,最后才回调 onSelectedChanged()。

    写在最后

    有什么问题欢迎留言

    相关文章

      网友评论

        本文标题:android ItemTouchHelper 的使用和遇到的问

        本文链接:https://www.haomeiwen.com/subject/vonxvftx.html