从源码角度理解Android View的事件分发机制(一)

开个头

事件分发机制,应该有很多android开发者都望而却步。心里大概都知道事件分发机制不就是各种手势事件从父布局,传到了View中。但是一旦面试的时候,让你谈一谈事件分发机制,就愣住了,感觉没什么好说的,或者说不出来个12345。接下来,让我们从源码看看这个事件分发到底是怎么分发的。

标题叫做View的事件分发机制,有很多人叫android的事件分发机制。其实这个事件分发主要还是对View来说的,因为只有View才能监听各种点击或者滑动事件,所以,我们大可叫他View的事件分发机制。

事件的传递规则

首先我们要知道三个方法

//字面意思了解一下,分发触摸事件。在View和ViewGroup里面都有这个方法
//1、在View类里,是用来判断View是否是把一个事件(运动事件,事件,手势事件都差不多一个意思)消耗掉了。
//消耗掉了的意思就是,这个事件GG了,不会继续往下传或者往上穿了。
//2、在ViewGropup里,是判断,要不要拦截这个事件,要不要发放给他的子View。
public boolean dispatchTouchEvent(MotionEvent event){}

//字面意思了解一下,拦截触摸事件。只有ViewGroup里面有这个方法,并且默认返回false
//这个方法就是ViewGroup在dispatchTouchEvent方法里判断,要不要拦截传过来的事件。
//默认false意思就是默认不拦截事件。
public boolean onInterceptTouchEvent(MotionEvent ev) {}

//这个方法用来处理事件。只有View里面有这个方法。
//这个方法里面就是具体处理事件的代码。返回值表示是否消耗掉了事件。
public boolean onTouchEvent(MotionEvent event){}

了解了这三个方法后,大致屡一下一个事件传递或者分发的过程。

用单击事件来举例好了(一个ACTION_DOWN,一个ACTION_UP)。

用户先点击了一下屏幕,首先收到响应的是当前的Activity,Activity收到后,传递给当前的Window(也就是PhoneWindow),Window收到后,再传递给顶层的View(DecorView),到了DecorView之后,就开始根据布局文件,从父控件依次往子控件传递,直到有一个View或者ViewGroup消耗掉了这个事件(有一个dispatchTouchEvent()方法返回true)。如果没有View或者ViewGroup消耗这个事件(所有的dispatchTouchEvent()都返回false),那么这个事件就会返回到Activity中,最终由Activity里面的ouTouchEvent()方法消耗掉(可以先这么理解其实也没做什么处理)。

上面大致说了下,一个事件怎么传到了我们的布局的ViewGroup里面,接下来是传递规则:对于布局的根布局的那个ViewGroup来说,收到DecorView传递过来的事件,首先会走ViewGroup的dispatchTouchEvent()方法,如果这个ViewGroup的interceptTouchEvent()方法返回true,表示他要拦截这个事件,那么这个事件就会走这个ViewGroup的super.onTouchEvent()方法,也就是View的onTouchEvent()方法。ViewGroup的dispatchTouchEvent()就会返回true,然后这个事件就被消耗了,然后GG。如果ViewGroup的interceptTouchEvent()返回false,即不拦截这个事件,那么,就会开始轮询ViewGrouop里面的子View的dispatchTouchEvent()方法。然后重复上面的逻辑,直到有一个View或者ViewGroup的dispatchTouchEvent()方法返回true,这个事件最终被消耗掉。

这里需要注意的是onTouchListener这个类,如果一个View设置了onTouchListener,并且在onTouchListener的onTouch()方法里返回了true,那么这个事件不会传递到onTouchEvent()这个方法里,而是由onTouch()这个方法直接消耗掉。如果onTouch()方法返回false,那么onTouchEvent()方法和onTouch()都会监听到这个事件。所以onTouchListener的优先级是比onTouchEvent()高的,在开发中要注意这个细节。

源码解析

要上源码了。

1.Activity对事件分发的过程
android.app.Activity

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //空方法
            onUserInteraction();
        }
        //可以看到是获取当前的Window,然后执行superDispatchTouchEvent(ev)方法。
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

然后我们到Window里面看源码

android.view.Window

//是个抽象方法,应该找Window的实现类里面重写的方法。
//大家应该都知道那个实现类就是PhoneWindow
public abstract boolean superDispatchTouchEvent(MotionEvent event);

/**
 *android源码自己对Window的注释。也说明了,唯一实现类就是android.internal.policy.PhoneWindow
 * <p>The only existing implementation of this abstract class is
 * android.internal.policy.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window {

我们找到PhoneWindow源码

com.android.internal.policy.PhoneVindow

    @Override
    public boolean superDispatchKeyEvent(KeyEvent event) {
        //可以看到这里调用的是mDecor
        return mDecor.superDispatchKeyEvent(event);
    }

    //这个mDecor就是我们熟知的DecorView,也就是我们界面布局最顶层的那个父布局,DecorView其实
    //就是一个FrameLayout,他的id为R.id.content。
    // This is the top-level view of the window, containing the window decor.
    private DecorView mDecor;

接着看DecorView

com.android.internal.policy.DecorView

    public boolean superDispatchTouchEvent(MotionEvent event) {
        //这里调用的是super.dispatchTouchEvent(event),因为我们已经知道了DecorView 是一个FrameLayout,所以
        //这里其实就是调用ViewGroup里面的dispatchTouchEvent()方法
        return super.dispatchTouchEvent(event);
    }

至此,Activity现在已经把点击事件传到了我们最顶层的ViewGroup(DecorView)里面了,接下来要重点分析ViewGroup和View的事件分发了。

2.ViewGroup的和View的分发过程

话不多说,直接ViewGroup的dispatchTouchEvent的源码(为了方便阅读,删减了很多,只挑了写关键性的代码)

 @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean handled = false;
        if (onFilterTouchEventForSecurity(ev)) {
            ...
            // Handle an initial down.
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                ...
                //进来之后先判断如果是ACTION_DOWN,就执行了下面的方法
                //这个方法主要是对FLAG_DISALLOW_INTERCEPT初始化,也就是重置;下面附有源码。
                //也就是(mGroupFlags & FLAG_DISALLOW_INTERCEPT) == 0,下面会用到。
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            //然后这里做了一个判断
            //如果这个事件ACTION_DOWN事件 或者 mFirstTouchTarget!=null 才会进入if语句,否则 
            //intercept直接=true了。还记得上面说的我们用一次点击事件来举例(一个ACTION_DOWN,一个ACTION_UP)
            //ACTION_DWON发生的时候,那就不管mFirstTouchTarget,直接进入if语句。
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //这个FLAG_DISALLOW_INTERCEPT不就是在刚进来的时候,对它重置了,所以disallowIntercept=false
                //开始执行onInterceptTouchEvent(ev)这个方法。
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //上面我们也说了,这个方法默认返回false。所以intercepted=false。
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action);
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
            ...
            //上面已经得到intercepted = false,所以我们ACTION_DOWN事件进入了这个if语句里面
            if ( !intercepted) {
                      ...
                        //下面的代码,可以看出来,就是找到这个viewGroup里面所有的子View然后遍历
                        final View[] children = mChildren;
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            ...
                            resetCancelNextUpFlag(child);
                            //然后就是dispatchTransformedTouchEvent()这个较长的方法。这个方法其实
                            //就是执行了child即子View的dispatchTouchEvent(ev)方法,也就是说通过这个方法
                            //我们的ACTION_DOWN事件从ViewGroup的dispatchTouchEvent(ev)里面
                            //传到了child的dispatchTouchEvent(ev),源码可以从下面看到。
                            //并且只有当child.dispatchTouchEvent(ev)返回true,也就说child消耗了
                            //ACTION_DOWN事件,才会走if语句,才会给mFirstTouchTarget赋值,否则它还是null
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                ...
                                //这里的addTouchTarget()方法是对上面提到的mFirstTouchTarget赋值
                                //也就是说mFirstTouchTarget!=null了,可以在下面看源码
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                //这个break。很关键,就是一旦有child消耗掉了ACTION_DOWN,就直接跳出循环
                                //所以也是说,同一个事件不会有两个View同时处理。
                                break;
                            }
                        }
            }
            //从上面我们得知如果child消耗了ACTION_DOWN事件mFirstTouchTarget!=null,child不消耗
            //或者说child处理了ACTION_DOWN但是dispatchTouchEvent返回false,mFirstTouchTarget=null
            //所以这里是所有的child都没有消耗掉ACTION_DOWN或者是这个ViewGroup没有子View。
            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                //如果child没有消耗ACTION_DOWN事件(注意,这里意思是,要么child处理了ACTION_DOWN但是
                //dispatchTouchEvent(ev)返回false,要么直接不处理直接返回false),执行这个很长的方法
                //上面已经见过这个方法了,注意的是第三个参数child=null,所以这里其实是调用了ViewGroup的
                //onTouchEvent(ev)方法(本质是ViewGroup父类View的onTouchEvent()方法,因为ViewGroup里面
                //就没有onTouchEvent(ev)这个方法,或者说并没有重写这个方法)在下面介绍View对事件处理的时候还会讲
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                 //如果child消耗掉了ACTION_DOWN事件(这里注意的地方和上面一样,只是dispatchTouchEvent(ev)
                 //返回了true,我们并不关心child是否真正处理了ACTION_DOWN。dispatchTouchEvent(ev)返回的
                 //那个boolean类型就是child有没有消耗的标志)
                 //又是这个很长的方法,我们应该已经很熟悉了,这个方法的返回值其实就是dispatchTouchEvent(ev)
                 //的返回值。所以handled =true
                   if (dispatchTransformedTouchEvent(ev, cancelChild, 
                       target.child, target.pointerIdBits)) {
                    handled = true;
                   }
                }
            }
        }
        //这个return handled总结上面的来说就是
        //1、如果ViewGroup有一个child消耗掉了ACTION_DOWN(child.dispatchTouchEvent(ev)返回true)就返回true
        //2、如果ViewGroup没有子View或者所有的child都不消耗ACTION_DOWN,就返回false。
        //然后这个返回值,首先返回给DecorView,然后再返给PhoneWindow,最终返回给Activity。
        //所以如果返回true那么ACTION_DOWN事件至此GG,如果是false,那么ACTION_DOWN会由Activity的
        //onTouchEvent(ev)处理,处理完了,ACTION_DWON事件也GG。
        return handled;
    }

    //重置FLAG_DISALLOW_INTERCEPT的值。
    private void resetTouchState() {
        clearTouchTargets();
        resetCancelNextUpFlag(this);
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        mNestedScrollAxes = SCROLL_AXIS_NONE;
    }
    //通过这个方法把ViewGroup吧事件传了 child的dispatchTouchEvent(ev)
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        ...
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispa tchTouchEvent(event);
            } else {
                //因为是通过ViewGroup遍历过来的,所以child肯定不为null,就执行了这个方法,把事件传到了child里面
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
        ...
    }   
    //给mFirstTouchTarget赋值,mFirstTouchTarget!=null。
    private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        //赋值
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

我已经在源码里做了大量的详细的注释,大家仔细看看源码。我就不再做过多的解释了,还是安心看源码吧。
如果错误,欢迎指正。

猜你喜欢

转载自blog.csdn.net/xy4_android/article/details/80452272