Android事件分发机制

641 阅读13分钟

前言

在我们平时开发中,总是会遇到滑动冲突。那么,如果要解决滑动冲突,首先就要求我们理解Android中一个非常重要的知识点:事件分析机制。

基础知识准备

Android UI层级

在了解事件分发机制之前,我们要知道Android布局层级。我们在写Activity的时候通常是通过setContentView(int layoutResID)方法设置布局,所以我们先从这个方法入手看一下布局层级。

public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID); //1
    initWindowDecorActionBar(); //2
}

public Window getWindow() {
    return mWindow;
}

由上面的代码我们可以看到,ActivitysetContentView方法实际上是调用了WindowsetContentView方法,让我们看一下Window对象。

/**
 * Abstract base class for a top-level window look and behavior policy.  An
 * instance of this class should be used as the top-level view added to the
 * window manager. It provides standard UI policies such as a background, title
 * area, default key processing, etc.
 *
 * The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {
....
}

从注释中我们可以看到Window是一个抽象类了,并且它的唯一一个子类是PhoneWindow,也就是说上面的方法调用实际上是调用了PhoneWindow的setContent方法,我们来看一下。

 @Override
public void setContentView(int layoutResID) {
    if (mContentParent == null) {
        installDecor(); //1
    } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        mContentParent.removeAllViews();
    }
    if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                getContext());
        transitionTo(newScene);
    } else {
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }
    mContentParent.requestApplyInsets();
    final Callback cb = getCallback();
    if (cb != null && !isDestroyed()) {
        cb.onContentChanged();
    }
    mContentParentExplicitlySet = true;
}

private void installDecor() {
        if (mDecor == null) {
            mDecor = generateDecor(-1); 
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); //1
            ...
        }
}

在这里我只挑出了主要的代码,第一段代码中我们可以看到通过installDecor方法去初始化DecorView,通过查看源码我们可以知道DecorView实际上是一个Framelayout。通过第二段代码的1处我们可以看到,其调用了generateLayout方法,我们来来看一下这里面做了什么。

protected ViewGroup generateLayout(DecorView decor) {
        ...
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestFeature(FEATURE_ACTION_BAR);
        }
        ...
        if ((features & (1 << FEATURE_NO_TITLE)) == 0) {
            // If no other features and not embedded, only need a title.
            // If the window is floating, we need a dialog layout
            if (mIsFloating) {
                TypedValue res = new TypedValue();
                getContext().getTheme().resolveAttribute(
                        R.attr.dialogTitleDecorLayout, res, true);
                layoutResource = res.resourceId;
            } else if ((features & (1 << FEATURE_ACTION_BAR)) != 0) {
                layoutResource = a.getResourceId(
                        R.styleable.Window_windowActionBarFullscreenDecorLayout,
                        R.layout.screen_action_bar);
            } else {
                layoutResource = R.layout.screen_title; //注释1
            }
            // System.out.println("Title!");
        }
        ...
}

上面的代码省略了很多,我们从代码中看到generateLayout方法中会根据不同的Future来给layoutResource设置布局。我们看一下注释1处的布局文件。

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:fitsSystemWindows="true">
    <!-- Popout bar for action modes -->
    <ViewStub android:id="@+id/action_mode_bar_stub"
              android:inflatedId="@+id/action_mode_bar"
              android:layout="@layout/action_mode_bar"
              android:layout_width="match_parent"
              android:layout_height="wrap_content"
              android:theme="?attr/actionBarTheme" />
    <FrameLayout
        android:layout_width="match_parent" 
        android:layout_height="?android:attr/windowTitleSize"
        style="?android:attr/windowTitleBackgroundStyle">
        <TextView android:id="@android:id/title" 
            style="?android:attr/windowTitleStyle"
            android:background="@null"
            android:fadingEdge="horizontal"
            android:gravity="center_vertical"
            android:layout_width="match_parent"
            android:layout_height="match_parent" />
    </FrameLayout>
    <FrameLayout android:id="@android:id/content"
        android:layout_width="match_parent" 
        android:layout_height="0dip"
        android:layout_weight="1"
        android:foregroundGravity="fill_horizontal|top"
        android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>

从布局文件中我们看到三个部分

ViewStub:用来显示ActionBar
第一个FrameLayout:用来显示标题
第二个FrameLayout:用来显示内容

通过上面的源码我们可以看出:Activity中包含一个Window对象,这对象是由PhoneWindow来实现。PhoneWindow中又包含一个DecorView,并将其作为跟布局。这个DecorView又分成两个部分,我们平时设置布局时其实是将布局展示在Content区域。

因为DecorView是继承自FrameLayout,而FrameLayout又继承自ViewGroup。所以我们可以将事件的流向简单的表示出来:Activity->ViewGroup->View。

事件概念

提了很多次事件,事件的具体概念是什么?其实,这里所说的事件,是指当手指从触碰到手机屏幕到离开手指屏幕所产生的一系列Touch事件,其中也包括手指在手指在屏幕上滑动所对应的操作。这些Touch事件被封装到一个MotionEvent对象中,接下来让我们看一下这个对象具体是做什么的。
实际上,事件分发机制处理的对象是手指操作屏幕时所产生的一些列MotionEvent对象。

MotionEvent事件分类和对应含义

事件分类 对应含义
MotionEvent.ACTION_DOWN A pressed gesture has started(一个按压动作已经开始了)
MotionEvent.ACTION_MOVE A change has happened between ACTION_DOWN and ACTION_UP(在点击和抬起事件中间的一些列操作)
MotionEvent.ACTION_UP A pressed gesture has finished(一个按压动作已经结束)
MotionEvent.ACTION_CANCEL A movement has happened outside of the normal bounds of the UI element(在滑动的过程中超出边界)

上面的表格中只列举了开发中最常用的几种事件类型,其实还有很多事件类型,有兴趣的小伙伴可以自行查看。MotionEvent对象中不仅封装了事件类型,同时封装了手指在操作过程中的坐标。

涉及到的方法

事件分发机制中主要涉及到三个方法:dispatchTouchEvent()、onTouchEvent()和onInterceptTouchEvent()。

方法名 方法的含义
dispatchTouchEvent() 被用来事件分发,如果事件传递到当前的View,该方法一定会被调用,默认返回false
onInterceptTouchEvent() 该方法被用来判断是否拦截某个事件,在dispatchTouchEvent方法中调用 ,返回值表示是否拦截当前事件,通常存在于ViewGroup,一般View中没有该方法
onTouchEvent() 该方法被用来处理点击事件,返回值表示是否消耗当前的事件,如果不进行消耗,该系事件序列将不会被接受

小结

从上面的分析中我们可以进行一下小结:

当手指触摸屏幕产生的一些列操作会被封装在MotionEvent中,通过Activity、ViewGroup和View调用一系列方法,找到事件的接受者并处理该事件的一个过程。

源码分析

从上面的分析我们已经知道事件的大致流向:Activity->ViewGroup->View,接下来让我们从源码的角度逐个分析。

Activity事件分发

既然事件最初是从Activity中进行传递,所以我们首先到Activity中找到dispatchTouchEvent()方法:

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction(); 
    }
    if (getWindow().superDispatchTouchEvent(ev)) { //1
        return true;
    }
    return onTouchEvent(ev);
}

注释1处的getWindow()方法获取的是Window对象,但是Window是一个抽象类,我们看一下它的唯一子类PhoneWindowsuperDispatchTouchEvent()方法

PhoneWindow.superDispatchTouchEvent
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

DecorView.superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}

我们看到在PhoneWindowsuperDispatchTouchEvent()方法实际上调用了DecorViewsuperDispatchTouchEvent()方法,这个方法内部调用了super.dispatchTouchEvent()。在基础知识准备中我们知道DecorView是继承自Framelayout,在Framelayout中是没有dispatchTouchEvent()的方法,但是Framelayout是继承自ViewGroup。所以,最后还是调用到了ViewGroupdispatchTouchEvent(),自此事件从Activity传递到了ViewGroup

ViewGroup事件分发

既然事件已经从Activity传递到了ViewGroup,让我们看一下ViewGroup具体做了什么处理。

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;
         //--------------------1----------------------
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
         //--------------------2----------------------
         final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            intercepted = true;
        }
        //--------------------3----------------------
        final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        if (!canceled && !intercepted) {
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                ...
                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                   ...
                    //--------------------4----------------------
                    for (int i = childrenCount - 1; i >= 0; i--) {
                    ...
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        //--------------------5----------------------
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        ...
                        }
                    }    
                }
            }
        }
    }
}

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        return;
    }
    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

public boolean onInterceptTouchEvent(MotionEvent ev) {
    if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
            && ev.getAction() == MotionEvent.ACTION_DOWN
            && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
            && isOnScrollbarThumb(ev.getX(), ev.getY())) {
        return true;
    }
    return false;
}

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    ...
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        if (child == null) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    ...
}

经过一番查找,终于把源码摘清。我这里是Android9.0的源码,如果跟各位的有出入,以各位手上的源码为准。闲话少说,直接看源码中都做了什么。
上面的代码共有五处注释:

注释1:这部分代码主要是清除事件接收目标同时初始化事件状态。
我们看到先判断是否是ACTION_DOWN事件,也就是手指按下的事件。
如果是,则调清除之前的事件接收目标,同时也初始化了点击状态。
这也很好理解,当触发ACTION_DOWN时说明用户重新对手机做了操作,
有可能点击了不同的控件,之前事件接收目标及其点击状态都应该重置掉。
注释2:这部分代码主要是用来判断是否要拦截事件。
首先判断了是否是ACTION_DOWN事件,或者事件接收目标是否为空。如果这两个条件都不成立,将会标识拦截。
在判断条件内我们还看到了FLAG_DISALLOW_INTERCEPT标识,这个标识表示子View是否允许父控件拦截事件,
当子View调用requestDisallowInterceptTouchEvent(boolean)方法时设置的。
只有当requestDisallowInterceptTouchEvent(boolean)传入值为true时,
表示不允许父控件拦截(上面第二段代码)。disallowIntercept默认值为false,也就是子控件没有设置禁止父控件拦截事件。
这时,会调用onInterceptTouchEvent()方法(第三段代码)。
注释3:这段代码主要来判断是否发生了ACTION_CANCEL事件。
这个事件在基础知识准备中已经有所说明,这里就不再详细讲解。
注释4:通过循环遍历找到子View。
首先是通过倒序循环遍历的方式找到每一个子View,为什么是倒序呢,。。。。
找到子View之后会调用canViewReceivePointerEvents()和isTransformedTouchPointInView()方法来判断子View的状态。
这里边包括是否可见、是否正在播放动画、点击事件的坐标是否落在子View的显示范围内。
注释5:这部分代码是ViewGroup事件分发的核心。
这里会调用dispatchTransformedTouchEvent()方法(第四段代码),在这个方法中我们可以看到,回去判断是否有子View,
如果有,继续调用子View的dispatchTouchEvent()方法;如果没有,则去调用super.dispatchTouchEvent()方法。

我们都知道ViewGroup是继承自View的,所以接下来让我们看一下View的dispatchTouchEvent()方法。

View事件分发

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

从上面的代码中我们看到,首先会获取OnTouchListener信息,也会调用OnTouchListeneronTouch()方法。如果OnTouchListener不为空,并且onTouch()方法返回true之后,就不会再执行onTouchEvent()方法,否则的话还要去执行onTouchEvent()方法。从中我们也可以看出OnTouchListener中的onTouch()方法优先级要高于onTouchEvent(MotionEvent)方法。

onTouchEvent(MotionEvent)

View的事件分发过程中调用了onTouchEvent(MotionEvent)方法,我们来看一下。

public boolean onTouchEvent(MotionEvent event) {
    ...
    //----------------------1----------------------
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    //----------------------2----------------------
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return clickable;
    }
    ...
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                if ((viewFlags & TOOLTIP) == TOOLTIP) {
                    handleTooltipUp();
                }
                if (!clickable) {
                    removeTapCallback();
                    removeLongPressCallback();
                    mInContextButtonPress = false;
                    mHasPerformedLongPress = false;
                    mIgnoreNextUpEvent = false;
                    break;
                }
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                            //----------------------3----------------------
                                performClickInternal();
                            }
                        }
                    }
                }
                break;
            case MotionEvent.ACTION_DOWN:
                if (!clickable) {
                    checkForLongClick(0, x, y);
                    break;
                }
                if (performButtonActionOnTouchDown(event)) {
                    break;
                }
                boolean isInScrollingContainer = isInScrollingContainer();
                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, x, y);
                }
                break;
            ...
        }
        return true;
    }
    return false;
}

private boolean performClickInternal() {
    ...
    return performClick();
}

public boolean performClick() {
    ...
    final boolean result;
    final ListenerInfo li = mListenerInfo;
    if (li != null && li.mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    ...
    return result;
}

注释1处检查了该View是否是可点击或者是长按点击,并记录下来;注释2处我们从英文注释中可以看到,虽然一个View是一个disabled状态,但是只要是clickable任然会消费事件,只是不做任何反馈而已。
之后会根据事件类型进行判断,当处于ACTION_UP也就是说当手指离开时,会触发performClickInternal方法,这个方法去执行内部点击,具体调用方法在最后两段代码中。从最后一段代码中我们看到了很熟悉的代码块li.mOnClickListener.onClick(this),这里的OnClickListener就是我们自己设置的。但是我们在ACTION_DOWN中没有看到执行点击的方法。所以,执行点击的方法不是在触摸时产生,而是在手指离开时产生。
从上面的源码中我们也可以看到, 最后从网上找了一张事件分发机制流程图,感谢各位大佬。

举例说明

下面我会通过一个案列加以说明

public class MyActivity extends AppCompatActivity {
    private MyButton my_buttom;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        my_buttom = findViewById(R.id.my_buttom);
        my_buttom.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Log.i("EventDemo", "MyButton--->onClick");
            }
        });
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("EventDemo", "MyActivity--->dispatchTouchEvent"+ev.getAction());
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("EventDemo", "MyActivity--->onTouchEvent"+event.getAction());
        return super.onTouchEvent(event);
    }
}
public class MyLinearLayout extends LinearLayout {
    public MyLinearLayout(Context context) {
        super(context);
    }
    public MyLinearLayout(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    public MyLinearLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        super(context, attrs, defStyleAttr, defStyleRes);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        Log.i("EventDemo", "MyLinearLayout--->dispatchTouchEvent" + ev.getAction());
        return super.dispatchTouchEvent(ev);
    }
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.i("EventDemo", "MyLinearLayout--->onInterceptTouchEvent" + ev.getAction());
        return super.onInterceptTouchEvent(ev);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("EventDemo", "MyLinearLayout--->onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}
public class MyButton extends AppCompatButton {
    public MyButton(Context context) {
        super(context);
    }
    public MyButton(Context context, AttributeSet attrs) {
        super(context, attrs);
    }
    public MyButton(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }
    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        Log.i("EventDemo", "MyButton--->dispatchTouchEvent" + event.getAction());
        return super.dispatchTouchEvent(event);
    }
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.i("EventDemo", "MyButton--->onTouchEvent" + event.getAction());
        return super.onTouchEvent(event);
    }
}
<com.example.eventdemo.MyLinearLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context=".MainActivity">
    
    <com.example.eventdemo.MyButton
        android:id="@+id/my_buttom"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="点我" />
        
</com.example.eventdemo.MyLinearLayout>

正常情况

我们直接默认设置,直接运行点击看一下效果。

我们看到这一次点击一共产生了两个事件:0表示ACTION_DOWN;1表示ACTION_UP。我们可以看到,事件首先是从Activity传入到ViewGroup中。
第一步:事件首先传递到Activity中,调用了Activity的dispatchTouchEvent方法;
第二步:事件进过Activity之后,传入了ViewGroup中,调用了dispatchTouchEvent方法。在这个方法中调用了onInterceptTouchEvent,查看是否要拦截事件,这里返回的都是默认值,所以不进行拦截。
第三步:经过ViewGroup的事件分发后,将时间传递到了View中。首先还是调用了View的dispatchTouchEvent方法。因为View中是没有onInterceptTouchEvent方法的,所以会调用View的onTouchEvent方法,因为这里的View实际上是一个Button,而Button默认的是可以点击的,所以在onTouchEvent方法中返回true,这就表明该事件的接收者是该Button。
第四步:当手指抬起时,会产生一个ACTION_UP事件,其分发过程跟上面一致。但是当传入到View中时会调用performClickInternal方法最终调用onClick方法。

比较OnTouchListener.onTouch()和onTouchEvent()方法的优先级

我们在MyActivity中添加如下代码:

my_buttom.setOnTouchListener(new View.OnTouchListener() {
        @Override
        public boolean onTouch(View v, MotionEvent event) {
            Log.i("EventDemo", "MyButton--->onTouch");
            return false;
        }
});

我们来看一下运行结果:

从运行结果来看,我们可以看到OnTouchListener.onTouch()方法的优先级要高于onTouchEvent()方法。

总结

写了这么多,让我们总结一下事件分发机制。

1、通常情况下,事件的序列是ACTION_DOWN开始,到ACTION_UP结束。
2、当一个事件由一个View决定拦截时(ACTION_DOWN触发时决定拦截),该事件序列都会由其接收。否则该事件序列将不再被其接收。
3、从优先级上讲:OnTouchListener.onTouch > onTouchEvent > onClickListener。
4、当一个View是disable状态时,但它是可点击状态时,事件还是由它来接收,只是不做任何反馈。
5、事件分发机制的核心设计模式是责任链设计模式,这个模式在okhttp的源码中也能看到。

到这里事件分发机制算是过了一遍,如果有什么写的不对的地方或者是有哪里遗漏了,还请各位大佬批评指正。