android ItemTouchHelper 的使用和遇到的问题

转载请注明出处: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:clearViewon() 比 SelectedChanged() 后调用,所以在手势抬起后,actionState 变为 ACTION_STATE_IDLE,然后 viewHolder 会在 clearView 中被置为null。

写在最后

有什么问题欢迎留言

你可能感兴趣的:(android ItemTouchHelper 的使用和遇到的问题)