简单理解Android事件分发机制(下)——走进源码解析原理

本篇文章将从源码的角度解析事件分发机制的详细内容。关于上篇文章的那些情况迥异的分发处理过程,是如何在源码中实现的?本篇文章将逐一揭晓。

一、分发机制中三个方法的关系

上篇文章关于dispatchTouchEvent()方法,onInterceptTouchEvent()方法和onTouchEvent ()方法的流程进行了梳理。那么在源码实现中,三者之间的关系具体是什么样的?用一段伪代码来介绍。

/ 点击事件产生后,会直接调用dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent ev) {
   //代表是否消耗事件
    boolean consume = false;
    if (onInterceptTouchEvent(ev)) {
    //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
    //则该点击事件则会交给当前View进行处理
    //即调用onTouchEvent ()方法去处理点击事件
      consume = onTouchEvent (ev) ;
    } else {
      //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
      //则该点击事件则会继续传递给它的子元素
      //子元素的dispatchTouchEvent()就会被调用,重复上述过程
      //直到点击事件被最终处理为止
      consume = child.dispatchTouchEvent (ev) ;
    }
    return consume;
   }

上述伪代码清楚地描述了,事件分发从Activity->ViewGroup->View过程中,三大方法之间的调用关系。

二、Activity中的分发机制

上篇文章,我们介绍到,分发机制是从Activity开始的,当触摸屏幕时,Activity先感受到,并调用dispatchTouchEvent()方法进行分发。下面就来具体了解这一方法的内容。

public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        //第一次按下操作时,用户希望能与设备进行交互,可通过实现该方法
        onUserInteraction();
    }

    //获取当前Activity的顶层窗口是PhoneWindow,执行其superDispatchTouchEvent()方法
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //当没有任何view处理时,交由activity的onTouchEvent处理
    return onTouchEvent(ev);
}

可以看到,当Activity向下分发事件,最终没有任何控件进行处理后,将会交给Activity的onTouchEvent()方法处理。
继续看superDispatchTouchEvent()方法

public boolean superDispatchTouchEvent(KeyEvent event) {
    return mDecor.superDispatcTouchEvent(event);
}

PhoneWindow的最顶View是DecorView,再交由DecorView处理。而DecorView的父类的父类是ViewGroup,接着调用 ViewGroup.dispatchTouchEvent()方法。

所以Activity中的分发机制简述为:若不重写该方法,则调用根ViewGroup的dispatchTouchEvent()方法,进行分发,如果事件没有任何控件进行处理,则最后返回给Activity的onTouchEvent()方法进行处理。若重写该方法,不论返回值是false/true,都不会向下进行事件分发,也就是事件停止分发,已经在Activity中消费了。

三、ViewGroup中的分发机制

从Acivity中向下分发,就到达了ViewGroup中的dispatchTouchEvent()方法,下面来具体看看这个方法。
该方法比较复杂,篇幅有限,就截取几个重要的逻辑片段进行介绍,来解析整个分发流程。

         // 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
            //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
            //可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            //默认情况下会进入该方法
            if (!disallowIntercept) {
                //调用拦截方法
                intercepted = onInterceptTouchEvent(ev); 
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
            intercepted = true;
        }

这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE或UP事件了,并且之间的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。其实这并不是onTouchEvent()方法傲娇,而是onInterceptTouchEvent()方法没给他机会,直接拦截了,不给子View机会。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。

当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。

                    /* 从最底层的父视图开始遍历,
                    ** 找寻newTouchTarget,即上面的mFirstTouchTarget
                    ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。
                    */
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = customOrder
                                ? getChildDrawingOrder(childrenCount, i) : i;
                        final View child = (preorderedList == null)
                                ? children[childIndex] : preorderedList.get(childIndex);

                        // 如果当前视图无法获取用户焦点,则跳过本次循环
                        if (childWithAccessibilityFocus != null) {
                            if (childWithAccessibilityFocus != child) {
                                continue;
                            }
                            childWithAccessibilityFocus = null;
                            i = childrenCount - 1;
                        }
                        //如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }

                        newTouchTarget = getTouchTarget(child);
                        // 已经开始接收触摸事件,并退出整个循环。
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        //重置取消或抬起标志位
                        //如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            // 获取TouchDown的时间点
                            mLastTouchDownTime = ev.getDownTime();
                            // 获取TouchDown的Index
                            if (preorderedList != null) {
                                for (int j = 0; j < childrenCount; j++) {
                                    if (children[childIndex] == mChildren[j]) {
                                        mLastTouchDownIndex = j;
                                        break;
                                    }
                                }
                            } else {
                                mLastTouchDownIndex = childIndex;
                            }

                            //获取TouchDown的x,y坐标
                            mLastTouchDownX = ev.getX();
                            mLastTouchDownY = ev.getY();
                            //添加TouchTarget,则mFirstTouchTarget != null。
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //表示以及分发给NewTouchTarget
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }

dispatchTransformedTouchEvent()方法实际就是调用子元素的dispatchTouchEvent()方法。
其中dispatchTransformedTouchEvent()方法的重要逻辑如下:

 if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }

由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。
如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。

//添加TouchTarget,则mFirstTouchTarget != null。
newTouchTarget = addTouchTarget(child, idBitsToAssign);
 //表示以及分发给NewTouchTarget
 alreadyDispatchedToNewTouchTarget = true;

其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值。
如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。
如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件。

所以ViewGroup中的分发机制简述为:若子View或ViewGroup不处理MotionEvent.ACTION_DOWN事件,那么接下来的一些列事件都交由ViewGroup处理。若ViewGroup的onInterceptTouchEvent()执行为true,则接下来的所有事件都默认由该ViewGroup执行。若子View或ViewGroup处理MotionEvent.ACTION_DOWN事件,则接下来的事件处理交给谁要看onInterceptTouchEvent()的返回值,如果返回true,则第一个MOVE事件,会变成CANCEL事件,继续交由原来的子View或ViewGroup处理,接下来的一些列事件都交由ViewGroup执行。如果返回false,则继续交给原来的子View或ViewGroup处理。

四、View中的分发机制

View中的分发机制就比较简单了。上面ViewGroup中已经开始调用View.dispatchTouchEvent()方法,下面来具体看一下。

public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        //在Down事件之前,如果存在滚动操作则停止。不存在则不进行操作
        stopNestedScroll();
    }
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        //第一个条件默认为true
        //第二个条件mOnTouchListener 不为空
       //第三个条件该条件是判断当前点击的控件是否enable 
       //第四个条件onTouch返回值是true
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true; //满足上述四个条件,已经消费事件,则返回True
        }
        //如果OnTouch()返回false,或者没满足其他条件,没有消费Touch事件则调用OnTouchEvent()
        if (!result && onTouchEvent(event)) { 
            result = true; //onTouchEvent(event)返回true,已经消费事件,则返回True
        }
    }

    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }

    // 处理取消或抬起操作
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}

针对上述四个条件的判断,很多View默认是(mViewFlags & ENABLED_MASK) == ENABLED,通过设置OnTouchListener中的onTouch返回true,那么onTouchEvent()方法就不会调用,表明OnTouchListener的优先级高于onTouchEvent。

//手动调用设置
button.setOnTouchListener(new OnTouchListener() {  

  @Override  
  public boolean onTouch(View v, MotionEvent event) {  

      return false;  
  }  
});

如果OnTouchListener中的onTouch返回false,那么会调用onTouchEvent()方法。

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;

    // 当View状态为DISABLED,如果可点击或可长按,则返回True,即消费事件
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    //当View状态为ENABLED,如果可点击或可长按,则返回True,即消费事件;
    //与前面的的结合,可得出结论:只要view是可点击或可长按,则消费该事件.
    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) {
                    boolean focusTaken = false;
                    if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                        focusTaken = requestFocus();
                    }

                    if (prepressed) {
                        setPressed(true, x, y);
                   }

                    if (!mHasPerformedLongPress) {
                        //这是Tap操作,移除长按回调方法
                        removeLongPressCallback();

                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            //调用View.OnClickListener
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (prepressed) {
                        postDelayed(mUnsetPressedState,
                                ViewConfiguration.getPressedStateDuration());
                    } else if (!post(mUnsetPressedState)) {
                        mUnsetPressedState.run();
                    }

                    removeTapCallback();
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mHasPerformedLongPress = false;

                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();
                    //当处于可滚动视图内,则延迟TAP_TIMEOUT,再反馈按压状态,用来判断用户是否想要滚动。默认延时为100ms
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    //当不再滚动视图内,则立刻反馈按压状态
                    setPressed(true, x, y);
                    checkForLongClick(0); //检测是否是长按
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                break;

            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);

                if (!pointInView(x, y, mTouchSlop)) {
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        removeLongPressCallback();
                        setPressed(false);
                    }
                }
                break;
        }

        return true;
    }
    return false;
}

只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。
如果View设置了OnClickLisenter,那么performClick方法内部就会调用onClick方法。

public boolean performClick() {  
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  

    if (mOnClickListener != null) {  
        playSoundEffect(SoundEffectConstants.CLICK);  
        mOnClickListener.onClick(this);  
        return true;  
    }  
    return false;  
}

则表明onTouch()优先级高于onTouchEvent(),并高于onClick()。
所以View中的分发机制简述为:默认情况下,如果View的CLICKABLE和LONG_CLICKABLE有一个为true(默认LONG_CLICKABLE为false,一般可以点击的View中CLICKABLE为true。),则就会消费该事件,如果都为false,则不会消费该事件,dispatchTouchEvent()方法返回false,交由父控件的循环下一个子View进行同样操作。如果重写onTouchEvent()方法,返回false,dispatchTouchEvent()方法返回false,交由父控件的循环下一个子View进行同样操作。如果重写onTouchEvent()方法,返回true,则消费该事件。

以上就是源码解析事件分发的内容。
参考文章:
Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
Android事件分发机制

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 214,444评论 6 496
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,421评论 3 389
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 160,036评论 0 349
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,363评论 1 288
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,460评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,502评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,511评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,280评论 0 270
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,736评论 1 307
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,014评论 2 328
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,190评论 1 342
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,848评论 5 338
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,531评论 3 322
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,159评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,411评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,067评论 2 365
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,078评论 2 352

推荐阅读更多精彩内容