Android仿QQ列表滑动删除 *** 作

Android仿QQ列表滑动删除 *** 作,第1张

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

这篇山寨一个新版QQ的列表滑动删除,上篇有说到QQ的滑动删除,推测原理就是ListVIEw本身每个item存在一个button,只不过普通的状态下隐藏掉了,检测到向左的滑动事件的时候d出隐藏的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事件,我们有两个情况下确定要处理,一种情况是用户在滑动一定距离就d出button,这个距离是当前滑动的位置和本次ACTION_DOWN记录下的事件位置的距离,第二中情况是用户滑动速度超过一个阈值的时候,d出button,这个速度的计算就是用前面提到的mTracker了,用法很简单;检测到ACTION_UP事件表示当前的这次交互完成,我们可以做一些清理工作;至于ACTION_CANCEL事件,这个这里暂且买个关子,这个使用个偷梁换柱的小技巧欺负一下系统~ 

上面的ACTION_MOVE事件里面如果处理了事件,d出了button,那我们在下次检测到ACTION_DOWN事件,如果这个事件发生的位置没有在button的区域,则表示用户不是点击d出的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有d出,因为每次d出一个button,会记下当前d出的item的位置mLastbuttonShowingPos;然后就是当前是不是ACTION_DOWN事件;以及是否点击d出的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掉当前d出的button,这样就把两种情况下的ACTION_DOWN区分出来进行了额外的处理了。

同时我们可以看到在ACTION_UP事件中,有进行判断,当当前的mLastbuttonShowingPos不为-1,,则表示这次是用户滑动d出button的 *** 作,这次的touch事件我们有进行处理了,这样我们就不能在把这次的ACTION_UP事件抛回给ListVIEw本身默认的super.ontouchEvent()逻辑处理了,因为前面的ACTION_DOWN以及ACTION_MOVE我们都是走的默认流程,那现在ListVIEw原本的逻辑就等着ACTION_UP事件派发,这样就是ListVIEw本身OnItemClick或者OnItemLongClick事件的触发了,想想一下,如果我们d出了隐藏的button,ListVIEw依然处理OnItemClick或者OnItemLongClick这样肯定就不合适了,所以这里我们依然要稍微欺骗一下系统,将原本的ACTION_UP替换成ACTION_CANCEL,这样当处理了button的d出后,就不会再处理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里面d出了button之后,就把所有的后续ACTION_MOVE事件无效化,因为如果不无效化的话后续的ACTION_MOVE事件ListVIEw依然会受到,那用户可以上下拖动ListVIEw,知道ListVIEw的item都是重用几个共同的vIEw的同学就应该会想到接下来要出什么BUG了,就是原本没有d出button的item出现在屏幕上后竟然也会d出button,因为这个item重用了已经消失的item的vIEw。那我用OntouchListener.ontouch()方法的时候,在d出了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等处理事件的监听器与处理滑动事件检测的代码产生冲突,像是滑动之后d出了button,而当前处理滑动事件的item则处于高亮的选中状态(androID里面用pressed表示),即使已经手指离开了屏幕。最后采用的方案则是维持了事件处理的逻辑在一个方法之内,既能做到系统事件正常的分发运转,本身也能处理滑动事件。

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

下面是本控件的展示:

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

总结

以上是内存溢出为你收集整理的Android仿QQ列表滑动删除 *** 作全部内容,希望文章能够帮你解决Android仿QQ列表滑动删除 *** 作所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/web/1148394.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-05-31
下一篇 2022-05-31

发表评论

登录后才能评论

评论列表(0条)

保存