Android仿QQ列表左滑删除操作

发布时间 - 2026-01-10 21:50:56    点击率:

最近学习了如何做一个像QQ的左滑RecyclerView的item显示选项的,主要是用到Scroller

我们首先新建一个自己的RecyclerView

定义好一些要用的的变量
重写构造方法,把前两个构造方法改为如下,使无论如何构造都要执行第三个构造方法
在第三个构造方法里初始化Scroller

public class LeftSwipeMenuRecyclerView extends RecyclerView {

  //置顶按钮
  private TextView tvTop;
  //删除按钮
  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());
  }

重写onTouchEvent方法

event主要有以下几个Action

  • ACTION_DOWN 手指接触到屏幕
  • ACTION_MOVE 手指在屏幕滑动
  • ACTION_UP 手指离开屏幕

一开始肯定要获取x和y的相对坐标

int x= (int) event.getX();
int y= (int) event.getY();

然后接下来分别处理3个不同的行为

1.ACTION_DOWN

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;
          //这个view的整个置顶按钮
          tvTop = holder.tvTop;
          //两个按钮的宽度
          mMaxLength = tvDelete.getWidth() + tvTop.getWidth();

          //设置删除按钮点击监听
          tvDelete.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
              mItemLayout.scrollTo(0, 0);
              mMenuState =MENU_CLOSED;
              mListener.OnItemDelete(mPosition);
            }
          });
          //设置置顶按钮点击监听
          tvTop.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(View view) {
              mItemLayout.scrollTo(0, 0);
              mMenuState =MENU_CLOSED;
              mListener.OnItemTop(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;

2.ACTION_MOVE

      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;

3.ACTION_UP

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;

之后还要在onTouchEvent方法里刷新坐标

   //只有更新mLastX,mLastY数据才会准确
    mLastX = x;
    mLastY = y;
    return super.onTouchEvent(e);

因为我们用到了startScroll所以要重写computeScroll方法

  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;
      }
    }
  }

**还要监听RecyclerView是否在上下滑动

 @Override
  public void onScrollStateChanged(int state) {
    super.onScrollStateChanged(state);
    //是否在上下滑动
    isDragging = state == SCROLL_STATE_DRAGGING;
  }

还要设置Listener

//设置Listener
  public void setOnItemActionListener(OnItemActionListener onItemActionListener) {
    this.mListener = onItemActionListener;
  }

这个Listener是要自己新建的

public interface OnItemActionListener {
  void OnItemClick(int position);
  void OnItemTop(int position);
  void OnItemDelete(int position);
}

最后是点击,置顶,删除在Activity里的回调

这里只展示回调实现部分,我这里用的List是LinkedList,可以在第一位添加数据

rv.setOnItemActionListener(new OnItemActionListener() {
      //点击
      @Override
      public void OnItemClick(int position) {
        Toast.makeText(MainActivity.this,"Click"+position,Toast.LENGTH_SHORT).show();
      }
      //置顶
      @Override
      public void OnItemTop(int position) {
        //获得当前位置的内容
        String temp =list.get(position);
        //移除这个item
        list.remove(position);
        //把它添加到第一个
        list.addFirst(temp);
        //更新数据源
        adapter.notifyDataSetChanged();
      }
      //删除
      @Override
      public void OnItemDelete(int position) {
        list.remove(position);
        //更新数据源
        adapter.notifyDataSetChanged();
      }
    });

Adapter和布局的代码太简单我就不放出来了,大家可以到源码里看看有什么

效果图

源码地址:https://github.com/jkgeekJack/SlideLeftMenuRecyclerView

以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持。


# Android仿QQ列表左滑删除  # Android仿QQ列表  # Android左滑删除  # Android高仿QQ6.0侧滑删除实例代码  # 详解Android中实现ListView左右滑动删除条目的方法  # android RecyclerView侧滑菜单  # 滑动删除  # 长按拖拽  # 下拉刷新上拉加载  # Android仿微信滑动弹出编辑、删除菜单效果、增加下拉刷新功能  # Android实现左滑删除列表功能  # Android 实现左滑出现删除选项  # Android使用CardView作为RecyclerView的Item并实现拖拽和左滑删除  # Android 滑动监听RecyclerView线性流+左右划删除+上下移动  # Android开发中模仿qq列表信息滑动删除功能  # Android Recyclerview实现左滑删除功能  # 置顶  # 重写  # 不动  # 第三个  # 回调  # 始终保持  # 自己的  # 有什么  # 几个  # 我就  # 是在  # 都要  # 才会  # 我这  # 把它  # 要在  # 我们可以  # 就把  # 要用  # 很重要 


相关栏目: 【 网站优化151355 】 【 网络推广146373 】 【 网络技术251813 】 【 AI营销90571


相关推荐: Laravel如何保护应用免受CSRF攻击?(原理和示例)  Win11怎么设置默认图片查看器_Windows11照片应用关联设置  Laravel如何实现数据库事务?(DB Facade示例)  个人网站制作流程图片大全,个人网站如何注销?  如何快速搭建安全的FTP站点?  Laravel怎么实现一对多关联查询_Laravel Eloquent模型关系定义与预加载【实战】  手机怎么制作网站教程步骤,手机怎么做自己的网页链接?  如何用PHP工具快速搭建高效网站?  Laravel Eloquent性能优化技巧_Laravel N+1查询问题解决  简历在线制作网站免费版,如何创建个人简历?  美食网站链接制作教程视频,哪个教做美食的网站比较专业点?  Laravel如何使用Contracts(契约)进行编程_Laravel契约接口与依赖反转  Bootstrap整体框架之JavaScript插件架构  南京网站制作费用,南京远驱官方网站?  佐糖AI抠图怎样调整抠图精度_佐糖AI精度调整与放大细化操作【攻略】  Laravel storage目录权限问题_Laravel文件写入权限设置  如何在IIS中新建站点并解决端口绑定冲突?  如何在建站主机中优化服务器配置?  ChatGPT怎么生成Excel公式_ChatGPT公式生成方法【指南】  JavaScript如何实现音频处理_Web Audio API如何工作?  零基础网站服务器架设实战:轻量应用与域名解析配置指南  深入理解Android中的xmlns:tools属性  悟空识字如何进行跟读录音_悟空识字开启麦克风权限与录音  Python文件操作最佳实践_稳定性说明【指导】  浅谈redis在项目中的应用  Laravel如何实现事件和监听器?(Event & Listener实战)  使用spring连接及操作mongodb3.0实例  laravel怎么实现图片的压缩和裁剪_laravel图片压缩与裁剪方法  如何用AWS免费套餐快速搭建高效网站?  Laravel如何记录日志_Laravel Logging系统配置与自定义日志通道  如何在万网利用已有域名快速建站?  node.js报错:Cannot find module &#39;ejs&#39;的解决办法  如何快速选择适合个人网站的云服务器配置?  焦点电影公司作品,电影焦点结局是什么?  韩国代理服务器如何选?解析IP设置技巧与跨境访问优化指南  php读取心率传感器数据怎么弄_php获取max30100的心率值【指南】  如何快速完成中国万网建站详细流程?  如何确保西部建站助手FTP传输的安全性?  公司网站制作价格怎么算,公司办个官网需要多少钱?  百度输入法ai组件怎么删除 百度输入法ai组件移除工具  Zeus浏览器网页版官网入口 宙斯浏览器官网在线通道  简单实现jsp分页  Windows10电脑怎么查看硬盘通电时间_Win10使用工具检测磁盘健康  如何基于云服务器快速搭建个人网站?  lovemo网页版地址 lovemo官网手机登录  Laravel如何实现登录错误次数限制_Laravel自带LoginThrottles限流配置【方法】  利用vue写todolist单页应用  如何利用DOS批处理实现定时关机操作详解  谷歌Google入口永久地址_Google搜索引擎官网首页永久入口  *服务器网站为何频现安全漏洞?