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

 更新时间:2016年08月31日 12:00:48   作者:ly513782705  
这篇文章主要为大家详细介绍了Android仿微信列表滑动删除,如何实现滑动列表SwipeListView,感兴趣的小伙伴们可以参考一下

上一篇,本篇主要讲如何实现滑动列表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

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

相关文章

  • Android学习之文件存储读取

    Android学习之文件存储读取

    本节给大家介绍的是Android数据存储与访问方式中的一个——文件存储与读写,当然除了这种方式外,我们可以存到SharedPreference,数据库, 或者ContentProvider中,当然这些后面都会讲,嗯,开始本文内容~
    2016-07-07
  • Flutter进阶之实现动画效果(二)

    Flutter进阶之实现动画效果(二)

    这篇文章主要为大家详细介绍了Flutter进阶之实现动画效果的相关资料,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-08-08
  • Android studio实现动态背景页面

    Android studio实现动态背景页面

    这篇文章主要为大家详细介绍了Android studio实现动态背景页面,文中示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2022-04-04
  • Android开发之微信底部菜单栏实现的几种方法汇总

    Android开发之微信底部菜单栏实现的几种方法汇总

    这篇文章主要介绍了Android开发之微信底部菜单栏实现的几种方法,下面小编把每种方法通过实例逐一给大家介绍,需要的朋友可以参考下
    2016-09-09
  • Android中通知栏跳动问题解决方法

    Android中通知栏跳动问题解决方法

    这篇文章主要介绍了Android中通知栏跳动问题解决方法,导致这个问题的原因是when这个属性值,默认它是使用的系统当前时间,这就是导致跳动问题的原因,指定一个固定时间即可解决这个问题,需要的朋友可以参考下
    2015-01-01
  • Android布局中gravity与layout_gravity属性说明

    Android布局中gravity与layout_gravity属性说明

    这篇文章主要介绍了Android布局中gravity与layout_gravity属性说明,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2023-01-01
  • Android实现可使用自定义透明Dialog样式的Activity完整实例

    Android实现可使用自定义透明Dialog样式的Activity完整实例

    这篇文章主要介绍了Android实现可使用自定义透明Dialog样式的Activity,结合完整实例形式分析了Android Activity自定义style的操作步骤与相关技巧,需要的朋友可以参考下
    2016-07-07
  • Android Jetpack 狠活Lifecycles与LiveData使用详解

    Android Jetpack 狠活Lifecycles与LiveData使用详解

    这篇文章主要为大家介绍了Android Jetpack 狠活Lifecycles与LiveData使用详解,有需要的朋友可以借鉴参考下,希望能够有所帮助,祝大家多多进步,早日升职加薪
    2022-10-10
  • Android自定义View实现简单文字描边功能

    Android自定义View实现简单文字描边功能

    这篇文章主要为大家详细介绍了Android自定义View实现简单文字描边功能,具有一定的参考价值,感兴趣的小伙伴们可以参考一下
    2018-12-12
  • Android拦截外拨电话程序示例

    Android拦截外拨电话程序示例

    这篇文章主要介绍了Android拦截外拨电话的示例,大家参考使用吧
    2013-11-11

最新评论