https://www.jianshu.com/p/b9cbc264ba03
(我在简书上的文章)

还是站在巨人的肩膀上
https://blog.csdn.net/a553181867/article/details/51287844 (有点难懂,记住他最后的总结即可。)

https://blog.csdn.net/zhangjin12312/article/details/78340998 (自称最好的事件分发博客)
立马找到一个错误:

Activity的dispatchTouchEvent并不是ViewGroup处理的

后来发现这篇文章是转载的,原文链接应该是:https://www.jianshu.com/p/38015afcdb58 (从源码出发分析)

由Demo来分析

在上面这篇文章的评论中我又发现了一篇佳作:https://www.cnblogs.com/fuly550871915/p/4983682.html (从Demo出发分析,更加直观易懂,但是可能忽略源码许多细节的地方),它将事件分发一分为二。

  • 事件的传递

    针对的是OnInterceptTouchEvent方法,传递是由外到内,自上而下,也就是Activity --->ViewGroup--->View

  • 事件的处理

    针对的是onTouchEvent方法,和事件的传递顺序正好相反,处理是由内到外,自下而上,也就是View--->ViewGroup--->Activity
    只有事件传递过来了,才有资格说处理。

由源码来分析

下面,对https://www.jianshu.com/p/38015afcdb58 分析。
首先,我要纠正我上面指出的那个错误,其实那个是正确的。activity的事件分发流程是activity ---> PhoneWindow ---> DecorView(ViewGroup),所以那个作者说的没有错,是跨度太大了。

  • 关于"2.2.3 Demo讲解"

    作者在ViewGroup的事件分发里写了这个Demo,但是Demo中Button是个View,所以例子的结尾没太说明白,这里我做一下补充:

    //Android 5.0 View.java源码
    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
    
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            // A disabled view that is clickable still consumes the touch 
            // events, it just doesn't respond to them. (即使View是disable状态,只要它是可点击的,它也会获取触摸事件,只是不去响应它们。)
    
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }
    
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
    
        //设置了点击事件,会走到这里。
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        ...
                        if (!mHasPerformedLongPress) {
                            // This is a tap, so remove the longpress check
                            removeLongPressCallback();
    
                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                                // Use a Runnable and post this rather than calling
                                // performClick directly. This lets other visual state
                                // of the view update before click actions start.
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
    
                        ...
                    }
                    break;
    
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
    
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
    
                    // Walk up the hierarchy to determine if we're inside a scrolling container.
                    boolean isInScrollingContainer = isInScrollingContainer();
    
                    // For views inside a scrolling container, delay the pressed feedback for
                    // a short period in case this is a scroll.
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        // Not inside a scrolling container, so show the feedback right away
                        setPressed(true, x, y);
                        checkForLongClick(0);
                    }
                    break;
                    ...
            }
    
            return true;            //消费这个事件,不会再向上回调。
        }
    
        return false;
    }

    所以作者博客里下的结论:Button的onClick()将事件消费掉了,从事件分发上说,就是Button(View)的onTouchEvent直接返回了true,所以它的父类ViewGroup不会收到事件的处理请求 。

  • 关于"2.3.3 Demo讲解"

    这个View涉及的是同一个View的事件分发,setOnTouchListener与setOnClickListener,
    setOnTouchListener的onTouchEvent的返回值为啥会干扰setOnClickListener的执行呢?

    //Android 4.4 View.java源码
    public boolean dispatchTouchEvent(MotionEvent event) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }
    
        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                return true;        //mOnTouchListener.onTouch结果返回值是true的话,就直接返回了,不会往后执行onTouchEvent。
            }
    
            if (onTouchEvent(event)) {
                return true;
            }
        }
    
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
        return false;
    }

    结合源码分析,mOnTouchListener.onTouch结果返回值为true,onTouchEvent方法不会执行,而click方法是在onTouchEvent里执行,所以click方法也不会执行。

  • 作者的伪代码说明3个方法间的关系

    /**
    * 点击事件产生后
    */ 
    // 步骤1:调用dispatchTouchEvent()
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
    boolean consume = false; //代表 是否会消费事件
    
    // 步骤2:判断是否拦截事件
    if (onInterceptTouchEvent(ev)) {
      // a. 若拦截,则将该事件交给当前View进行处理
      // 即调用onTouchEvent ()方法去处理点击事件
        consume = onTouchEvent (ev) ;
    
    } else {
    
      // b. 若不拦截,则将该事件传递到下层
      // 即 下层元素的dispatchTouchEvent()就会被调用,重复上述过程
      // 直到点击事件被最终处理为止
      consume = child.dispatchTouchEvent (ev) ;
    }
    
    // 步骤3:最终返回通知 该事件是否被消费(接收 & 处理)
    return consume;
    
    }
  • Down的后续事件

    众多的事件分发博客都会详说到Down事件,但是很少说明MOVE、UP等后续事件对事件分发的影响。
    事件分发.jpg
    所以DOWN事件是否消费直接影响MOVE、UP事件是否执行。代码里是如何体现的呢?

我自己的总结

1.关于事件分发的那几个方法

  • ViewGroup独有onInterceptTouchEvent方法,View独有onTouchEvent方法(但是可以被它的子View复写,而且通常直接继承它的View都会复写。继承ViewGroup的类,如纯布局类,一般不会对onTouchEvent进行复写,但是也有例外,如ToolBar。所以,正是由于对onTouchEvent方法的复写,导致各种控件嵌套后冲突不断。)
  • ViewGroup和View都有dispatchTouchEvent方法,ViewGroup复写了View的dispatchTouchEvent方法,实现不同。

    • 1.ViewGroup的dispatchTouchEvent方法
      判断onInterceptTouchEvent方法是否拦截事件,缺省情况下会遍历子View,如果View满足条件(如touch事件的坐标落在View上)就会调用dispatchTransformedTouchEvent方法,然后可能会调用到子View的dispatchTouchEvent方法或者父类的dispatchTouchEvent方法。这就是事件传递,我们最需要关注的地方就是事件传递的条件

    关于ViewGroup的dispatchTouchEvent方法,我还要强调一个重点: mFirstTouchTarget。我们总是在说,如果View的onTouchEvent返回false,事件就会交由它的父布局的onTouch方法处理。这个结论如何从代码里推导出来呢?
    》》Down事件触发,mFirstTouchTarget置为null。
    image.png
    》》假如有某个子View处理了事件,addTouchTarget方法会执行,里面给mFirstTouchTarget赋值了。
    image.png
    》》假如没有一个子View处理事件,mFirstTouchTarget是null,就会执行下面代码:
    image.png
    看dispatchTransformedTouchEvent方法
    image.png

    super.dispatchTouchEvent

    注意是super,否则就是死循环了。也就会调用到了onTouchEvent方法(这个方法是View类里的)。所以“下级View不处理,上级View就处理”这个判断是在上级View里判断的,并非是我们一贯的监听回调模式,并非在下级View里回调上级View的onTouch事件

    • 2.View的dispatchTouchEvent方法
      image.png
      关于View的dispatchTouchEvent方法,我只想强调2点:
      1)通过setOnTouchListener设置的监听者比View本身的onTouchEvent处理优先级高,监听者一旦处理了Touch事件,View本身的onTouchEvent方法则不会被执行
      2)View的dispatchTouchEvent的返回值代表着View对事件的处理结果,看官方的注释:
      image.png

对于ViewGroup而言,dispatchTouchEvent的返回值代表着内层View或者自己对事件的处理结果。

由此可见,View的dispatchTouchEvent方法只是对onTouchEvent的一个封装而已,而ViewGroup的dispatchTouchEvent方法是对View的dispatchTouchEvent方法遍历和对自己的onTouchEvent的一个封装而已。

2.实践是检验真理的唯一标准

下面,我就亲自写几个Demo亲自验证上面的理论。
自定义一个View和ViewGroup,用来打印日志。(执行方法后的日志,带有handle的,只是标识当前方法的执行结果,日志打印出来的执行顺序是乱序的,不要管。)
View类

public class MyView extends View {
    private static final String TAG = "MyView";
    public MyView(Context context) {
        super(context);
    }

    public MyView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MyView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //打印方法的执行顺序
        Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev));
        boolean result = super.dispatchTouchEvent(ev);
        //打印方法的执行结果(不要管顺序,看handle即可。)
        Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev)  + ",handle:" + result);
        return result;
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
        if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        } if(MotionEvent.ACTION_MOVE == event.getAction()){
            result = false;
        }
        else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

    private String getEventName(MotionEvent ev){
        if(MotionEvent.ACTION_DOWN == ev.getAction()){
            return "ACTION_DOWN";
        }else if(MotionEvent.ACTION_MOVE == ev.getAction()){
            return "ACTION_MOVE";
        }else if(MotionEvent.ACTION_UP == ev.getAction()){
            return "ACTION_UP";
        }
        return  "other";
    }

}

ViewGroup类

public class MyViewGroup extends ViewGroup {
    private static final String TAG = "MyViewGroup";
    public MyViewGroup(@NonNull Context context) {
        super(context);
    }

    public MyViewGroup(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public MyViewGroup(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //打印方法的执行顺序
        Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev));
        boolean result = super.dispatchTouchEvent(ev);
        //打印方法的执行结果(不要管顺序,看handle即可。)
        Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev)  + ",handle:" + result);
        return result;
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "onInterceptTouchEvent: "  + getTag() + ",name:" + getEventName(ev) );
        boolean result = super.onInterceptTouchEvent(ev);
        Log.e(TAG, "onInterceptTouchEvent: "  + getTag() + ",name:" + getEventName(ev)  + ",handle:" + result);
        return result;
    }

    @Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {

    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
        if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        }else if(MotionEvent.ACTION_MOVE == event.getAction()){
            result = true;
        }
        else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

    private String getEventName(MotionEvent ev){
        if(MotionEvent.ACTION_DOWN == ev.getAction()){
            return "ACTION_DOWN";
        }else if(MotionEvent.ACTION_MOVE == ev.getAction()){
            return "ACTION_MOVE";
        }else if(MotionEvent.ACTION_UP == ev.getAction()){
            return "ACTION_UP";
        }
        return  "other";
    }
}
  • Demo1:只有View-最简单,只有一个单独的MyView。
    布局:
    image.png
    输出日志1-1:

    MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
    MyView: onTouchEvent: btn,name:ACTION_DOWN
    MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:false
    MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:false

    可以发现,View默认onTouchEvent的ACTION_DOWN事件处理为false,所以也不会收到后续的MOVE、UP事件。
    》》那么,在onTouchEvent里判断一下,如果ACTION_DOWN事件就返回true,看看是什么结果?
    修改MyView的onTouch方法:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
        if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        }
        else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

    轻轻快速点击,输出日志1-2:

    MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
    MyView: onTouchEvent: btn,name:ACTION_DOWN
    MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:true
    MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
    MyView: dispatchTouchEvent: btn,name:ACTION_MOVE
    MyView: onTouchEvent: btn,name:ACTION_MOVE
    MyView: onTouchEvent: btn,name:ACTION_MOVE,handle:false
    MyView: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:false
    MyView: dispatchTouchEvent: btn,name:ACTION_UP
    MyView: onTouchEvent: btn,name:ACTION_UP
    MyView: onTouchEvent: btn,name:ACTION_UP,handle:false
    MyView: dispatchTouchEvent: btn,name:ACTION_UP,handle:false

    可以发现View的ACTION_DOWN事件处理为true,后续MOVE、UP事件都能接收,且默认处理结果都为false
    》》为了满足我的好奇心,在onTouchEvent里,在上面的基础上修改一下,如果ACTION_DOWN 处理结果为true,ACTION_MOVE处理结果为true,看看是什么结果?
    继续修改MyView的onTouch方法:

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
     if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        }else if(MotionEvent.ACTION_MOVE == event.getAction()){
            result = true;
        }else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

    输出日志1-3:

    MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
    MyView: onTouchEvent: btn,name:ACTION_DOWN
    MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:true
    MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
    MyView: dispatchTouchEvent: btn,name:ACTION_MOVE
    MyView: onTouchEvent: btn,name:ACTION_MOVE
    MyView: onTouchEvent: btn,name:ACTION_MOVE,handle:true
    MyView: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:true
    MyView: dispatchTouchEvent: btn,name:ACTION_UP
    MyView: onTouchEvent: btn,name:ACTION_UP
    MyView: onTouchEvent: btn,name:ACTION_UP,handle:false
    MyView: dispatchTouchEvent: btn,name:ACTION_UP,handle:false

    由此可见,MOVE事件的处理结果不影响UP事件的传递和处理结果
    当然,除了修改源码,也可以通过setOnTouchListener来修改各个事件的处理结果。

  • Demo2:只有ViewGroup- 只有一个单独的MyViewGroup。
    布局:
    image.png
    输出日志2-1:

    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN
    MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN
    MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN,handle:false
    MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN
    MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN,handle:false
    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:false

    可见,ViewGroup默认是不拦截事件且不处理onTouchEvent的,所以也就没有后续的MOVE、UP事件。
    》》那么,在onTouchEvent里判断一下,如果ACTION_DOWN事件就返回true,看看是什么结果?

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
        if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        }else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

    输出日志2-2:

    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN
    MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN
    MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN,handle:false
    MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN
    MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN,handle:true
    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE
    MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE
    MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE,handle:false
    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:false
    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP
    MyViewGroup: onTouchEvent: btn,name:ACTION_UP
    MyViewGroup: onTouchEvent: btn,name:ACTION_UP,handle:false
    MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP,handle:false

    可以看到,ViewGroup的DOWN事件处理结果若为true,MOVE、UP事件都会处理,且默认处理结果都为false(和View一致的)

》》为了满足我的好奇心,在onTouchEvent里,在上面的基础上修改一下,如果ACTION_DOWN 处理结果为true,ACTION_MOVE处理结果为true,看看是什么结果?
继续修改MyRelativeLayout的onTouch方法:

  @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
        if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        }else if(MotionEvent.ACTION_MOVE == event.getAction()){
            result = true;
        }
        else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

输出日志2-3:

MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN
MyViewGroup: onInterceptTouchEvent: btn,name:ACTION_DOWN,handle:false
MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN
MyViewGroup: onTouchEvent: btn,name:ACTION_DOWN,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE
MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE
MyViewGroup: onTouchEvent: btn,name:ACTION_MOVE,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:true
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP
MyViewGroup: onTouchEvent: btn,name:ACTION_UP
MyViewGroup: onTouchEvent: btn,name:ACTION_UP,handle:false
MyViewGroup: dispatchTouchEvent: btn,name:ACTION_UP,handle:false

可见,对MOVE的处理结果修改,不影响UP事件的传递和默认处理结果。

对Demo1、Demo2做一个总结:

对于onTouchEvent方法,View和ViewGroup的处理结果默认是一致的,默认都是不处理。一旦DOWN事件处理结果为true,MOVE、UP事件也会处理且处理结果默认为false。其实不用打印日志也知道,ViewGroup直接继承的View,且没有对onTouchEvent作复写,自然而然处理结果是一致的呢。

对于onTouchEvent方法,View的直接子View一般都会复写,默认的处理过程都有点不太一样。比如TextView默认就是处理全部的事件。这也导致了事件冲突由于控件不同,现象千奇百怪,所以有必要对发生冲突的控件的onTouchEvent的实现过程有一定的了解。

  • Demo3:ViewGroup+View。
    由于ViewGroup无法直接布局,所以使用RelativeLayout来替代ViewGroup(RelativeLayout直接继承ViewGroup并且没有对onTouchEvent进行复写,所以不会对我们后面的事件分析产生误导。)

    public class MyRelativeLayout extends RelativeLayout {
    private static final String TAG = "MyRelativeLayout";
    public MyRelativeLayout(@NonNull Context context) {
        super(context);
    }
    
    public MyRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }
    
    public MyRelativeLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        //打印方法的执行顺序
        Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev));
        boolean result = super.dispatchTouchEvent(ev);
        //打印方法的执行结果(不要管顺序,看handle即可。)
        Log.e(TAG, "dispatchTouchEvent: " + getTag() + ",name:" + getEventName(ev)  + ",handle:" + result);
        return result;
    }
    
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e(TAG, "onInterceptTouchEvent: "  + getTag() + ",name:" + getEventName(ev) );
        boolean result = super.onInterceptTouchEvent(ev);
        Log.e(TAG, "onInterceptTouchEvent: "  + getTag() + ",name:" + getEventName(ev)  + ",handle:" + result);
        return result;
    }
    
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result = super.onTouchEvent(event);
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }
    
    private String getEventName(MotionEvent ev){
        if(MotionEvent.ACTION_DOWN == ev.getAction()){
            return "ACTION_DOWN";
        }else if(MotionEvent.ACTION_MOVE == ev.getAction()){
            return "ACTION_MOVE";
        }else if(MotionEvent.ACTION_UP == ev.getAction()){
            return "ACTION_UP";
        }
        return  "other";
    }
    }

布局:
image.png
日志输出3-1(不要理会dispatchTouchEvent方法带handle的日志的顺序):

MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:false
MyRelativeLayout: onTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onTouchEvent: rl2,name:ACTION_DOWN,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN,handle:false

由以上日志我们可以分析出以下几点信息:

1.事件传递的顺序:由外到内(由最外层布局向最里层)
2.事件处理的顺序:外层优先拦截,如果拦截,自己处理,不会再交由内层处理;如果不拦截,交由内层处理,内层如果处理结果为false,那么外层就会去处理。(看倒数第二条日志可以分析定论)
但是这里有个大前提:这里的事件一定是DOWN事件,对于内层没有处理的其它事件,外层是不会进行处理的。

所以事件处理,记住上面2条规律即可。其它的都是在上面的基础之上排列组合,然后不同的View的拦截、事件处理流程不一样而已

为了验证上面的结论,现在通过一些实验来证明一下。
》》修改MyView的onTouchEvent方法,DOWN事件处理结为true。

@Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event));
        boolean result;
        if(MotionEvent.ACTION_DOWN == event.getAction()){
            result = true;
        }
        else{
            result = super.onTouchEvent(event);
        }
        Log.e(TAG, "onTouchEvent: " + getTag() + ",name:" + getEventName(event)  + ",handle:" + result);
        return result;
    }

输出日志3-2:

MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_DOWN,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN
MyView: onTouchEvent: btn,name:ACTION_DOWN,handle:true
MyView: dispatchTouchEvent: btn,name:ACTION_DOWN,handle:true
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_DOWN,handle:true
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_MOVE
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_MOVE
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_MOVE,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_MOVE
MyView: onTouchEvent: btn,name:ACTION_MOVE
MyView: onTouchEvent: btn,name:ACTION_MOVE,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_MOVE,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_MOVE,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_UP
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_UP
MyRelativeLayout: onInterceptTouchEvent: rl2,name:ACTION_UP,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_UP
MyView: onTouchEvent: btn,name:ACTION_UP
MyView: onTouchEvent: btn,name:ACTION_UP,handle:false
MyView: dispatchTouchEvent: btn,name:ACTION_UP,handle:false
MyRelativeLayout: dispatchTouchEvent: rl2,name:ACTION_UP,handle:false

可以看到内层的MyView对MOVE、UP的处理结果为false,和DOWN事件不一样的是,外层的MyRelativeLayout不会再做处理。

3.事件冲突实战

  • 冲突类型的划分

    按照冲突的对象来划分,可以分为:
    1.同一控件内部冲突
    如onTouch与onClick的事件冲突。
    2.不同控件嵌套冲突
    按照冲突的类型划分,可以分为:
    1.点击冲突
    2.滑动冲突

典型事件冲突分析

1.同一控件的onTouch与onClick的事件冲突。(点击冲突
看这篇博客:https://www.jianshu.com/p/d07bd8e0ac70
作者的需求是:在View移动之后,不要执行点击事件。
作者的解决思路:onTouch仍然全部return true,然后在UP事件里判断View的移动范围,认为是点击事件就直接手动执行click事件(他用的反射,其实performClick就可以,下面评论区已经有人指出)。
但是,作者并没有对这个问题的根本性原因作出解答,为什么onTouch事件返回true,点击事件失效?

原因:

上面的博文中我已经强调,View的setOnTouchListener的onTouch方法优先于View自身的onTouchEvent方法执行,并且互斥。而View的点击等事件,是在View自身的onTouchEvent方法里实现的,在UP事件分支里执行。调用View的setOnTouchListener方法并且onTouch返回true,导致了View自身的onTouchEvent方法不会被执行,那么点击事件就更加不会被执行了。
2.滑动冲突
提起滑动冲突,最臭名昭著的当属ScrollView了,关于它的各种常见冲突,我总结了一下网上的资料如下:

深入

上面的博客只涉及到Activity的dispatchTouchEvent方法讲解,那么activity的这个方法又是在哪里被调用的呢
https://blog.csdn.net/stonecao/article/details/6759189

分类: view体系

0 条评论

发表回复

您的电子邮箱地址不会被公开。