touch事件传递机制总结

最近在做一个ListView里面嵌套一个pageView的案例(如下图),在处理pageView左右拖动的touch事件,会跟ListView的冲突,原本想要切换pageview的图片,结果切换了ListView,这种滑动冲突问题,查看了相关的材料和源代码后,总结如下:



一、首先查看消息传递流程图:


1、android事件分发是先传递到Viewgroup,再有Viewgroup传递到View。

2、在Viewgroup中可以通过onInterceptTouchEvent方法对事件传递进行拦截,onIntercrptTouchEvent方法返回true代表不允许事件继续向子View传递,返回false代表不对事件进行拦截,默认是false

3、子View中如果将传递的事件消费掉(通过dispatchTouchEvent方法),Viewgroup中将无法接收到事件



二、进一步了解具体如何实现,来看下ViewGroup的dispatchTouchEvent方法

@Override
	  public boolean dispatchTouchEvent(MotionEvent ev) {
	    final int action = ev.getAction();
	    final float xf = ev.getX();
	    final float yf = ev.getY();
	    final float scrolledXFloat = xf + mScrollX;
	    final float scrolledYFloat = yf + mScrollY;
	    final Rect frame = mTempRect;
	    //可以通过requestDisallowInterceptTouchEvent方法来设置该变量的值,通常是false</span>
	    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;

	    if (action == MotionEvent.ACTION_DOWN) {
	      if (mMotionTarget != null) {
	        // this is weird, we got a pen down, but we thought it was
	        // already down!
	        // XXX: We should probably send an ACTION_UP to the current
	        // target.
	        mMotionTarget = null;
	      }
	      // If we're disallowing intercept or if we're allowing and we didn't
	      // intercept
	      //onInterceptTouchEvent在默认情况下是返回false的,所以这里通常是可以进去的</span>
	      if (disallowIntercept || !onInterceptTouchEvent(ev)) {
	        // reset this event's action (just to protect ourselves)
	        ev.setAction(MotionEvent.ACTION_DOWN);
	        // We know we want to dispatch the event down, find a child
	        // who can handle it, start with the front-most child.
	        final int scrolledXInt = (int) scrolledXFloat;
	        final int scrolledYInt = (int) scrolledYFloat;
	        final View[] children = mChildren;
	        final int count = mChildrenCount;
	        //遍历ViewGroup的孩子,如果触摸点在某一个子View中,则调用在子View的dispatchTouchEvent</span>
	        for (int i = count - 1; i >= 0; i--) {
	          final View child = children[i];
	          if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
	              || child.getAnimation() != null) {
	            child.getHitRect(frame);
               //判断触点是否在该子View上</span>
	            if (frame.contains(scrolledXInt, scrolledYInt)) {
	              // offset the event to the view's coordinate system
	              final float xc = scrolledXFloat - child.mLeft;
	              final float yc = scrolledYFloat - child.mTop;
	              ev.setLocation(xc, yc);
	              child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
	        //调用了某一个子View 的dispatchTouchEvent ,如果这个子View 的dispatchTouchEvent返回true,那么意味着这个事件已经被这个子View消费了,不会继续                //传递</span>
	              if (child.dispatchTouchEvent(ev))  {
	                // Event handled, we have a target now.
<span style="white-space:pre">			</span>//mMotionTarget 置为处理该事件的子View

	                mMotionTarget = child;
	                return true;
	              }
	              // The event didn't get handled, try the next view.
	              // Don't reset the event's location, it's not
	              // necessary here.
	            }
	          }
	        }
	      }
	    }

	    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
	        (action == MotionEvent.ACTION_CANCEL);

	    if (isUpOrCancel) {
	      // Note, we've already copied the previous state to our local
	      // variable, so this takes effect on the next event
	      mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
	    }

	    // The event wasn't an ACTION_DOWN, dispatch it to our target if
	    // we have one.
	    final View target = mMotionTarget;

	    if (target == null) {
	    //对于一个Action_down事件,如果走到了这里,说明所有的子View 都没有消费掉这个事件,那么它就调用父类的
	    //的dispatchTouchEvnet方法,也就是View的
<pre name="code" class="java">ev.setLocation(xf, yf);
      if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
      }
      return super.dispatchTouchEvent(ev);
    }





总结如下:

1、如果disallowIntercept||onInterceptTouchEvent(),那么事件才可以继续传递下去,否则直接调用该ViewGroup的父类的dispatchTouchEvent,也就是ViewdispatchTouchEvent.

2、依次遍历ViewGroup的所有子View,将事件传递个子View,如果某一个子View处理了该事件,并且返回true,那么事件结束,停止传递

3、如果所有的子View没有消费掉这个事件,那么就调用父类即ViewdispatchTouchEvent

三、接下来我们查看ViewdispatchTouchEvent代码

public boolean dispatchTouchEvent(MotionEvent event) {
        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
                mOnTouchListener.onTouch(this, event)) {
            return true;
        }
        return onTouchEvent(event);
    }

首先判断mTouchListener是否为空,并且这个View是否Eneable,如果都满足,那么首先调用mOnTouchListener.onTouch方法,如果onTouch方法返回true,那么就是说这个View消费了该事件,直接返回true,如果onTouch返回false,那么就会调用onTouchEvnet方法



四、总结

ViewGroup的通过dispatchTouchEvent,如果它的所有的子View没有处理掉该事件,那么调用的是父类ViewdispatchTouchEvnet方法,从而执行到了该ViewGrouponTouchonTouchEvent方法。
像本项目的拖动冲突,需要覆写pageView的dispatchTouchEvnet并返回true,就会实现其图片的切换,不会再将touch事件传递到其父View(ListView),从而不会再执行翻页行为。

(PS:来了优快云这段事件,逐渐喜欢上了这里的氛围,这是第一篇博客,还有很多不足还望指教,希望大家共同进步)





评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值