注: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)
致敬老前辈!!!