欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

RecyclerView之使用ItemTouchHelper实现拖拽和滑动删除

程序员文章站 2022-05-15 19:51:44
...

一、简述

运用v7包中的ItemTouchHelper与RecyclerView结合后实现拖拽和滑动删除交互效果。

示例如下:

RecyclerView之使用ItemTouchHelper实现拖拽和滑动删除

二、创建项目(ItemTouchHelperDemo)

  1. 添加支持库:
    compile 'com.android.support:appcompat-v7:26.0.0-alpha1'
        compile 'com.android.support:cardview-v7:26.0.0-alpha1'
        compile 'com.android.support:recyclerview-v7:26.0.0-alpha1'
  2. 修改styles.xml文件:

    <resources>
    
        <!-- Base application theme. -->
        <style name="AppTheme" parent="android:Theme.Holo.Light.NoActionBar">
            <!-- Customize your theme here. -->
        </style>
    
    </resources>
  3. 创建布局资源文件:activity_item_touch_helper.xml:

    <?xml version="1.0" encoding="utf-8"?>
    <RelativeLayout
        xmlns:android="http://schemas.android.com/apk/res/android"
        xmlns:tools="http://schemas.android.com/tools"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="vertical"
        android:gravity="center"
        android:background="#dfdddd"
        tools:context="edu.lzy.lp.item_touch_helper_demo.ItemTouchHelperActivity">
    
        <android.support.v7.widget.RecyclerView
            android:id="@+id/recycler_view"
            android:layout_width="match_parent"
            android:layout_height="match_parent">
    
        </android.support.v7.widget.RecyclerView>
    
        <!--转换布局图标-->
        <ImageView
            android:id="@+id/switchover_layout"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/ic_action_switchover"
            android:onClick="doSwitchoverLayout"/>  
        <!--放大图标-->
        <ImageView
            android:id="@+id/iv_shrink"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:background="@drawable/ic_action_shrink"
            android:layout_alignParentRight="true"
            android:layout_marginLeft="10dp"
            android:onClick="doShrink"/>
        <!--缩小图标-->
        <ImageView
            android:id="@+id/iv_magnify"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content"
            android:layout_toLeftOf="@id/iv_shrink"
            android:background="@drawable/ic_action_magnify"
            android:onClick="doMagnify"/>
    
    </RelativeLayout>
  4. 创建一个列表项模板:item_card_view.xml
    此处运用了CardView
    compile 'com.android.support:cardview-v7:26.0.0-alpha1'(CardView版本号与项目里面V7版本号相同即可)

    红色标注部分是本案例要用到的

    简单简绍CardView的一些属性:
    app:cardBackgroundColor    设置背景颜色    注意:CardView中使用android:background设置背景颜色无效。
    app:cardCornerRadius         设置圆角大小
    app:cardElevation            设置z轴阴影高度
    app:cardMaxElevation         设置z轴最大高度值
    app:contentPadding           内容与边距的间隔
    app:contentPaddingLeft       内容与左边的间隔
    app:contentPaddingTop        内容与顶部的间隔
    app:contentPaddingRight      内容与右边的间隔
    app:contentPaddingBottom     内容与底部的间隔    
    app:paddingStart             内容与边距的间隔起始
    app:paddingEnd               内容与边距的间隔终止

    android:foreground=”?android:attr/selectableItemBackground”即可实现点击CardView出现波纹效果
    Cards一般都是可点击的,为此我们使用了foreground属性并使用系统的selectableItemBackground值,同时设置clickable为true(如果在java代码中使用了cardView.setOnClickListener,就可以不用写clickable属性了),从而达到在Lollipop及以上版本系统中实现点击时的涟漪效果(Ripple)

    setUseCompatPadding 属性,设置为true(默认值为false),让CardView在不同系统中使用相同的padding值
    app:cardPreventConrerOverlap 在API20及以下版本中添加内边距,这个属性为了防止内容和边角的重叠

    CardView详细讲解见:http://www.open-open.com/lib/view/open1476847497671.html

    <?xml version="1.0" encoding="utf-8"?>
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
                  xmlns:app="http://schemas.android.com/apk/res-auto"
                  xmlns:tools="http://schemas.android.com/tools"
                  android:layout_width="match_parent"
                  android:layout_height="wrap_content"
                  android:gravity="center"
                  android:orientation="vertical">
    
        <android.support.v7.widget.CardView
            android:id="@+id/waterfall_card"
            android:layout_width="match_parent"
            android:layout_height="match_parent"
            android:clickable="true"
            android:foreground="?android:attr/selectableItemBackground"
            app:cardBackgroundColor="#F4AFAF"
            app:cardCornerRadius="10dp"
            android:layout_margin="5dp"
            app:cardElevation="5dp"
            app:cardUseCompatPadding="true">
    
            <RelativeLayout
                android:layout_width="match_parent"
                android:layout_height="200dp"
                android:orientation="vertical">
    
                <ImageView
                    android:layout_width="match_parent"
                    android:layout_height="150dp"
                    android:background="@mipmap/img_bg"
                    android:layout_alignParentTop="true"/>
                <TextView
                    android:id="@+id/tv_number"
                    android:layout_width="match_parent"
                    android:layout_height="50dp"
                    android:layout_alignParentBottom="true"
                    android:gravity="center"
                    android:textColor="#f4eeee"
                    android:textSize="18sp"
                    tools:text="1"/>
                <!--加号图标-->
                <ImageView
                    android:id="@+id/iv_add"
                    android:layout_width="25dp"
                    android:layout_height="25dp"
                    android:layout_alignParentRight="true"
                    android:layout_alignParentTop="true"
                    android:padding="5dp"
                    android:layout_margin="5dp"
                    android:background="@drawable/ic_action_add"/>
            </RelativeLayout>
    
        </android.support.v7.widget.CardView>
    
    
    </LinearLayout>

5.创建类ItemTouchHelperActivity

import android.app.Activity;
import android.os.Bundle;
import android.support.v7.widget.DefaultItemAnimator;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.StaggeredGridLayoutManager;
import android.support.v7.widget.helper.ItemTouchHelper;
import android.view.View;
import android.widget.Toast;

import java.util.ArrayList;
import java.util.List;

public class ItemTouchHelperActivity extends Activity {

    private RecyclerView mRecyclerView;
    private boolean flag = true;
    /**
     * 线性布局管理器
     */
    private LinearLayoutManager linearLayoutManager;
    /**
     * 交错式网格布局管理器
     */
    private StaggeredGridLayoutManager staggeredGridLayoutManager;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_item_touch_helper);

        mRecyclerView = findViewById(R.id.recycler_view);
        List<Integer> dataList = new ArrayList<>();
        for(int i = 1;i<= 20;i++){
            dataList.add(i);
        }
        CustomRecyclerViewAdapter adapter = new CustomRecyclerViewAdapter(this,dataList);
        mRecyclerView.setAdapter(adapter);

        //线性布局管理器
        linearLayoutManager = new LinearLayoutManager(ItemTouchHelperActivity.this);
        //交错式网格布局管理器
        staggeredGridLayoutManager = new StaggeredGridLayoutManager(2,//跨数:也就是列数
                StaggeredGridLayoutManager.VERTICAL//方向:水平或垂直
        );
        mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
        //设置动画为默认动画
        mRecyclerView.setItemAnimator(new DefaultItemAnimator());
    }


    /**
     * 转换布局的事件处理方法
     * @param view
     */
    public void doSwitchoverLayout(View view) {
        if(flag){
            mRecyclerView.setLayoutManager(linearLayoutManager);
            flag = false;
        }else{
            mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
            flag = true;
        }
    }

    /**
     * 放大事件处理方法
     * 减少列数
     * @param view
     */
    public void doMagnify(View view) {
        if(mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager){
            int spanCount = ((StaggeredGridLayoutManager) mRecyclerView.getLayoutManager()).getSpanCount();
            if(spanCount<=1){
                Toast.makeText(this, "已放到最大!", Toast.LENGTH_SHORT).show();
                return;
            }
            staggeredGridLayoutManager = new StaggeredGridLayoutManager(spanCount-1,StaggeredGridLayoutManager.VERTICAL);
            mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
        }else{
            Toast.makeText(this, "当前布局不支持,请切换布局!", Toast.LENGTH_SHORT).show();
        }
    }

    /**
     * 缩小事件处理方法
     * 增加列数
     * @param view
     */
    public void doShrink(View view) {
        if(mRecyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager){
            int spanCount = ((StaggeredGridLayoutManager) mRecyclerView.getLayoutManager()).getSpanCount();
            if(spanCount>=6){
                Toast.makeText(this, "已缩到最小!", Toast.LENGTH_SHORT).show();
                return;
            }
            staggeredGridLayoutManager = new StaggeredGridLayoutManager(spanCount+1,StaggeredGridLayoutManager.VERTICAL);
            mRecyclerView.setLayoutManager(staggeredGridLayoutManager);
        }else{
            Toast.makeText(this, "当前布局不支持,请切换布局!", Toast.LENGTH_SHORT).show();
        }
    }

}
6.新建一个接口让Adapter实现它

从解耦的角度考虑,我们需要一个接口来实现Adapter和ItemTouchHelper之间涉及数据的操作,因为ItemTouchHelper在完成触摸的各种动画后,就要对Adapter的数据进行操作,比如侧滑删除操作,最后需要调用Adapter的notifyItemRemove()方法来移除该数据。因此我们可以把数据操作的部分抽象成一个接口方法,让ItemTouchHelper.Callback调用该方法即可。具体如下: 

创建接口ItemTouchHelperAdapter

public interface ItemTouchHelperAdapter {

    void onItemMove(int fromPosition,int toPosition);
    void onItemRemove(int position);

}
7.创建自定义适配器CustomRecyclerViewAdapter,继承自RecyclerView.Adapter、实现第7部中自定义的接口ItemTouchHelperAdapter

import android.content.Context;
import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;

import java.util.Collections;
import java.util.List;

public class CustomRecyclerViewAdapter extends RecyclerView.Adapter <CustomRecyclerViewAdapter.CustomViewHolder> implements ItemTouchHelperAdapter{

    private Context context;
    private List<Integer> dataList;

    public CustomRecyclerViewAdapter(Context context, List<Integer> dataList){
        this.context = context;
        this.dataList = dataList;
    }

    @Override
    public CustomViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View view = LayoutInflater.from(context).inflate(R.layout.item_card_view,parent,false);
        return new CustomViewHolder(view);
    }

    @Override
    public void onBindViewHolder(CustomViewHolder holder, int position) {
        holder.bindData(position);
        holder.ivAdd.setOnClickListener(new CustomOnClickListener(position));
    }

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

    /**
     *数据交换,自定义接口中的方法
     * @param fromPosition
     * @param toPosition
     */
    @Override
    public void onItemMove(int fromPosition, int toPosition) {
        Collections.swap(dataList,fromPosition,toPosition);
        notifyItemMoved(fromPosition,toPosition);
    }

    /**
     * 数据移除,自定义接口中的方法
     * @param position
     */
    @Override
    public void onItemRemove(int position) {
        dataList.remove(position);
        notifyItemRemoved(position);
    }

    /**
     * 自定义试图持有者
     */
    class CustomViewHolder extends RecyclerView.ViewHolder{

        private TextView tvNumber;
        private ImageView ivAdd;

        public CustomViewHolder(View itemView) {
            super(itemView);
            tvNumber = itemView.findViewById(R.id.tv_number);
            ivAdd = itemView.findViewById(R.id.iv_add);
        }

        public void bindData(int number){
            tvNumber.setText(""+number);
        }

    }

    /**
     * 自定义单击事件接听器类,实现单击事件接听器接口
     * 用于实现单击添加时,在该项后新添加一项
     */
    class CustomOnClickListener implements View.OnClickListener{

        private int position;

        public CustomOnClickListener(int position){
            this.position = position;
        }

        @Override
        public void onClick(View view) {
            dataList.add(position+1,position+1);
            notifyItemInserted(position+1);
            //待动画结束后通知数据集更新,防止由于复用而产生的显示错乱问题。
            new Handler().postDelayed(new Runnable() {
                @Override
                public void run() {
                    notifyDataSetChanged();
                }
            },500);
        }

    }

}

8.创建类CustomTouchHelperCallback让其继承自ItemTouchHelper.Callback
 使用ItemTouchHelper需要一个Callback,该Callback是ItemTouchHelper.Callback的子类,所以我们需要新建一个类比如     CustomTouchHelperCallback继承自ItemTouchHelper.Callback。我们可以重写其数个方法来实现我们的需求。
这里我们主要重写了四个方法:
public int getMovementFlags(RecyclerView, RecyclerView.ViewHolder)
public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) 
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) 
public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) 


import android.os.Handler;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.helper.ItemTouchHelper;

public class CustomTouchHelperCallback extends ItemTouchHelper.Callback {

    CustomRecyclerViewAdapter adapter;

    public CustomTouchHelperCallback(CustomRecyclerViewAdapter adapter){
        this.adapter = adapter;
    }

    /**
     * 方法用于返回可以滑动的方向,
     * 比如说允许从右到左侧滑,允许上下拖动等
     * @param recyclerView
     * @param viewHolder
     * @return
     */
    @Override
    public int getMovementFlags(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        //拖拽:允许上、下、左、右
        int dragFlags = ItemTouchHelper.UP|ItemTouchHelper.DOWN|ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
        //滑动:只允许左和有
        int swipeFlags = ItemTouchHelper.LEFT|ItemTouchHelper.RIGHT;
        return makeMovementFlags(dragFlags,swipeFlags);
    }

    /**
     * 当用户拖动一个Item进行上或下或左或右移动从旧的位置到新的位置的时候会调用该方法
     * 在该方法内,我们可以调用Adapter的notifyItemMoved方法来交换两个ViewHolder的位置,
     * 最后返回true,表示被拖动的ViewHolder已经移动到了目的位置。
     * @param recyclerView
     * @param viewHolder
     * @param target
     * @return
     */
    @Override
    public boolean onMove(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, RecyclerView.ViewHolder target) {
        adapter.onItemMove(viewHolder.getAdapterPosition(),target.getAdapterPosition());
        //待动画结束后通知数据集更新,防止由于复用而产生的显示错乱问题。
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                adapter.notifyDataSetChanged();
            }
        },500);
        return true;
    }

    /**
     *当用户左右滑动Item达到删除条件时,会调用该方法,一般手指触摸滑动的距离达到RecyclerView宽度的一半时,
     * 再松开手指,此时该Item会继续向原先滑动方向滑过去并且调用onSwiped方法进行删除,
     * 否则会反向滑回原来的位置。
     * @param viewHolder
     * @param direction
     */
    @Override
    public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
        adapter.onItemRemove(viewHolder.getAdapterPosition());
        //待动画结束后通知数据集更新,防止由于复用而产生的显示错乱问题。
        new Handler().postDelayed(new Runnable() {
            @Override
            public void run() {
                adapter.notifyDataSetChanged();
            }
        },500);
    }

    /**
     * 当用户操作完毕某个item并且其动画也结束后会调用该方法,
     * 一般我们在该方法内调用notifyDataSetChanged(),防止由于复用而产生的显示错乱问题。
     * @param recyclerView
     * @param viewHolder
     */
    @Override
    public void clearView(RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder) {
        super.clearView(recyclerView, viewHolder);

    }

}



如果在onSwiped方法内我们没有进行任何操作,即不删除已经滑过去的Item,那么就会留下空白的地方,因为实际上该ItemView还占据着该位置,只是移出了我们的可视范围内罢了。

一下几个是我们未重写的方法:
public boolean isLongPressDragEnabled():该方法返回true时,表示支持长按拖动,即长按ItemView后才可以拖动,我们遇到的场景一般也是这样的。默认是返回true。

public boolean boolean isItemViewSwipeEnabled():该方法返回true时,表示如果用户触摸并左右滑动了View,那么可以执行滑动删除操作,即可以调用到onSwiped()方法。默认是返回true。

public void onSelectedChanged(RecyclerView.ViewHolder viewHolder, int actionState):从静止状态变为拖拽或者滑动的时候会回调该方法,参数actionState表示当前的状态。

public void clearView(RecyclerView recyclerView, ViewHolder viewHolder):当用户操作完毕某个item并且其动画也结束后会调用该方法,一般我们在该方法内恢复ItemView的初始状态,防止由于复用而产生的显示错乱问题。

public void onChildDraw(Canvas c, RecyclerView recyclerView, RecyclerView.ViewHolder viewHolder, float dX, float dY, int actionState, boolean isCurrentlyActive)我们可以在这个方法内实现我们自定义的交互规则或者自定义的动画效果。

9.RecycleView添加ItemTouchHelper

给第五步中的ItemTouchHelperActivity类添加如下代码:

//先实例化Callback
        ItemTouchHelper.Callback callback = new CustomTouchHelperCallback(adapter);
        //用Callback构造ItemtouchHelper
        ItemTouchHelper touchHelper = new ItemTouchHelper(callback);
        //调用ItemTouchHelper的attachToRecyclerView方法建立联系
        touchHelper.attachToRecyclerView(mRecyclerView);
到此我们就能实现以上效果