权利的游戏Episode 3 of Series 8,想起《起风了》中的一句话。

起风了,唯有努力生存

无论对手多么强大,纵然被一步一步逼入绝望,我们能做唯有拼尽全力。

伪代码 ViewGruop(简单版)

dispatchTouchEvent代码逻辑

public boolean dispatchTouchEvent(MotionEvent ev) { 
   boolean consume = false;
   if(onInterceptTouchEvent(ev)) {
      consume = onTouchEvent(ev);
   } else {
      consume = child.dispatchTouchEvent(ev);
   }
}

ViewGroup会先调用onInterceptTouchEvent方法判断是否要拦截,如果拦截就交给自己的onTouchEvent处理,如果不拦截交给匹配到的子View处理。不是很细节。

view dispatchTouchEvent代码

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

可以看出TouchListener.onTouch优先级高于View的OnTouchEvent方法,如果TouchListener的onTouch返回true表示事件已经被消耗了,就不会交给OnTouchEvent方法处理了。

代码 ViewGruop (复杂版)

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;
    boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    //根据Action_Down事件 找到目标View
    if (action == MotionEvent.ACTION_DOWN) {
        if (mMotionTarget != null) {
            mMotionTarget = null;
        }
        // 是否禁用拦截  或者 是否没有拦截 Down事件
        if (disallowIntercept || !onInterceptTouchEvent(ev)) {
            ev.setAction(MotionEvent.ACTION_DOWN);
            final int scrolledXInt = (int) scrolledXFloat;
            final int scrolledYInt = (int) scrolledYFloat;
            final View[] children = mChildren;
            final int count = mChildrenCount;
            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);
                    if (frame.contains(scrolledXInt, scrolledYInt)) {
                        final float xc = scrolledXFloat - child.mLeft;
                        final float yc = scrolledYFloat - child.mTop;
                        ev.setLocation(xc, yc);
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
                        if (child.dispatchTouchEvent(ev))  {
                            mMotionTarget = child;
                            return true;
                        }
                    }
                }
            }
        }
    }
    boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
            (action == MotionEvent.ACTION_CANCEL);
    if (isUpOrCancel) {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    final View target = mMotionTarget;
    //目标view为空 (1.未找到匹配的view 2.down事件被自身拦截了)直接调用 自身onTouch 
    if (target == null) {
        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);
    }
		//如果拦截子View会收到ACTION_CANCEL事件
    if (!disallowIntercept && onInterceptTouchEvent(ev)) {
        final float xc = scrolledXFloat - (float) target.mLeft;
        final float yc = scrolledYFloat - (float) target.mTop;
        mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        ev.setAction(MotionEvent.ACTION_CANCEL);
        ev.setLocation(xc, yc);
        if (!target.dispatchTouchEvent(ev)) {
	
        }
        mMotionTarget = null;
        return true;
    }
    if (isUpOrCancel) {
        mMotionTarget = null;
    }
    final float xc = scrolledXFloat - (float) target.mLeft;
    final float yc = scrolledYFloat - (float) target.mTop;
    ev.setLocation(xc, yc);
    if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
        ev.setAction(MotionEvent.ACTION_CANCEL);
        target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
        mMotionTarget = null;
    }
    return target.dispatchTouchEvent(ev);
}

归纳总结
1.消费了DOWN事件的View,后续事件不管触摸点是否在该View内部还是外部,都会分发给该View
2.消费了DOWN事件的View,如果后续事件在其上层父View分发过程中拦截了,那么该View会收到一个CANCEL事件
3.一个View只有可见性为VISIBLE才有可能消费触摸事件,对于那些满足条件但被设置为INVISIBLE或GONE的View,不会消费触摸事件

滑动冲突解决方式

外部拦截法(修改父View)

父View

public boolean onInterceptTouchEvent(MotionEvent ev) {
   
   switch (ev.getAction) {
     case Action_DOWN:
         return faslse; //不能拦截 否则后续全部交由父View处理
        
     case Action_MOVE:
         if(拦截条件)return true;
         else return false;
     case Action_UP:
         return false;     
 
   
   }

}

内部拦截法

父View不能拦截Action_DOWN事件 确保Action_DOWN 返回false

子View

public boolean dispatchTouchEvent(MotionEvent ev) {
   
   switch (ev.getAction) {
     case Action_DOWN:
         parent.requestDisallowInterceptTouchEvent(true);
         break;
     case Action_MOVE:
         
         if(父容器需要处理改事件) {
            parent.requestDisallowInterceptTouchEvent(false);
         }
         
         break;
     case Action_UP:
         break;    
   
   }
   return super. dispatchTouchEvent(ev);
}

参考文献

https://www.kancloud.cn/digest/defineview/120018
https://blog.csdn.net/guolin_blog/article/details/9153747
https://blog.csdn.net/abcdef314159/article/details/51119238