安卓 RecyclerView的item出实现左滑删除功能

注:Java和kotlin混用了

第一种实现方式:自定义view


一、自定义RecyclerView -- LeftSwipeMenuRecyclerView.java

package 自己的包名;


import android.content.Context;
import android.util.AttributeSet;
import android.view.MotionEvent;
import android.view.View;
import android.view.animation.LinearInterpolator;
import android.widget.LinearLayout;
import android.widget.Scroller;
import android.widget.TextView;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.RecyclerView;

import org.yangbin.flow.adapter.RVAdapter;

public class LeftSwipeMenuRecyclerView extends RecyclerView {

    //删除按钮
    private TextView tvDelete;
    //item相应的布局
    private LinearLayout mItemLayout;
    //菜单的最大宽度
    private int mMaxLength;

    //上一次触摸行为的x坐标
    private int mLastX;
    //上一次触摸行为的y坐标
    private int mLastY;

    //当前触摸的item的位置
    private int mPosition;

    //是否在垂直滑动列表
    private boolean isDragging;
    //item是在否跟随手指移动
    private boolean isItemMoving;
    //item是否开始自动滑动
    private boolean isStartScroll;

    //左滑菜单状态   0:关闭 1:将要关闭 2:将要打开 3:打开
    private int mMenuState;
    private static int MENU_CLOSED = 0;
    private static int MENU_WILL_CLOSED = 1;
    private static int MENU_OPEN = 2;
    private static int MENU_WILL_OPEN = 3;

    //实现弹性滑动,恢复
    private Scroller mScroller;

    //item的事件监听
    private OnItemActionListener mListener;

    public LeftSwipeMenuRecyclerView(Context context) {
        this(context, null);
    }

    public LeftSwipeMenuRecyclerView(Context context, @Nullable AttributeSet attrs) {
        this(context, attrs, 0);
    }

    public LeftSwipeMenuRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) {
        super(context, attrs, defStyle);
        mScroller = new Scroller(context, new LinearInterpolator());
    }

    @Override
    public boolean onTouchEvent(MotionEvent e) {
        int x = (int) e.getX();
        int y = (int) e.getY();
        switch (e.getAction()) {
            case MotionEvent.ACTION_DOWN:
                if (mMenuState == MENU_CLOSED) {
                    //根据坐标获得view
                    View view = findChildViewUnder(x, y);
                    if (view == null) {
                        return false;
                    }
                    //获得这个view的ViewHolder
                    RVAdapter.Holder holder = (RVAdapter.Holder) getChildViewHolder(view);
                    //获得这个view的position
                    mPosition = holder.getAdapterPosition();
                    //获得这个view的整个布局
                    mItemLayout = holder.llLayout;
                    //获得这个view的删除按钮
                    tvDelete = holder.tvDelete;
                    //两个按钮的宽度
                    mMaxLength = tvDelete.getWidth();

                    //设置删除按钮点击监听
                    tvDelete.setOnClickListener(new OnClickListener() {
                        @Override
                        public void onClick(View view) {
                            mItemLayout.scrollTo(0, 0);
                            mMenuState =MENU_CLOSED;
                            mListener.OnItemDelete(mPosition);
                        }
                    });
                    //如果是打开状态,点击其他就把当前menu关闭掉
                } else if (mMenuState == MENU_OPEN) {
                    mScroller.startScroll(mItemLayout.getScrollX(), 0, -mMaxLength, 0, 200);
                    invalidate();
                    mMenuState = MENU_CLOSED;
                    //该点击无效
                    return false;
                } else {
                    return false;
                }
                break;
            case MotionEvent.ACTION_MOVE:
                //计算偏移量
                int dx = mLastX - x;
                int dy = mLastY - y;
                //当前滑动的x
                int scrollx = mItemLayout.getScrollX();

                if (Math.abs(dx) > Math.abs(dy)) {

                    isItemMoving = true;
                    //超出左边界则始终保持不动
                    if (scrollx + dx <= 0) {
                        mItemLayout.scrollTo(0, 0);
                        //滑动无效
                        return false;
                        //超出右边界则始终保持不动
                    } else if (scrollx + dx >= mMaxLength) {
                        mItemLayout.scrollTo(mMaxLength, 0);
                        //滑动无效
                        return false;
                    }
                    //菜单随着手指移动
                    mItemLayout.scrollBy(dx, 0);
                    //如果水平移动距离大于30像素的话,recyclerView不会上下滑动
                }else  if (Math.abs(dx) > 30){
                    return true;
                }
                //如果菜单正在打开就不能上下滑动
                if (isItemMoving){
                    mLastX = x;
                    mLastY = y;
                    return true;
                }
                break;
            case MotionEvent.ACTION_UP:
                //手指抬起时判断是否点击,静止且有Listener才能点击
                if (!isItemMoving && !isDragging && mListener != null) {
                    mListener.OnItemClick(mPosition);
                }
                isItemMoving = false;

                //等一下要移动的距离
                int deltaX = 0;
                int upScrollx = mItemLayout.getScrollX();
                //滑动距离大于1/2menu长度就自动展开,否则就关掉
                if (upScrollx >= mMaxLength / 2) {
                    deltaX = mMaxLength - upScrollx;
                    mMenuState = MENU_WILL_OPEN;
                } else if (upScrollx < mMaxLength / 2) {
                    deltaX = -upScrollx;
                    mMenuState = MENU_WILL_CLOSED;
                }
                //知道我们为什么不直接把mMenuState赋值为MENU_OPEN或者MENU_CLOSED吗?因为滑动时有时间的,我们可以在滑动完成时才把状态改为已经完成
                mScroller.startScroll(upScrollx, 0, deltaX, 0, 200);
                isStartScroll = true;
                //刷新界面
                invalidate();
                break;
        }
        //只有更新mLastX,mLastY数据才会准确
        mLastX = x;
        mLastY = y;
        return super.onTouchEvent(e);
    }

    @Override
    public void computeScroll() {
        //判断scroller是否完成滑动
        if (mScroller.computeScrollOffset()) {
            mItemLayout.scrollTo(mScroller.getCurrX(), mScroller.getCurrY());
            //这个很重要
            invalidate();
            //如果已经完成就改变状态
        } else if (isStartScroll) {
            isStartScroll = false;
            if (mMenuState == MENU_WILL_CLOSED) {
                mMenuState = MENU_CLOSED;
            }
            if (mMenuState == MENU_WILL_OPEN) {
                mMenuState = MENU_OPEN;
            }
        }
    }

    @Override
    public void onScrollStateChanged(int state) {
        super.onScrollStateChanged(state);
        //是否在上下滑动
        isDragging = state == SCROLL_STATE_DRAGGING;
    }
    //设置Listener
    public void setOnItemActionListener(OnItemActionListener onItemActionListener) {
        this.mListener = onItemActionListener;
    }
}

二、点击事件的回调接口 -- OnItemActionListener.kt

public interface OnItemActionListener {
    void OnItemClick(int position);

    void OnItemDelete(int position);

}

三、代码中recyclerview中适配器的Bean对象 -- Event.kt
 

class Event(var name: String, var completed: Boolean) {
}

四、recyclerView所在的xml文件


意思到了就行了,有些属性自己改一下就不贴出来了

<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout 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="match_parent"
    android:background="@mipmap/tab_bg"
    android:orientation="vertical"
    android:paddingHorizontal="@dimen/dp_20">

  

    <org.yangbin.flow.LeftSwipeMenuRecyclerView
        android:layout_width="match_parent"
        android:layout_height="0dp"
        android:id="@+id/rv"
        android:layout_marginBottom="@dimen/dp_20"
        android:background="@mipmap/tab2_layout_bg"
        android:paddingHorizontal="@dimen/dp_15"
        android:paddingTop="@dimen/dp_50"
        app:layout_constraintBottom_toBottomOf="parent"
        app:layout_constraintTop_toBottomOf="@+id/addEventImg"
        tools:ignore="MissingConstraints"/>


</androidx.constraintlayout.widget.ConstraintLayout>

五、item 布局 -- item_recyclerview.xml
 

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="100dp"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    android:orientation="horizontal">

    <!--ShapeLinearLayout 是我用的组件库里的,你们改成原生的LinearLayout就行,下面的ShapeConstraintLayout同理-->
    <com.hjq.shape.layout.ShapeLinearLayout
        android:id="@+id/llLayout"
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        android:orientation="horizontal"
        android:layout_marginTop="@dimen/dp_15"
        app:shape_solidGradientStartColor="#9AD0FC"
        app:shape_solidGradientEndColor="#E5F3FF"
        app:shape_radius="@dimen/dp_20">

        <!--ShapeConstraintLayout里面放的就是没有出现左滑按钮之前需要展示的内容-->
        <com.hjq.shape.layout.ShapeConstraintLayout
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            app:layout_constraintStart_toStartOf="parent"
            app:layout_constraintTop_toTopOf="parent">

            <ImageView
                android:id="@+id/eventStateImg"
                android:layout_width="@dimen/dp_40"
                android:layout_height="@dimen/dp_40"
                android:layout_marginStart="@dimen/dp_30"
                android:layout_marginTop="@dimen/dp_20"
                android:layout_marginBottom="@dimen/dp_20"
                android:src="@mipmap/event_mwc"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintStart_toStartOf="parent"
                app:layout_constraintTop_toTopOf="parent" />

            <TextView
                android:id="@+id/nameTv"
                android:layout_width="wrap_content"
                android:layout_height="wrap_content"
                android:textColor="@color/color_333333"
                android:textSize="@dimen/sp_14"
                app:layout_constraintBottom_toBottomOf="parent"
                app:layout_constraintEnd_toEndOf="parent"
                app:layout_constraintHorizontal_bias="0.2"
                app:layout_constraintStart_toEndOf="@+id/eventStateImg"
                app:layout_constraintTop_toTopOf="parent" />

        </com.hjq.shape.layout.ShapeConstraintLayout>

        <com.hjq.shape.view.ShapeTextView
            android:id="@+id/tvDelete"
            android:layout_width="100dp"
            android:textColor="@color/white"
            android:textSize="@dimen/sp_14"
            android:gravity="center"
            android:layout_height="match_parent"
            app:shape_solidGradientStartColor="#D94242"
            app:shape_solidGradientEndColor="#FBC16A"
            app:shape_radius="@dimen/dp_20"
            android:text="取消" />

    </com.hjq.shape.layout.ShapeLinearLayout>
</RelativeLayout>

六、创建adapter-- RVAdapter.java
 


import android.content.Context;
import android.content.SharedPreferences;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView;
import com.hjq.shape.view.ShapeTextView;
import org.yangbin.flow.R;
import org.yangbin.flow.bean.Event;
import java.util.ArrayList;
import java.util.List;

/**
 * Created by Administrator on 2016/8/15.
 */
public class RVAdapter extends RecyclerView.Adapter<RVAdapter.Holder> {
    private List<Event> list = new ArrayList<Event>();
    private Context context;

    private SharedPreferences.Editor edit;
    public RVAdapter(Context context, List<Event> list, SharedPreferences.Editor edit) {
        this.list = list;
        this.context = context;
        this.edit = edit;
    }


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

    @Override
    public void onBindViewHolder(Holder holder, int position) {
        Event event = list.get(position);
        if (event.getCompleted()){
            holder.eventStateImg.setImageResource(R.mipmap.event_wc);
        }else {
            holder.eventStateImg.setImageResource(R.mipmap.event_mwc);
            holder.eventStateImg.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    event.setCompleted(true);
                    edit.putBoolean("eventState"+(position+1),true).apply();
                    notifyDataSetChanged();
                }
            });
        }
        holder.nameTv.setText(event.getName());
    }


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

    public class Holder extends RecyclerView.ViewHolder {
        public TextView nameTv;
        public ImageView eventStateImg;
        public ShapeTextView tvDelete;

        public LinearLayout llLayout;

        public Holder(View itemView) {
            super(itemView);
            tvDelete = itemView.findViewById(R.id.tvDelete);
            nameTv = itemView.findViewById(R.id.nameTv);
            eventStateImg = itemView.findViewById(R.id.eventStateImg);
            llLayout = itemView.findViewById(R.id.llLayout);
        }
    }
}

七、具体使用
里面的edit是功能需要,不需要的自己改改,list就是 一个mutableListOf<Event>(),数据自己填充

 binding.rv.layoutManager = LinearLayoutManager(requireContext())
        val adapter = RVAdapter(requireContext(),list,edit)
        binding.rv.adapter = adapter

        binding.rv.setOnItemActionListener(object : OnItemActionListener {
            override fun OnItemClick(position: Int) {
                // Toast.makeText(requireContext(), "点击了$position", Toast.LENGTH_SHORT).show()
            }

            override fun OnItemDelete(position: Int) {
                list.removeAt(position)
                edit.apply {
                    putInt("eventNum",list.size)
                    remove("event${position+1}")
                    remove("eventState${position+1}")
                    apply()
                }
                adapter.notifyDataSetChanged()
            }
        })

第二中实现方式:第三方控件

1、导入第三方控件

api 'com.github.anzaizai:EasySwipeMenuLayout:1.1.4'

2、在xml文件中使用

    <com.guanaj.easyswipemenulibrary.EasySwipeMenuLayout
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        app:contentView="@+id/content"
        app:rightMenuView="@+id/right"
        tools:ignore="MissingConstraints">
        
        <androidx.constraintlayout.widget.ConstraintLayout
            android:id="@+id/content"
            android:layout_width="match_parent"
            android:layout_height="wrap_content">
            

        </androidx.constraintlayout.widget.ConstraintLayout>

        <LinearLayout
            android:id="@+id/right"
            android:layout_width="wrap_content"
            android:layout_height="match_parent"
            android:background="@android:color/holo_red_light"
            android:orientation="horizontal">

            <TextView
                android:id="@+id/tvDel"
                android:layout_width="@dimen/dp_80"
                android:layout_height="match_parent"
                android:background="@android:color/holo_red_light"
                android:gravity="center"
                android:text="删除"
                android:textColor="@color/white" />

        </LinearLayout>
    </com.guanaj.easyswipemenulibrary.EasySwipeMenuLayout>

说明:EasySwipeMenuLayout布局支持两个子布局,第一个布局就是没有左滑时显示的布局,第二个布局就是左滑后显示出来的布局

方式1借鉴自:Android仿QQ列表左滑删除操作_Android_脚本之家 (jb51.net)
致敬老前辈!!!

评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小趴菜8227

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值