Android中对GridView, ListView等滚动控件的Touch事件onInterceptTouchEvent,onTouchEvent理解

在开始正文之前,首先得感谢 http://blog.csdn.net/guitk/article/details/7057155提供的一篇转载文章,里面说的内容和插图让我对TouchEvent的事件传递迅速有了一个大致的印象。但是文章里面有说的不全面不详细的地方,特发此博客记录个人对于这类问题的理解,用来补充和完善,有不妥的地方,也欢迎大家批评讨论。

       正如命名一样,onInterceptTouchEvent用来拦截事件,onTouchEvent用来处理事件,网上大部分的文章中也都对这两类方法的使用情况进行了简单的说明。

       事件传递如下图,逐级向下看onInterceptTouchEvent()是否需要截断事件,如果没发生截断,则逐级向上寻找能够处理该事件的onTouchEvent()。

      源码上RelativeLayout等布局是继承ViewGroup,看源码上ViewGroup中的onInterceptTouchEvent却非常简单:

Java代码 复制代码 收藏代码
  1. public boolean onInterceptTouchEvent(MotionEvent ev) { 
  2.         return false
public boolean onInterceptTouchEvent(MotionEvent ev) {
        return false;
}

       如果不重写该方法直接就会return false。

       如果在其中一层截断事件,让onInterceptTouchEvent()返回true,例如在上图中ChildLayout中发生截断,则事件传递则会向跳过MyView,直接从这一层进入onTouchEvent。若ChildLayout的onTouchEvent返回false,则传递流程如下图


       很重要的一点:事件传递是这样的,你可以在传递到响应的方法里面做任何你想做的事情,这些事情不会改变事件传递,能影响事件传递的是这些方法的返回值。例如你在ChildLayout的onTouchEvent做了很多事情,只要返回了false,事件还是会进入到ParentLayout的onTouchEvent。

       若保持ChildLayout的onInterceptTouchEvent返回true,将它的onTouchEvent返回true,则事件不会再进入其他组件的onTouchEvent,后续的时间会依次进入这个返回了true的onTouchEvent,传递则变为如下图:



       自此说的内容都跟博客开头那个链接里面的文章类似。下面写一些那篇文章里面没有谈及或者不够全面的地方。下文中举例进行的操作都是拖动,即ACITON_DOWN -> ACTION_MOVE -> ACTION_UP

       那篇文章里面说在事件发生截断的时候,会像子View发出ACTION_CANCEL,但是我在ChildLayout的onInterceptTouchEvent里面return true之后,却没有收到ACTION_CANCEL,经实验发现,谈谈关于ACTION_CANCEL的事情。ACTION_CANCEL的发出是有条件的:如果子一层曾经处理过事件,即事件进入到onTouchEvent中,则此时截断事件,上一层会向下一层发出ACTION_CANCEL。例如在ChildLayout的onInterceptTouchEvent中对event.getAction()进行判断,如果是ACTION_DOWN,返回false,在ACTION_MOVE的时候,返回true进行截断。则可以发现MyView接到了action为ACTION_DOWN的事件,但是当出现ACTION_MOVE在ChildLayout截断发生时,MyView则收到了ACTION_CANCEL的消息。代码如下:

       ChildLayout

Java代码 复制代码 收藏代码
  1. @Override 
  2. public boolean onInterceptTouchEvent(MotionEvent ev) { 
  3.      boolean result = super.onInterceptTouchEvent(ev); 
  4.      switch(ev.getAction()) { 
  5.      case MotionEvent.ACTION_DOWN: 
  6.          Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_DOWN:"); 
  7.          break
  8.      case MotionEvent.ACTION_MOVE: 
  9.          Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_MOVE:"); 
  10.          result = true
  11.          break
  12.      case MotionEvent.ACTION_CANCEL: 
  13.          Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_CANCEL:"); 
  14.          break
  15.      case MotionEvent.ACTION_UP: 
  16.          Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_UP:"); 
  17.          break
  18.      } 
  19.      Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent return "+result); 
  20.      return result; 
  21.  
  22. @Override 
  23. public boolean onTouchEvent(MotionEvent ev) { 
  24.      boolean result = super.onTouchEvent(ev); 
  25.      switch(ev.getAction()) { 
  26.      case MotionEvent.ACTION_DOWN: 
  27.          Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_DOWN"); 
  28.          break
  29.      case MotionEvent.ACTION_MOVE: 
  30.          Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_MOVE"); 
  31.          break
  32.      case MotionEvent.ACTION_CANCEL: 
  33.          Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_CANCEL"); 
  34.      case MotionEvent.ACTION_UP: 
  35.          Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_UP"); 
  36.          break
  37.      } 
  38.  
  39.      result = true
  40.      Log.i("ZZZZ", "ChildLayout onTouchEvent return "+result); 
  41.      return result; 
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result = super.onInterceptTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_DOWN:");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_MOVE:");
            result = true;
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_CANCEL:");
            break;
        case MotionEvent.ACTION_UP:
            Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent ACITON_UP:");
            break;
        }
        Log.i("ZZZZ", "ChildLayout onInterceptTouchEvent return "+result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result = super.onTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_CANCEL");
        case MotionEvent.ACTION_UP:
            Log.i("ZZZZ", "ChildLayout onTouchEvent ACTION_UP");
            break;
        }

        result = true;
        Log.i("ZZZZ", "ChildLayout onTouchEvent return "+result);
        return result;
   }

      为了增加事件的复杂性,挑选了有滚动效果的GridView在ChildLayout下一层的MyView作为实验对象

      MyGridView

Java代码 复制代码 收藏代码
  1. @Override 
  2. public boolean onInterceptTouchEvent(MotionEvent ev) { 
  3.     boolean result = super.onInterceptTouchEvent(ev); 
  4.     switch(ev.getAction()) { 
  5.     case MotionEvent.ACTION_DOWN: 
  6.         Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_DOWN:"); 
  7.         break
  8.     case MotionEvent.ACTION_MOVE: 
  9.         Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_MOVE:"); 
  10.         break
  11.     case MotionEvent.ACTION_CANCEL: 
  12.         Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_CANCEL:"); 
  13.         break
  14.     case MotionEvent.ACTION_UP: 
  15.         Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_UP:"); 
  16.         break
  17.     } 
  18.     Log.d("ZZZZ", "MyGridView onInterceptTouchEvent return "+result); 
  19.     return result; 
  20.  
  21. @Override 
  22. public boolean onTouchEvent(MotionEvent ev) { 
  23.     boolean result = super.onTouchEvent(ev); 
  24.     switch(ev.getAction()) { 
  25.     case MotionEvent.ACTION_DOWN: 
  26.         Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_DOWN"); 
  27.         break
  28.     case MotionEvent.ACTION_MOVE: 
  29.         Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_MOVE"); 
  30.         break
  31.     case MotionEvent.ACTION_CANCEL: 
  32.         Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_CANCEL"); 
  33.         break
  34.     case MotionEvent.ACTION_UP: 
  35.         Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_UP"); 
  36.         break
  37.     } 
  38.  
  39.     Log.d("ZZZZ", "MyGridView onTouchEvent return "+result); 
  40.     return result; 
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean result = super.onInterceptTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_DOWN:");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_MOVE:");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_CANCEL:");
            break;
        case MotionEvent.ACTION_UP:
            Log.d("ZZZZ", "MyGridView onInterceptTouchEvent ACITON_UP:");
            break;
        }
        Log.d("ZZZZ", "MyGridView onInterceptTouchEvent return "+result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent ev) {
        boolean result = super.onTouchEvent(ev);
        switch(ev.getAction()) {
        case MotionEvent.ACTION_DOWN:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_DOWN");
            break;
        case MotionEvent.ACTION_MOVE:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_MOVE");
            break;
        case MotionEvent.ACTION_CANCEL:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_CANCEL");
            break;
        case MotionEvent.ACTION_UP:
            Log.d("ZZZZ", "MyGridView onTouchEvent ACTION_UP");
            break;
        }

        Log.d("ZZZZ", "MyGridView onTouchEvent return "+result);
        return result;
  }


运行日志:



       再次修改代码,取消ChildLayout里onInterceptTouchEvent对事件的拦截返回false,让MyGridView的onTouchEvent方法return super.onTouchEvent(ev)默认返回true。

       在同样尝试拖动效果的时候,按照上面的说法,理论上在ParentLayout,ChildLayout,MyGridView都不拦截事件return false的状态下,事件会通过依次通过ParentLayout,ChildLayout,MyGridView的onInterceptTouchEvent,然后到达MyGridView的onTouchEvent,但是我们发现事实并不是如此,移动一段距离后,就没有任何onInterceptTouchEvent执行了。运行效果如下:



         说好的依次传递并没有发生,无奈只能去源码中寻找原因,GridView继承于AbsListView,与之前ChildLayout,ParentLayout继承与ViewGroup不同,在AbsListView中的onTouchEvent的ACTION_MOVE的这个case中看到有个startScrollIfNeeded方法,点进去才发现有方法说明“Check if we have moved far enough that it looks more like a scroll than a tap”, 在我们tap屏幕的时候有一段距离touchSlop,小于这个距离的ACTION_MOVE是会被判定成为tap效果的,所以在这段源码里面能看到

Java代码 复制代码 收藏代码
  1. if (overscroll || distance > mTouchSlop) { 
  2.             createScrollingCache(); 
  3.             if (overscroll) { 
  4.                 mTouchMode = TOUCH_MODE_OVERSCROLL; 
  5.                 mMotionCorrection = 0
  6.             } else
  7.                 mTouchMode = TOUCH_MODE_SCROLL; 
  8.                 mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop; 
  9.             } 
  10.             final Handler handler = getHandler(); 
  11.             // Handler should not be null unless the AbsListView is not attached to a 
  12.             // window, which would make it very hard to scroll it... but the monkeys 
  13.             // say it's possible. 
  14.             if (handler != null) { 
  15.                 handler.removeCallbacks(mPendingCheckForLongPress); 
  16.             } 
  17.             setPressed(false); 
  18.             View motionView = getChildAt(mMotionPosition - mFirstPosition); 
  19.             if (motionView != null) { 
  20.                 motionView.setPressed(false); 
  21.             } 
  22.             reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL); 
  23.             // Time to start stealing events! Once we've stolen them, don't let anyone 
  24.             // steal from us 
  25.             final ViewParent parent = getParent(); 
  26.             if (parent != null) { 
  27.                 parent.requestDisallowInterceptTouchEvent(true); 
  28.             } 
  29.             scrollIfNeeded(y); 
  30.             return true
  31.         } 
if (overscroll || distance > mTouchSlop) {
            createScrollingCache();
            if (overscroll) {
                mTouchMode = TOUCH_MODE_OVERSCROLL;
                mMotionCorrection = 0;
            } else {
                mTouchMode = TOUCH_MODE_SCROLL;
                mMotionCorrection = deltaY > 0 ? mTouchSlop : -mTouchSlop;
            }
            final Handler handler = getHandler();
            // Handler should not be null unless the AbsListView is not attached to a
            // window, which would make it very hard to scroll it... but the monkeys
            // say it's possible.
            if (handler != null) {
                handler.removeCallbacks(mPendingCheckForLongPress);
            }
            setPressed(false);
            View motionView = getChildAt(mMotionPosition - mFirstPosition);
            if (motionView != null) {
                motionView.setPressed(false);
            }
            reportScrollStateChange(OnScrollListener.SCROLL_STATE_TOUCH_SCROLL);
            // Time to start stealing events! Once we've stolen them, don't let anyone
            // steal from us
            final ViewParent parent = getParent();
            if (parent != null) {
                parent.requestDisallowInterceptTouchEvent(true);
            }
            scrollIfNeeded(y);
            return true;
        }

       里面有代码,对parent判空了执行requestDisallowInterceptTouchEvent(true);看到注释则说明的很清楚了,如果满足了条件,被判定成为looks more like a scroll than a tap,则start stealing events, once we've stolen them, don't let anyone steal from us,好傲娇的样子,直接屏蔽了各种有可能阻截这些事件的情况,然后能够阻截这个MotionEvent的就只有onInterceptTouchEvent了,这也就是为什么在有GridView的情况下,"ACTION_DOWN -> ACTION_MOVE -> ACTION_UP"操作一小距离之后却看不到任何onInterceptTouchEvent被执行的原因了。

       前几天写代码里面涉及到了GridView和ListView的处理,看到打出来的LOG并不是如之前理解的,故有了这篇博客,用来给自己加深印象,也给有需要的同学提供帮助。

       如上文中有不妥的地方欢迎各位批评讨论,谢谢。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值