Android进阶七:RecyclerView拖动滑动之ItemTouchHelper

ItemTouchHelper

ItemTouchHelper是一个强大的工具,它处理好了关于在RecyclerView上添加拖动排序与滑动删除的所有事情。

它是RecyclerView.ItemDecoration的子类,也就是说它可以轻易的添加到几乎所有的LayoutManager和Adapter中。

它还可以和现有的item动画一起工作,提供受类型限制的拖放动画等等,

类介绍

主要涉及到ItemTouchHelperItemTouchHelper.Callback两个类

ItemTouchHelper主要对RecyclerView内的item的touch事件进行扩展。

ItemTouchHelper.Callback是一个抽象类,里面有一些抽象方法需要开发者去定制,包括在设置item在什么方向可以drag(拖动)和swipe(滑动),drag需要做什么,swipe需要做什么等,重写的方法如下:

1. getMovementFlags(RecyclerView, ViewHolder) 

2. onMove(RecyclerView, ViewHolder, ViewHolder)

3. onSwiped(ViewHolder, int)

4. onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState)

5. clearView(RecyclerView recyclerView,RecyclerView.ViewHolder viewHolder)

6. isLongPressDragEnabled()

7. isItemViewSwipeEnabled()

getMovementFlags()

这个方法是设置那些方向可以拖动和滑动RecyclerView中的item:

@Override
public int getMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}

dragFlags表示拖动flag,swipeFlags表示滑动flag。

onMove()

拖动item的回调方法,在这里面实现拖动需要做的事情,比如RecyclerView里面item的顺序交换,
传入的参数是被拖动item的ViewHolder和目标ViewHolder

onSwiped()

滑动item的回调方法,在这里面实现滑动需要做的事情,比如RecyclerView里面item的顺序交换

onSelectedChanged()

在每次item的状态变成拖拽 (ACTION_STATE_DRAG) 或者 滑动 (ACTION_STATE_SWIPE)的时候被调用。这是把你的item view变成激活状态的最佳地点。

clearView()

item被放开或者动画完成的回调

isLongPressDragEnabled()

设置是否长按才进入拖动

isItemViewSwipeEnabled()

设置是否支持滑动操作

基本使用

代码见GitHub:iPaulPro

步骤:

  • 扩展ItemTouchHelper.Callback类,重写getMovementFlags(),onMove(),onSwiped()等方法,类名SimpleItemTouchHelperCallback

  • 建立RecyclerView配套的Adapter类的回调接口,Adapter类实现该接口,用于进行item删除和交换,类名ItemTouchHelperAdapter

  • 建立ViewHolder的回调接口,ViewHolder类实现该接口,用于改变item选中和放开时候的背景改变,类名ItemTouchHelperViewHolder

  • 实例化ItemTouchHelper,绑定RecyclerView

1.建立SimpleItemTouchHelperCallback类

SimpleItemTouchHelperCallback继承ItemTouchHelper.Callback抽象类,重写getMovementFlags(),onMove(),onSwiped()等方法

public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback {
    private final ItemTouchHelperAdapter mAdapter;

    public SimpleItemTouchHelperCallback(ItemTouchHelperAdapter adapter) {
        mAdapter = adapter;
    }

    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
        final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
        return makeMovementFlags(dragFlags, swipeFlags);
    }

    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        if (viewHolder.getItemViewType() != target.getItemViewType()) {
            return false;
        }
         mAdapter.onItemMove(viewHolder.getAdapterPosition(), target.getAdapterPosition());
         return true;

    }

    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        mAdapter.onItemDismiss(viewHolder.getAdapterPosition());
    }

    @Override
    public boolean isLongPressDragEnabled() {
        return true;
    }

    @Override
    public boolean isItemViewSwipeEnabled() {
        return true;
    }


    @Override
    public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState) {
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
            //回调改变item的背景颜色
            itemViewHolder.onItemSelected();
        }

        super.onSelectedChanged(viewHolder, actionState);
    }

    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);

        ItemTouchHelperViewHolder itemViewHolder = (ItemTouchHelperViewHolder) viewHolder;
          //回调改变item的背景颜色
        itemViewHolder.onItemClear();
    }
}

2.建立ItemTouchHelperAdapter接口,Adapter类实现该接口

public interface ItemTouchHelperAdapter {

    //拖动item的回调
    void onItemMove(int fromPosition, int toPosition);
    //滑动item后删除的回调
    void onItemDismiss(int position);
}
public class RecyclerListAdapter extends RecyclerView.Adapter<RecyclerListAdapter.ItemViewHolder> implements ItemTouchHelperAdapter {
{
    ……
    //交换item
    @Override
    public void onItemMove(int fromPosition, int toPosition) {
        String prev = mItems.remove(fromPosition);
        mItems.add(toPosition > fromPosition ? toPosition - 1 : toPosition, prev);
        notifyItemMoved(fromPosition, toPosition);
    }
   //删除item
    @Override
    public void onItemDismiss(int position) {
        mItems.remove(position);
        notifyItemRemoved(position);
    }
    ……
}

回调方法的调用在步骤1的onMove()和onSwiped()方法中。

notifyItemRemoved()和 notifyItemMoved()的调用非常重要,有了它们Adapter才能知道发生了改变。

3.建立ItemTouchHelperViewHolder接口,ViewHolder类实现该接口

public interface ItemTouchHelperViewHolder {
    //item选中的时候的回调
    void onItemSelected();
    //item放开的时候的回调
    void onItemClear();
}
public class ItemViewHolder extends RecyclerView.ViewHolder implements ItemTouchHelperViewHolder
{
      ……
       //选中item,改变背景颜色
        @Override
        public void onItemSelected() {
            itemView.setBackgroundColor(Color.LTGRAY);
        }
       //放开item,改变背景颜色
        @Override
        public void onItemClear() {
            itemView.setBackgroundColor(0);
        }
}

回调方法的调用在步骤1的onSelectedChanged()和clearView()方法中。

4.实例化ItemTouchHelper,绑定RecyclerView

mRecyclerListAdapter = new RecyclerListAdapter(this);
mRecyclerView = (RecyclerView)findViewById(R.id.id_recycler);
mRecyclerView.setLayoutManager(new LinearLayoutManager(this));
mRecyclerView.setAdapter(mRecyclerListAdapter);

ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(mRecyclerListAdapter);
mItemTouchHelper = new ItemTouchHelper(callback);
mItemTouchHelper.attachToRecyclerView(mRecyclerView);

以上就可以实现item的拖动和滑动操作。

滑块

当设计一个支持拖拽与拖放的列表的时候,通常都会包含一个提示可以触摸拖动的东西。它对于用户发现此功能与软件的易用性都是有帮助的,
并且Material指南也推荐 在列表处于“编辑模式”的时候这样做。让我们的例子包含一个这样的滑块也相当简单。

1.layout添加滑块


<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical" android:layout_width="match_parent"
    android:layout_height="30dp"
    android:clickable="true"
    android:focusable="true"
    >
    <TextView
        android:id="@+id/id_text"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:layout_gravity="center_vertical"
        android:textColor="#000000"
        android:layout_marginLeft="16dp"
        android:textAppearance="?android:attr/textAppearanceMedium"/>
    <ImageView
        android:id="@+id/id_img"
        android:layout_width="wrap_content"
        android:layout_height="match_parent"
        android:layout_gravity="center_vertical|right"
        android:scaleType="center"
        android:src="@mipmap/ic_launcher"/>
FrameLayout>

id_img是在item中添加的一个图标,就是该滑块,当点击到这个滑块的时候,才能进行拖动操作。

2.定义接口,用于控制ItemTouchHelper直接开始执行拖动操作

public interface OnStartDragListener {

    void onStartDrag(RecyclerView.ViewHolder viewHolder);
}

3.RecyclerListAdapter添加id_img的触摸事件

public class RecyclerListAdapter extends RecyclerView.Adapter<RecyclerListAdapter.ItemViewHolder> implements ItemTouchHelperAdapter {
    private final OnStartDragListener mOnStartDragListener;
    @Override
    public void onBindViewHolder(final RecyclerListAdapter.ItemViewHolder holder, int position) {
        holder.textView.setText(mItems.get(position));
        holder.handleView.setOnTouchListener(new View.OnTouchListener() {
            @Override
            public boolean onTouch(View view, MotionEvent motionEvent) {
                if(MotionEventCompat.getActionMasked(motionEvent) == MotionEvent.ACTION_DOWN)
                {
                    mOnStartDragListener.onStartDrag(holder);
                }
                return false;
            }
        });
    }
}

4.Activity或Fragment实现OnStartDragListener接口,调用ItemTouchHelper的startDrag立即进行拖动

public class RecyclerListFragment extends Fragment implements RecyclerListAdapter.OnDragStartListener 
{
   ……
    @Override
    public void onStartDrag(RecyclerView.ViewHolder viewHolder) {
        mItemTouchHelper.startDrag(viewHolder);
    }
    ……
}

效果:

Grid 布局

如果你想用GridLayoutManager来修改这个项目,你会发现不能正常工作。原因和解决办法都很简单:我们必须告诉ItemTouchHelper我们想支持向左拖动和向右拖动。在InSimpleItemTouchHelperCallback中,我们已经指明了:

@Override
public int getMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN;
    int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END;
    return makeMovementFlags(dragFlags, swipeFlags);
}

要支持grid布局,唯一需要的修改是向dragFlags中添加left和 right方向。

int dragFlags = ItemTouchHelper.UP   | ItemTouchHelper.DOWN | 
              ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;

但是,对于grid而言,滑动删除不是非常自然的设计,因此你可能需要这样来去掉此功能:

@Override
public int getMovementFlags(RecyclerView recyclerView, 
        RecyclerView.ViewHolder viewHolder) {
    int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | 
                    ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
    int swipeFlags = 0;
    return makeMovementFlags(dragFlags, swipeFlags);
}

效果:

Android进阶七:RecyclerView拖动滑动之ItemTouchHelper_第1张图片

自定义滑动动画

ItemTouchHelper.Callback 提供了非常方便的方法来控制拖拽和滑动期间的view动画。因为ItemTouchHelper其实是一个RecyclerView.ItemDecoration,我可以

用同样的方式进行view的绘制。

在后面的部分,我们将更深入的讨论这个问题,但是这里也给出一个简单的例子,重写默认的滑动动画,显示线性淡化效果。

@Override
public void onChildDraw(Canvas c, RecyclerView recyclerView, 
        ViewHolder viewHolder, float dX, float dY, 
        int actionState, boolean isCurrentlyActive) {

    if (actionState == ItemTouchHelper.ACTION_STATE_SWIPE) {
        float width = (float) viewHolder.itemView.getWidth();
        float alpha = 1.0f - Math.abs(dX) / width;
        viewHolder.itemView.setAlpha(alpha);
        viewHolder.itemView.setTranslationX(dX);    
    } else {
        super.onChildDraw(c, recyclerView, viewHolder, dX, dY, 
                actionState, isCurrentlyActive);
    }
}

dX 与 dY参数代表目前被选择view 的移动距离,其中:

•-1.0f is a full ItemTouchHelper.END to ItemTouchHelper.STARTswipe

•1.0f is a full ItemTouchHelper.START to ItemTouchHelper.END swipe

为了不漏掉我们没有处理的actionState,记住务必调用super方法,这样其他的默认动画才会运行。

你可能感兴趣的:(进阶)