我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android仿QQ列表滑动删除操作

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android仿QQ列表滑动删除操作

这篇山寨一个新版QQ的列表滑动删除,上篇有说到QQ的滑动删除,推测原理就是ListView本身每个item存在一个Button,只不过普通的状态下隐藏掉了,检测到向左的滑动事件的时候弹出隐藏的Button,不过再切换Button状态的时候会给Button一个出现和隐藏的动画。下面实现这个ListView。 

首先有个难点就是通过ListView获取它某个item的View,对于ViewGroup,可以直接调用getChildAt()方法获取对应的子view,但是在ListView直接使用getChildAt()的话,会发现只要滑动ListView就会报空指针异常,很明显对于ListView直接使用getChildAt()方法是行不通的,虽然ListView就是个ViewGroup。已经有人解释了这个问题以及解决方法,大概意思就是可以理解为,ListView虽然看上去有很多item,但是这只是看上去而已,实际上ListView只构造了你能看到的,就是屏幕上能看到的那么多item的view,所以要获取ListView某一个位置position的item的view,就需要用如下的代码:


int firstVisiblePos = getFirstVisiblePosition() - getHeaderViewsCount();
int factPos = curPos - firstVisiblePos;
 mItemView = getChildAt(factPos);

就是先获取ListView当前第一个可见的item的firstVisiblePos,当然啦,还要记得减去header view的数目,然后用想获取的item的curPos减去firstVisiblePos就是对应的item实际在ListView的位置factPos了。这下就不会报空指针异常了。

知道了获取某一个位置的item的view,现在就需要通过检测滑动事件,判断当前是在和ListView哪个position的item交互。使用ListView中如下方法:

int curPos = pointToPosition((int)curX, (int)curY);       

接下来就是截获ListView的touch事件了,自定义一个SlidingDeleteListView,继承自ListView,重写onTouchEvent()方法:
 


  @Override
  public boolean onTouchEvent(MotionEvent event) {
    if(!mEnableSliding)
      return false;
    if(mCancelMotionEvent && event.getAction() == MotionEvent.ACTION_MOVE) {
      return true;
    } else if(mCancelMotionEvent && event.getAction() == MotionEvent.ACTION_DOWN) {
      event.setAction(MotionEvent.ACTION_CANCEL);
    }
    switch(event.getAction()) {
      case MotionEvent.ACTION_DOWN: {
        if(mTracker == null)
          mTracker = VelocityTracker.obtain();
        else
          mTracker.clear();
        mLastMotionX = event.getX();
        mLastMotionY = event.getY();
      }break;
      case MotionEvent.ACTION_MOVE: {
        mTracker.addMovement(event);
        mTracker.computeCurrentVelocity(1000);
        int curVelocityX = (int) mTracker.getXVelocity();
        float curX = event.getX();
        float curY = event.getY();
        int lastPos = pointToPosition(
            (int)mLastMotionX, (int)mLastMotionY);
        int curPos = pointToPosition((int)curX, (int)curY);
        int distanceX = (int)(mLastMotionX - curX);
        if(lastPos == curPos && (distanceX >= MAX_DISTANCE || curVelocityX < -MAX_FLING_VELOCITY)) {
          int firstVisiblePos = getFirstVisiblePosition() - getHeaderViewsCount();
          int factPos = curPos - firstVisiblePos;
          mItemView = getChildAt(factPos);
          if(mItemView != null) {
            if(mButtonID == -1)
              throw new IllegalButtonIDException("Illegal DeleteButton resource id,"
                  + "ensure excute the function setButtonID(int id)");
            mButton = mItemView.findViewById(mButtonID);
            mButton.setVisibility(View.VISIBLE);
            mButton.startAnimation(mShowAnim);
            mLastButtonShowingPos = curPos;
            mButton.setOnClickListener(new View.OnClickListener() {
              @Override
              public void onClick(View v) {
                if(mDeleteItemListener != null)
                  mDeleteItemListener.onButtonClick(v, mLastButtonShowingPos);
                mButton.setVisibility(View.GONE);
                mLastButtonShowingPos = -1;
              }
            });
            mCancelMotionEvent = true;
          }
        }
      }break;
      case MotionEvent.ACTION_UP: {
        if(mTracker != null) {
          mTracker.clear();
          mTracker.recycle();
          mTracker = null;
        }
        mCancelMotionEvent = false;
        if(mLastButtonShowingPos != -1) {
          event.setAction(MotionEvent.ACTION_CANCEL);
        }
      }break;
      case MotionEvent.ACTION_CANCEL: {
        hideShowingButtonWithAnim();
      }break;
    }
    return super.onTouchEvent(event);
  }

解释上面代码之前先简单说一下android的touch事件的分发原理,主要是MotionEvent.ACTION_DOWN这个事件是最重要的,事件的分发有一来一回两部分,“来”是指ViewGroup获取到系统传递过来的ACTION_DOWN事件,先调用ViewGroup的onInterceptTouchEvent()方法,这个方法表示这个事件ViewGroup是否想截获,如果返回true的话,则会将ACTION_DOWN事件分发到ViewGroup的onTouchEvent()方法进行处理了,表示该事件被父view截获掉了,子view将不再会获取到事件。而如果ViewGroup的onInterceptTouchEvent()方法返回false则意味ViewGroup不截获该事件,接下来事件发生的位置存在子view的话,ViewGroup会将该ACTION_DOWN事件传递给该子view进行处理。这个过程是事件的分发过程,接下来是“回”,”回“这个过程是事件的消耗过程,子view的onTouchEvent()方法如果返回true,表示该ACTION_DOWN事件被该子view消耗了,则ViewGroup将不会在onTouchEvent()方法接收到该事件了,因为该事件被消耗了。如果子view的onTouchEvent()方法返回false表示子view不消耗该ACTION_DOWN事件(当然啦,子view依然可以处理该事件,但是返回false依然会把事件抛回给ViewGroup,这就可以做很多事了),之后事件会返回给父view。最终MotionEvent.ACTION_DOWN事件在哪一层的view消耗了,则接下来的后续touch事件,如ACTION_UP、ACTION_MOVE、ACTION_CANCEL等事件都将会直接传递给消耗ACTION_DOWN事件的view,其他层的view将不再受到后续的事件,直到下一次的ACTION_DOWN事件。 

以上的代码,暂时关注switch的代码块,对于检测到MotionEvent.ACTION_DOWN事件的时候,记录下当前touch事件的位置,同时我们先获取mTracker,这是一个VelocityTracker对象,android提供的用于计算当前滑动事件的速率的;检测到MotionEvent.ACTION_MOVE事件,我们有两个情况下确定要处理,一种情况是用户在滑动一定距离就弹出button,这个距离是当前滑动的位置和本次ACTION_DOWN记录下的事件位置的距离,第二中情况是用户滑动速度超过一个阈值的时候,弹出button,这个速度的计算就是用前面提到的mTracker了,用法很简单;检测到ACTION_UP事件表示当前的这次交互完成,我们可以做一些清理工作;至于ACTION_CANCEL事件,这个这里暂且买个关子,这个使用个偷梁换柱的小技巧欺负一下系统~ 

上面的ACTION_MOVE事件里面如果处理了事件,弹出了button,那我们在下次检测到ACTION_DOWN事件,如果这个事件发生的位置没有在button的区域,则表示用户不是点击弹出的button,那我们需要gone掉这个button,即在此隐藏它。那这里就需要使用带前面提及的ViewGroup的onInterceptTouchEvent()方法,在这次的ACTION_DOWN事件传递给子view前截获它,当然先判断一下这次的事件是不是点击button的事件: 
private boolean isClickButton(MotionEvent ev) {


    mButton.getLocationOnScreen(mShowingButtonLocation);
    int left = mShowingButtonLocation[0];
    int right = mShowingButtonLocation[0] + mButton.getWidth();
    int top = mShowingButtonLocation[1];
    int bottom = mShowingButtonLocation[1] + mButton.getHeight();
    return (ev.getRawX() >= left
        && ev.getRawX() <= right
        && ev.getRawY() >= top
        && ev.getRawY() <= bottom);
  }    接下来重写onInterceptTouchEvent()方法: 
  @Override
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    if(mEnableSliding && mLastButtonShowingPos != -1 &&
        ev.getAction() == MotionEvent.ACTION_DOWN && !isClickButton(ev)) {
      ev.setAction(MotionEvent.ACTION_CANCEL);
      mCancelMotionEvent = true;
      return true;
    }
    return super.onInterceptTouchEvent(ev);
  };  

判断要不要截获ACTION_DOWN事件,一先判断当前有没有button有弹出,因为每次弹出一个button,会记下当前弹出的item的位置mLastButtonShowingPos;然后就是当前是不是ACTION_DOWN事件;以及是否点击弹出的button。所有条件符合,我们就截获这个ACTION_DOWN事件,在onInterceptTouchEvent()方法return true。这样该ACTION_DOWN事件就会传递到本SlidingDeleteListView的onTouchEvent()方法里面,这里再解释前面的那个ACTION_CANCEL事件,在onTouchEvent()方法里面判断到是ACTION_DOWN,并且前面在onInterceptTouchEvent()里面做的标记mCancelMotionEvent,这个标记表示截获了ACTION_DOWN事件,需要特殊处理这个ACTION_DOWN事件,然后看onTouchEvent()方法里面是如何处理这次的ACTION_DOWN事件呢: 


else if(mCancelMotionEvent && event.getAction() == MotionEvent.ACTION_DOWN) {
      event.setAction(MotionEvent.ACTION_CANCEL);
    } 

是滴,偷梁换柱,把当前的ACTION_DOWN事件换成ACTION_CANCEL事件,在ACTION_CANCEL事件的处理就是gone掉当前弹出的button,这样就把两种情况下的ACTION_DOWN区分出来进行了额外的处理了。

同时我们可以看到在ACTION_UP事件中,有进行判断,当当前的mLastButtonShowingPos不为-1,,则表示这次是用户滑动弹出button的操作,这次的touch事件我们有进行处理了,这样我们就不能在把这次的ACTION_UP事件抛回给ListView本身默认的super.onTouchEvent()逻辑处理了,因为前面的ACTION_DOWN以及ACTION_MOVE我们都是走的默认流程,那现在ListView原本的逻辑就等着ACTION_UP事件派发,这样就是ListView本身OnItemClick或者OnItemLongClick事件的触发了,想想一下,如果我们弹出了隐藏的button,ListView依然处理OnItemClick或者OnItemLongClick这样肯定就不合适了,所以这里我们依然要稍微欺骗一下系统,将原本的ACTION_UP替换成ACTION_CANCEL,这样当处理了button的弹出后,就不会再处理ListView原本的OnItemClick或者OnItemLongClick事件了:


 if(mLastButtonShowingPos != -1) { 
        event.setAction(MotionEvent.ACTION_CANCEL); 
      }  

最后讲一下我们这样重写onTouchEvent()方法的话,会不会影响到这个自定义的ListView的onItemClick()和onItemLongClick()方法呢,答案是本方案不会,因为onTouchEvent()方法对于没有截获的事件,都是返回super.onTouchEvent(ev),这样既处理了滑动事件的检测,有没有干扰到系统对于这次事件的处理流程,而截获的事件,有给了事件的完整的生命周期(我有伪造一个ACTION_CANCEL事件结束一次touch的交互),这里我姑且就说生命周期吧,以ACTION_DOWN事件起始,ACTION_UP或是ACTION_CANCEL事件结束,中间夹杂着一系列的ACTION_MOVE事件。我最初的方案是采用ListView.setOnTouchListener(),并实现该TouchListener的onTouch()方法,这样处理事件略复杂,因为这个控件的处理逻辑在ACTION_MOVE里面弹出了button之后,就把所有的后续ACTION_MOVE事件无效化,因为如果不无效化的话后续的ACTION_MOVE事件ListView依然会受到,那用户可以上下拖动ListView,知道ListView的item都是重用几个共同的view的同学就应该会想到接下来要出什么bug了,就是原本没有弹出button的item出现在屏幕上后竟然也会弹出button,因为这个item重用了已经消失的item的view。那我用OnTouchListener.onTouch()方法的时候,在弹出了button就直接return回了true,表示这个事件被OnTouchListener处理了,但这里就出了问题,因为前面的ACTION_DOWN事件一直都是返回false,表示touch的交互的最初始事件由ListView默认的onTouchEvent()逻辑处理(也必须返回false,要不然所有的事件都被这和OnTouchListener吃掉了),由于我们不知道默认的onTouchEvent()里面如何处理了这次的ACTION_DOWN,虽然一般情况下是ListView消耗这次的ACTION_DOWN,开始一个OnItemClick或者OnItemLongClick事件的处理,这是因为item的点击事件都是由ListView的onTouchEvent()处理的,ACTION_DOWN被ListView自身的onTouchEvent()消耗了,但是后续的ACTION_MOVE甚至ACTION_UP事件又被OnTouchListener消耗了的话,无法再传递到默认的onTouchEvent()里面处理,一个本来完整的touch生命周期硬生生的被切成了两部分交由两个地方处理,这样肯定会导致一大推问题,最明显的就是ListView本身的OnItemClickListener等处理事件的监听器与处理滑动事件检测的代码产生冲突,像是滑动之后弹出了button,而当前处理滑动事件的item则处于高亮的选中状态(android里面用pressed表示),即使已经手指离开了屏幕。最后采用的方案则是维持了事件处理的逻辑在一个方法之内,既能做到系统事件正常的分发运转,本身也能处理滑动事件。

最后代码提交到了我的github上:https://github.com/YoungLeeForeverBoy/SlidingDeleteListView。 

下面是本控件的展示:

您可能感兴趣的文章:Android开发中模仿qq列表信息滑动删除功能Android仿QQ列表左滑删除操作android AsynTask处理返回数据和AsynTask使用get,post请求


免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android仿QQ列表滑动删除操作

下载Word文档到电脑,方便收藏和打印~

下载Word文档

猜你喜欢

Android仿QQ列表滑动删除操作

这篇山寨一个新版QQ的列表滑动删除,上篇有说到QQ的滑动删除,推测原理就是ListView本身每个item存在一个Button,只不过普通的状态下隐藏掉了,检测到向左的滑动事件的时候弹出隐藏的Button,不过再切换Button状态的时候会
2022-06-06

Android仿QQ列表左滑删除操作

最近学习了如何做一个像QQ的左滑RecyclerView的item显示选项的,主要是用到Scroller 我们首先新建一个自己的RecyclerView 定义好一些要用的的变量 重写构造方法,把前两个构造方法改为如下,使无论如何构造都要执
2022-06-06

Android仿QQ左滑删除置顶ListView操作

最近闲来无事,于是研究了一下qq的左滑删除效果,尝试着实现了一下,先上效果图:大致思路原理: - 通过设置margin实现菜单的显示与隐藏 - 监听onTouchEvent,处理滑动事件 上代码import android.conten
2022-06-06

Android开发中模仿qq列表信息滑动删除功能

这个效果的完成主要分为两个部分 自定义view作为listview的列表项 一个view里面包括 显示头像,名字,消息内容等的contentView和滑动才能显示出来的删除,置顶的右边菜单menuView 在手指移动的时候同时改变这两个视图
2022-06-06

Android仿微信列表滑动删除 如何实现滑动列表SwipeListView

接上一篇,本篇主要讲如何实现滑动列表SwipeListView。上篇完成了滑动控件SwipeItemView,这个控件是一个自定义的ViewGroup,作为列表的一个item,为列表提供一些方法让这个SwipeItemView能滑动其视图内
2022-06-06

Android仿微信列表滑动删除之可滑动控件(一)

这次是列表滑动删除的第三波,仿微信的列表滑动删除。先上个效果图: 前面的文章里面说过开源框架SwipeListView的实现原理是每个列表item中包含上下两层view,普通状态下上层的view覆盖着下层的view,当用户滑开上层的vie
2022-06-06

Android仿微信对话列表滑动删除效果

微信对话列表滑动删除效果很不错的,借鉴了github上SwipeListView(项目地址:https://github.com/likebamboo/SwipeListView),在其上进行了一些重构,最终实现了微信对话列表滑动删除效果。
2022-06-06

Android仿QQ微信侧滑删除效果

仿QQ侧滑删除效果图1.自定义listviewpublic class DragDelListView extends ListView {private boolean moveable=false;private boolean clo
2022-06-06

Android高仿微信对话列表滑动删除效果

前言 用过微信的都知道,微信对话列表滑动删除效果是很不错的,这个效果我们也可以有。思路其实很简单,弄个ListView,然后里面的每个item做成一个可以滑动的自定义控件即可。由于ListView是上下滑动而item是左右滑动,因此会有滑动
2022-06-06

Android ListView滑动删除操作(SwipeListView)

新版本的微信和QQ上引入的滑动删除功能是现在比较流行的一个功能。其实这个滑动删除的控件,github上已经有了,是一个热门的开源框架SwipeListView。不过,这个SwipeListView是一个framelayout,即是一个两层的
2022-06-06

Android仿腾讯QQ实现滑动删除 附源码下载

看了很多大神们的文章,感觉受益良多,也非常欣赏大家的分享态度,所以决定开始写Blog,给大家分享自己的心得。 先看看效果图:本来准备在ListView的每个Item的布局上设置一个隐藏的Button,当滑动的时候显示。但是因为每次只要存在一
2022-06-06

Android实现滑动删除操作(PopupWindow)

参考Android仿腾讯QQ实现滑动删除这篇文章进行学习,文章实现的功能是:在ListView的Item上从右向左滑时,出现删除按钮,点击删除按钮把Item删除,效果看过文章后,感觉没有必要把dispatchTouchEvent()和onT
2022-06-06

Android仿QQ首页ListView左滑置顶、删除功能

Android 仿QQ首页ListView左滑置顶、删除等实现源码,具体内容如下效果图实现源码:package com.duguang.baseanimation.ui.listivew.deletelistview; import
2023-05-30

Android使用Item Swipemenulistview实现仿QQ侧滑删除功能

大家都用过QQ,肯定有人好奇QQ滑动删除Item的效果是怎样实现的,其实我们使用Swipemenulistview就可以简单的实现。先看看我们项目中的效果: 使用的时候可以把Swipemenulistview作为一个library,也
2022-06-06

Android App中ListView仿QQ实现滑动删除效果的要点解析

本来准备在ListView的每个Item的布局上设置一个隐藏的Button,当滑动的时候显示。但是因为每次只要存在一个Button,发现每个Item上的Button相互间不好控制。所以决定继承ListView然后结合PopupWindow。
2022-06-06

Android使用SwipeListView实现类似QQ的滑动删除效果

QQ的滑动删除效果很不错,要实现这种效果,可以使用SwipeListView。1. 下载com.fortysevendeg.swipelistview这个项目(以前GitHub上有,现在GitHub上没有了,百度了很多次才下载到的),导入E
2022-06-06

Android仿QQ好友列表分组实现增删改及持久化

Android自带的控件ExpandableListView实现了分组列表功能,本案例在此基础上进行优化,为此控件添加增删改分组及子项的功能,以及列表数据的持久化。 Demo实现效果: GroupListDemo具体实现: ①demo中
2022-06-06

Android自定义view实现列表内左滑删除Item

这篇文章主要介绍了微信小程序列表中item左滑删除功能,本文分步骤给大家介绍的非常详细,具有一定的参考借鉴价值,需要的朋友可以参考下
2023-02-09

Python列表对象中元素的删除操作方法

列表的删操作指的是在列表中删除已存在的元素,列表中的元素被删除后,后面所有的元素依次往前移动一位,挂在被删除元素的索引下,保证每一个索引都有元素,这篇文章主要介绍了Python列表对象中元素的删除操作方法,需要的朋友可以参考下
2022-12-21

Android自定义view系列之99.99%实现QQ侧滑删除效果实例代码详解

首先声明本文是基于GitHub上"baoyongzhang"的SwipeMenuListView修改而来,该项目地址: https://github.com/baoyongzhang/SwipeMenuListView 可以说这个侧滑删除效
2022-06-06

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录