欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  移动技术

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

程序员文章站 2024-03-06 16:56:14
接,本篇主要讲如何实现滑动列表swipelistview。 上篇完成了滑动控件swipeitemview,这个控件是一个自定义的viewgroup,作为列表的一个it...

接,本篇主要讲如何实现滑动列表swipelistview。

上篇完成了滑动控件swipeitemview,这个控件是一个自定义的viewgroup,作为列表的一个item,为列表提供一些方法让这个swipeitemview能滑动其视图内容,同时滑动过程中会有顺滑的动画效果。而本篇讲的swipelistview则是这个列表的具体实现了。当然啦,这个swipelistview继承自listview,为了实现我们需要的功能,重点就是重写listview的ontouchevent()以及onintercepttouchevent()这个方法了。先说ontouchevent():

@override
  public boolean ontouchevent(motionevent ev) {

    //if user had not set mswipeitemviewid, not handle any touch event
    if(mswipeitemviewid == -1)
      return super.ontouchevent(ev);

    if(mcancelmotionevent && ev.getaction() == motionevent.action_down) {
      //ev.setaction(motionevent.action_cancel);
      logutil.log("swipelistview.ontouchevent(), cancel action_down");
      hideshowingitem();

      return true;
    } else if(mcancelmotionevent && ev.getaction() == motionevent.action_move) {
      if(mswipeitemview.getcurrentscrollx() > 0) {
        mswipeitemview.computescroll();
        //mswipeitemview.scrollby(-1, 0);
      }

      return true;
    } else if(mcancelmotionevent && ev.getaction() == motionevent.action_up) {
      mcancelmotionevent = false;

      return true;
    }

    switch(ev.getaction()) {
      case motionevent.action_down: {
        logutil.log("action_down");
        if(mtracker == null) {
          mtracker = velocitytracker.obtain();
        } else {
          mtracker.clear();
        }

        mactiondownx = ev.getx();
        mactiondowny = ev.gety();
        mlastmotionx = ev.getx();
        mlastmotiony = ev.gety();
      }break;

      case motionevent.action_move: {
        //if the scroll distance at x-axis or y-axis less than the
        //touch_slop, do not handle the event motionevent.action_move
        if(math.abs(ev.getx() - mactiondownx) < touch_slop
            || math.abs(ev.gety() - mactiondowny) < touch_slop)
          break;

        float curx = ev.getx();
        float cury = ev.gety();
        int distancex = (int)(mlastmotionx - curx);
        int distancey = (int)(mlastmotiony - cury);

        if(mscrolldirection == direction_unknow
            && math.abs(distancey) <= math.abs(distancex))
          mscrolldirection = direction_horizontal;
        else if(mscrolldirection == direction_unknow
            && math.abs(distancey) > math.abs(distancex))
          mscrolldirection = direction_vertical;

        //if listview is scrolling vertical, do not handle the touch event
        if(mscrolldirection == direction_vertical)
          break;

        int lastpos = pointtoposition((int)mactiondownx, (int)mactiondowny);
        int firstvisibleitempos = getfirstvisibleposition()
            - getheaderviewscount();
        int factpos = lastpos - firstvisibleitempos;
        mitemview = getchildat(factpos);
        if(mitemview != null) {
          mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid);
          if(mswipeitemview.getslidingview() != null
              && mswipeitemview.getscrollx()
                  <= mswipeitemview.getslidingview().getwidth()
              && mswipeitemview.getscrollx() >= 0) {
            if(mswipeitemview.getscrollx() + distancex
                > mswipeitemview.getslidingview().getwidth())
              distancex = mswipeitemview.getslidingview().getwidth()
                  - mswipeitemview.getscrollx();
            else if(mswipeitemview.getscrollx() + distancex < 0)
              distancex = -mswipeitemview.getscrollx();

            mswipeitemview.scrollby(distancex, 0);
          }

          mlastshowingpos = lastpos;

          ev.setaction(motionevent.action_cancel);
        }

        mlastmotionx = curx;
        mlastmotiony = cury;
      }break;

      case motionevent.action_up: {
        logutil.log("action_up");
        if(mtracker != null) {
          mtracker.clear();
          mtracker.recycle();
          mtracker = null;
        }

        //reset the mscrolldirection to direction_unknow
        mscrolldirection = direction_unknow;

        //reset the mcancelmotionevent to false
        mcancelmotionevent = false;

        //ensure if the showing item need open or hide
        if(mlastshowingpos != -1)
          ensureifitemopenorhide();
      }break;

      case motionevent.action_cancel: {
        hideshowingitem();
      }break;
    }

    return super.ontouchevent(ev);
  }

上面代码,首先分析用户滑开一个item的操作,这个操作以action_down起始,一系列的action_move,以action_up作为结束,所以,在action_down事件里面,我们先记录下最开始的事件位置mactiondownx和mactiondowny;然后再action_move事件里面,我们先要进行判断,这个判断分两步,第一步,判断这个action_move事件下,当前事件的位置curx和cury在x轴上以及y轴上和action_down里面记录的位置的距离是否已经超过touch_slop的值,这个值是android用来判断是否应该进行一次滑动的阈值,第二步,我们要进一步判断用户是纵向滑动这整个列表还是左右滑动某个item,这里的逻辑判断就简单点处理,若是超过touch_slop阈值的情况下,x轴方向上距离大于y轴的,我们就认为用户是左右滑动单个item,反之则是纵向滑动整个列表,这里我们用三种状态区分,direction_unknow表示当前的滑动操作还没有进行判断左右滑动还是纵向滑动,direction_horizontal表示当前滑动操作判定为左右滑动,direction_vertical表示判定为纵向滑动,一旦滑动操作被判定了,则在action_up处理前,我们都认为用户是做同一方向的滑动;action_up事件里面,重置滑动操作状态为direction_unknow以便下一次的判定,以及这次action_up事件处理的时候,如果当前滑开的item的位置mlastshowingpos不为-1,则表示当前是一次滑开的操作,这次仔细想想,用户可能在并没有完全滑开这个item的状态下手就离开屏幕了,这时候我们就应该要判断此时这个被滑动的item是应该完全打开又或者是关闭,这里的逻辑判断是item已经滑开的距离超过它本身宽度的一半的话,就完全打开它,否则就关闭它,ensureifitemopenorhide()的具体代码如下:

 private void ensureifitemopenorhide() {
    if(mlastshowingpos != -1) {
      int firstvisibleitempos = getfirstvisibleposition()
          - getheaderviewscount();
      int factpos = mlastshowingpos - firstvisibleitempos;
      mitemview = getchildat(factpos);
      if(mitemview != null) {
        mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid);
        if(mswipeitemview.getslidingview() != null &&
            mswipeitemview.getscrollx() >=
                mswipeitemview.getslidingview().getwidth() / 2) {
          openshowingitem();
        } else if(mswipeitemview.getslidingview() != null) {
          hideshowingitem();
        }
      }
    }
  }

那第一次的用户滑动操作的逻辑判定我们就算处理完了。接下来是第二次的,为什么说第二次呢,第一次用户滑开了某单个的item,使其处于打开的状态下,再一次触摸屏幕,这次我们则要再一次进行判定,其一,如果action_down发生的位置在item滑开显示出来的button的范围内,表示当前用户是点击这个button,这样我们就不做额外处理,直接交由列表默认的逻辑处理;其二,如果action_down发生的位置不在item滑开后显示出来的button范围内,怎表示当前用户只是操作列表的其他范围,这里我们就关闭当前打开了的item,并取消后续的touch事件,这里的话,我们就要截获这个actioin_down事件了,需要重写listview的onintercepttouchevent()方法,代码如下:

public boolean onintercepttouchevent(motionevent ev) {
    //if user had not set mswipeitemviewid, not handle any touch event
    if(mswipeitemviewid == -1)
      return super.onintercepttouchevent(ev);

    if(mlastshowingpos != -1
        && ev.getaction() == motionevent.action_down
        && !isclickchildview(ev)) {
      logutil.log("swipelistview.onintercepttouchevent(), intercept action_down");
      mcancelmotionevent = true;

      return true;
    } else if(mlastshowingpos == -1
        && ev.getaction() == motionevent.action_down) {
      return true;
    }

    return super.onintercepttouchevent(ev);
  }  

上面的mcancelmotionevent是用来在ontouchevent()里面判断是否需要取消后续touch事件的标志,那期间,如何判断当前的action_down事件是否发生在button的范围内呢,使用如下代码:

private boolean isclickchildview(motionevent event) {
    if(mlastshowingpos != -1) {
      int firstvisibleitempos = getfirstvisibleposition()
          - getheaderviewscount();
      int factpos = mlastshowingpos - firstvisibleitempos;
      mitemview = getchildat(factpos);
      if(mitemview != null) {
        mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid);
        view slidingview = mswipeitemview.getslidingview();
        if(slidingview != null) {
          int[] slidingviewlocation = new int[2];
          slidingview.getlocationonscreen(slidingviewlocation);

          int left = slidingviewlocation[0];
          int right = slidingviewlocation[0] + slidingview.getwidth();
          int top = slidingviewlocation[1];
          int bottom = slidingviewlocation[1] + slidingview.getheight();

          return (event.getrawx() > left && event.getrawx() < right
              && event.getrawy() > top && event.getrawy() < bottom);
        }
      }
    }

    return false;
  }

剩下的,就是如何打开或者关闭一个item了,代码如下:

private void openshowingitem() {
    if(mlastshowingpos != -1) {
      int firstvisibleitempos = getfirstvisibleposition()
          - getheaderviewscount();
      int factpos = mlastshowingpos - firstvisibleitempos;
      mitemview = getchildat(factpos);
      if(mitemview != null) {
        mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid);
        if(mswipeitemview.getslidingview() != null)
          mswipeitemview.scrolltowithanimation(
              mswipeitemview.getslidingview().getwidth(), 0);
      }
    }
  }
   private void hideshowingitem() {
    if(mlastshowingpos != -1) {
      int firstvisibleitempos = getfirstvisibleposition()
          - getheaderviewscount();
      int factpos = mlastshowingpos - firstvisibleitempos;
      mitemview = getchildat(factpos);
      if(mitemview != null) {
        mswipeitemview = (swipeitemview)mitemview.findviewbyid(mswipeitemviewid);
        mswipeitemview.scrolltowithanimation(0, 0);
      }

      mlastshowingpos = -1;
    }
  }


上面的scrolltowithanimation()方法就是上一篇博客里面我们实现了的移动item并使其带有动画效果的方法了。 

这样,整个仿微信滑动删除操作的总体实现方案就解释完毕了,具体一些细节的话可以查看这个工程的源码,源码我已经上传到了我的github主页上:https://github.com/youngleeforeverboy/slidinglistviewplus

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