Android随笔-RecyclerView实现拖拽换位功能

概述

RecyclerView除了有强大的列表功能外,自身还带有Item拖拽和滑动功能,对于有这方面需求的开发来讲,可以节省不少时间。

使用

1. 创建RecyclerView

    <androidx.recyclerview.widget.RecyclerView
        android:id="@+id/rv_drag"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:padding="20dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent" />

2. 创建ItemView

    <androidx.cardview.widget.CardView
        android:layout_width="66dp"
        android:layout_height="66dp"
        app:cardBackgroundColor="#1dd9c4"
        app:cardCornerRadius="12dp"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintEnd_toEndOf="parent"
        app:layout_constraintStart_toStartOf="parent"
        app:layout_constraintTop_toTopOf="parent">

        <TextView
            android:id="@+id/tv_content"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:gravity="center"
            android:textColor="@color/white"
            android:textSize="36sp"
            android:textStyle="bold" />

    </androidx.cardview.widget.CardView>

3. 创建Adapter

public class DragAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private List data = new ArrayList();
    private Context context;

    public DragAdapter(Context context) {
        this.context = context;
        initData();
    }
        /**
     * 添加数据
     */
    private void initData() {
        data.clear();
        for (int i = 1; i < 10; i++) {
            data.add(i);
        }
    }

    @NonNull
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
        View inflate = LayoutInflater.from(context).inflate(R.layout.item_drag, null);
        return new CardViewHold(inflate);
    }

    @Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
        CardViewHold viewHold = (CardViewHold) holder;
        viewHold.tvContent.setText("" + data.get(position));

    }

    @Override
    public int getItemCount() {
        return data.size();
    }

    class CardViewHold extends RecyclerView.ViewHolder {
        TextView tvContent;

        public CardViewHold(@NonNull View itemView) {
            super(itemView);
            tvContent = itemView.findViewById(R.id.tv_content);
        }
    }
}

4. 设置Adapter

        // 表格布局,3列
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(gridLayoutManager);
        DragAdapter dragAdapter = new DragAdapter(this);
        recyclerView.setAdapter(dragAdapter);

设置为3x3的表格布局。

5. 定义拖拽事件

public interface ItemDragTouchHelper {
    /**
     * 交换
     *
     * @param source  
     * @param target
     */
    void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target);

    /**
     * 选中
     *
     * @param source
     */
    void onItemSelect(RecyclerView.ViewHolder source);

    /**
     * 状态清除
     *
     * @param source
     */
    void onItemClear(RecyclerView.ViewHolder source);
}

定义三种事件,Item交换,Item被选中,Item选中状态清除。若是有其他需求,可以自行添加,比如删除,左右滑动等。

6. 实现拖拽事件回调

public class ItemDragTouchHelperCallback extends ItemTouchHelper.Callback {
    private ItemDragTouchHelper helper;

    public ItemDragTouchHelperCallback(ItemDragTouchHelper helper) {
        this.helper = helper;
    }

    @Override
    public int getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        // 上下左右都可以拖拽
        int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN | ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT;
        return makeMovementFlags(dragFlags, 0);
    }

    @Override
    public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
        helper.onItemMove(viewHolder, target);
        return true;
    }

    @Override
    public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {

    }

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

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


    @Override
    public void clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);
        if (!recyclerView.isComputingLayout()) {
            helper.onItemClear(viewHolder);
        }
    }

    @Override
    public void onSelectedChanged(@Nullable RecyclerView.ViewHolder viewHolder, int actionState) {
        super.onSelectedChanged(viewHolder, actionState);
        if (actionState != ItemTouchHelper.ACTION_STATE_IDLE) {
            helper.onItemSelect(viewHolder);
        }
    }
}

通过继承ItemTouchHelper.Callback可以实现具体的拖拽功能回调。这里设置的是可以在四个方向进行长按拖拽,但不可滑动。

getMovementFlags(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder)
 /**
         * Should return a composite flag which defines the enabled move directions in each state
         * (idle, swiping, dragging).
         * <p>
         * Instead of composing this flag manually, you can use {@link #makeMovementFlags(int,
         * int)}
         * or {@link #makeFlag(int, int)}.
         * <p>
         * This flag is composed of 3 sets of 8 bits, where first 8 bits are for IDLE state, next
         * 8 bits are for SWIPE state and third 8 bits are for DRAG state.
         * Each 8 bit sections can be constructed by simply OR'ing direction flags defined in
         * {@link ItemTouchHelper}.
         * <p>
         * For example, if you want it to allow swiping LEFT and RIGHT but only allow starting to
         * swipe by swiping RIGHT, you can return:
         * <pre>
         *      makeFlag(ACTION_STATE_IDLE, RIGHT) | makeFlag(ACTION_STATE_SWIPE, LEFT | RIGHT);
         * </pre>
         * This means, allow right movement while IDLE and allow right and left movement while
         * swiping.
         *
         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached.
         * @param viewHolder   The ViewHolder for which the movement information is necessary.
         * @return flags specifying which movements are allowed on this ViewHolder.
         * @see #makeMovementFlags(int, int)
         * @see #makeFlag(int, int)
         */
        public abstract int getMovementFlags(@NonNull RecyclerView recyclerView,
                @NonNull ViewHolder viewHolder);
  • ItemTouchHelper.UP :往上拖
  • ItemTouchHelper.DOWN:往下拖
  • ItemTouchHelper.LEFT:往左拖
  • ItemTouchHelper.RIGHT:往右拖

同时取这几个值就可以实现多个方向的拖拽,若是对方向有要求可以根据需要单独设置。

makeMovementFlags(int dragFlags, int swipeFlags)

        /**
         * Convenience method to create movement flags.
         * <p>
         * For instance, if you want to let your items be drag & dropped vertically and swiped
         * left to be dismissed, you can call this method with:
         * <code>makeMovementFlags(UP | DOWN, LEFT);</code>
         *
         * @param dragFlags  The directions in which the item can be dragged.
         * @param swipeFlags The directions in which the item can be swiped.
         * @return Returns an integer composed of the given drag and swipe flags.
         */
        public static int makeMovementFlags(int dragFlags, int swipeFlags) {
            return makeFlag(ACTION_STATE_IDLE, swipeFlags | dragFlags)
                    | makeFlag(ACTION_STATE_SWIPE, swipeFlags)
                    | makeFlag(ACTION_STATE_DRAG, dragFlags);
        }
  • dragFlags:拖拽方向
  • swipeFlags:滑动方向
onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target)

        /**
         * Called when ItemTouchHelper wants to move the dragged item from its old position to
         * the new position.
         * <p>
         * If this method returns true, ItemTouchHelper assumes {@code viewHolder} has been moved
         * to the adapter position of {@code target} ViewHolder
         * ({@link ViewHolder#getAbsoluteAdapterPosition()
         * ViewHolder#getAdapterPositionInRecyclerView()}).
         * <p>
         * If you don't support drag & drop, this method will never be called.
         *
         * @param recyclerView The RecyclerView to which ItemTouchHelper is attached to.
         * @param viewHolder   The ViewHolder which is being dragged by the user.
         * @param target       The ViewHolder over which the currently active item is being
         *                     dragged.
         * @return True if the {@code viewHolder} has been moved to the adapter position of
         * {@code target}.
         * @see #onMoved(RecyclerView, ViewHolder, int, ViewHolder, int, int, int)
         */
        public abstract boolean onMove(@NonNull RecyclerView recyclerView,
                @NonNull ViewHolder viewHolder, @NonNull ViewHolder target);
  • recyclerView:有长按拖拽功能的recyclerView
  • viewHolder:手指长按后被拖拽的Item
  • target:拖拽目标位置的Item

当返回true时,进行拖拽从一个位置到另一个位置会调用该方法,具体变换逻辑用户自行处理。

onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction)

        /**
         * Called when a ViewHolder is swiped by the user.
         * <p>
         * If you are returning relative directions ({@link #START} , {@link #END}) from the
         * {@link #getMovementFlags(RecyclerView, ViewHolder)} method, this method
         * will also use relative directions. Otherwise, it will use absolute directions.
         * <p>
         * If you don't support swiping, this method will never be called.
         * <p>
         * ItemTouchHelper will keep a reference to the View until it is detached from
         * RecyclerView.
         * As soon as it is detached, ItemTouchHelper will call
         * {@link #clearView(RecyclerView, ViewHolder)}.
         *
         * @param viewHolder The ViewHolder which has been swiped by the user.
         * @param direction  The direction to which the ViewHolder is swiped. It is one of
         *                   {@link #UP}, {@link #DOWN},
         *                   {@link #LEFT} or {@link #RIGHT}. If your
         *                   {@link #getMovementFlags(RecyclerView, ViewHolder)}
         *                   method
         *                   returned relative flags instead of {@link #LEFT} / {@link #RIGHT};
         *                   `direction` will be relative as well. ({@link #START} or {@link
         *                   #END}).
         */
        public abstract void onSwiped(@NonNull ViewHolder viewHolder, int direction);

  • viewHolder:滑动Item
  • direction:滑动方向,建议使用START or END代替LEFT or RIGHT

若是没有滑动功能就不做处理。

isLongPressDragEnabled

返回true可长按拖动,即本文指的拖拽功能。

isItemViewSwipeEnabled

返回false即Item不可滑动。

clearView(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder)

        /**
         * Called by the ItemTouchHelper when the user interaction with an element is over and it
         * also completed its animation.
         * <p>
         * This is a good place to clear all changes on the View that was done in
         * {@link #onSelectedChanged(RecyclerView.ViewHolder, int)},
         * {@link #onChildDraw(Canvas, RecyclerView, ViewHolder, float, float, int,
         * boolean)} or
         * {@link #onChildDrawOver(Canvas, RecyclerView, ViewHolder, float, float, int, boolean)}.
         *
         * @param recyclerView The RecyclerView which is controlled by the ItemTouchHelper.
         * @param viewHolder   The View that was interacted by the user.
         */
        public void clearView(@NonNull RecyclerView recyclerView, @NonNull ViewHolder viewHolder) {
            ItemTouchUIUtilImpl.INSTANCE.clearView(viewHolder.itemView);
        }

交互完成后调用该方法,此时可以做相关处理,关闭动画,开启其他动画,恢复正常状态等,但需要等RecycleView计算布局完成后再。

onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState)

        /**
         * Called when the ViewHolder swiped or dragged by the ItemTouchHelper is changed.
         * <p/>
         * If you override this method, you should call super.
         *
         * @param viewHolder  The new ViewHolder that is being swiped or dragged. Might be null if
         *                    it is cleared.
         * @param actionState One of {@link ItemTouchHelper#ACTION_STATE_IDLE},
         *                    {@link ItemTouchHelper#ACTION_STATE_SWIPE} or
         *                    {@link ItemTouchHelper#ACTION_STATE_DRAG}.
         * @see #clearView(RecyclerView, RecyclerView.ViewHolder)
         */
        public void onSelectedChanged(@Nullable ViewHolder viewHolder, int actionState) {
            if (viewHolder != null) {
                ItemTouchUIUtilImpl.INSTANCE.onSelected(viewHolder.itemView);
            }
        }
  • viewHolder:被选中的Item
  • actionState:行为状态, ACTION_STATE_IDLE, ACTION_STATE_SWIPE :滑动行为,ACTION_STATE_DRAG:拖拽行为

Item被选中后调用该方法,作用和clearView(RecyclerView, RecyclerView.ViewHolder)差不多。
需要重写的方法还有一些,可根据具体需求选择性重写。

7. Adapter进行具体的拖拽逻辑处理

public class DragAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements ItemDragTouchHelper {
    ...
    @Override
    public void onItemMove(RecyclerView.ViewHolder source, RecyclerView.ViewHolder target) {
        int fromPosition = source.getBindingAdapterPosition();
        int toPosition = target.getBindingAdapterPosition();
        if (fromPosition < data.size() && toPosition < data.size()) {
            Collections.swap(data, fromPosition, toPosition);
            notifyItemMoved(fromPosition, toPosition);
        }
        onItemClear(source);
    }

    @Override
    public void onItemSelect(RecyclerView.ViewHolder source) {
        source.itemView.setScaleX(1.2f);
        source.itemView.setScaleY(1.2f);
    }

    @Override
    public void onItemClear(RecyclerView.ViewHolder source) {
        source.itemView.setScaleX(1.0f);
        source.itemView.setScaleY(1.0f);
    }
}

通过让Adapter实现ItemDragTouchHelper接口,进行具体的逻辑逻辑处理。

  • onItemMove:通过获取被拖拽的item和目标item的绑定位置,通过swap(List<?> list, int i, int j)对数据进行交换,再调用notifyItemMoved进行页面酸性和换位动画。
  • onItemSelect:Item被选中后让被选中的Item变大为原来的1.2倍。
  • onItemClear:换位完成后,让被选中的Item恢复到原来的样子。

8. 开启拖拽功能

     // 表格布局,3列
        GridLayoutManager gridLayoutManager = new GridLayoutManager(this, 3);
        recyclerView.setLayoutManager(gridLayoutManager);
        // 默认动画
        recyclerView.setItemAnimator(new DefaultItemAnimator());
        // 设置item偏移量
        recyclerView.addItemDecoration(new RecyclerView.ItemDecoration() {
            @Override
            public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
                super.getItemOffsets(outRect, view, parent, state);
                // 边距16
                outRect.set(16, 16, 16, 16);
            }
        });
        DragAdapter dragAdapter = new DragAdapter(this);
        recyclerView.setAdapter(dragAdapter);
        // 适配器添加拖拽回调
        ItemTouchHelper.Callback callback = new ItemDragTouchHelperCallback(dragAdapter);
        ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback);
        // 为recyclerView添加拖拽功能
        itemTouchHelper.attachToRecyclerView(recyclerView);
  • addItemDecoration:设置item的装饰,这里设置上下左右的偏移量为16px,若有特殊需要用户可自定义ItemDecoration。
  • ItemDragTouchHelperCallback(dragAdapter):创建之前定义好的拖拽逻辑处理回调。
  • attachToRecyclerView:连接RecyclerView的拖拽事件,实现定义好的功能。

预览

请添加图片描述

以下是一个简单的示例代码,演示如何实现两个RecyclerView之间的拖拽添加item功能: ```java public class MainActivity extends AppCompatActivity implements ItemTouchHelperAdapter { private RecyclerView recyclerView1, recyclerView2; private Adapter1 adapter1; private Adapter2 adapter2; private List<String> data1 = new ArrayList<>(); private List<String> data2 = new ArrayList<>(); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); recyclerView1 = findViewById(R.id.recyclerView1); recyclerView2 = findViewById(R.id.recyclerView2); // 初始化数据 for (int i = 0; i < 10; i++) { data1.add("item " + i); data2.add("item " + (i + 10)); } // 创建Adapter和LayoutManager adapter1 = new Adapter1(data1, this); adapter2 = new Adapter2(data2, this); LinearLayoutManager layoutManager1 = new LinearLayoutManager(this); LinearLayoutManager layoutManager2 = new LinearLayoutManager(this); recyclerView1.setLayoutManager(layoutManager1); recyclerView2.setLayoutManager(layoutManager2); recyclerView1.setAdapter(adapter1); recyclerView2.setAdapter(adapter2); // 创建ItemTouchHelper ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(this); ItemTouchHelper itemTouchHelper = new ItemTouchHelper(callback); itemTouchHelper.attachToRecyclerView(recyclerView1); itemTouchHelper.attachToRecyclerView(recyclerView2); } // 实现ItemTouchHelperAdapter接口 @Override public void onItemMove(int fromPosition, int toPosition) { // 根据拖拽位置,将数据从一个RecyclerView的Adapter中删除,添加到另一个RecyclerView的Adapter中 if (fromPosition < data1.size()) { String item = data1.remove(fromPosition); data2.add(toPosition, item); adapter1.notifyItemRemoved(fromPosition); adapter2.notifyItemInserted(toPosition); } else { String item = data2.remove(fromPosition - data1.size()); data1.add(toPosition, item); adapter2.notifyItemRemoved(fromPosition - data1.size()); adapter1.notifyItemInserted(toPosition); } } @Override public void onItemDismiss(int position) { // 根据删除的位置,从另一个RecyclerView的Adapter中删除对应的数据 if (position < data1.size()) { data2.remove(position); adapter2.notifyItemRemoved(position); } else { data1.remove(position - data1.size()); adapter1.notifyItemRemoved(position - data1.size()); } } } ``` 在上述代码中,Adapter1和Adapter2分别是两个RecyclerView的Adapter,SimpleItemTouchHelperCallback是ItemTouchHelper的一个回调类,用于实现拖拽和删除item的功能实现拖拽添加item的方法在MainActivity中的onItemMove()方法中,根据拖拽位置,将数据从一个RecyclerView的Adapter中删除,添加到另一个RecyclerView的Adapter中。删除item的方法在MainActivity中的onItemDismiss()方法中,根据删除的位置,从另一个RecyclerView的Adapter中删除对应的数据。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值