二、View 事件分发机制

本文是自己看过一些资料后的总结,如要详细了解事件分发机制,请看「参考」内的文章。

一、事件分发基础认知

1.1 当我们在谈论事件分发时,到底再谈论什么?

  • 当用户触摸屏幕时,会产生点击事件 (Touch 事件)
    而 Touch 事件的相关细节(发生触摸的位置、时间等)都被封装成了 MotionEvent 对象
    所以当我们讨论事件分发时,实际是在讨论,是谁来处理这个 MotionEvent 对象,这个 MotionEvent 会不断传递。

1.2 那么 MotionEvent 会在哪些对象之间传递呢?

  • Activity、ViewGroup、View
    牢记: Android 事件分发的流程是 Activity -> ViewGroup -> View
    即:1个点击事件发生后,事件会先传递到 Activity, 再传到 ViewGroup,最终再传到 View。
    所以当我们分析时,会首先从 Activity 开始分析

1.3 事件分发过程中都会经历哪些方法来进行 MotionEvent 的传递?

  • dispatchTouchEvent()、onInterceptTouchEvent()、onTouchEvent()
  • dispatchTouchEvent:用于分发点击事件,当点击事件传递给当前 View 时,就会调用该方法。
  • onInterceptTouchEvent:拦截某个事件,只有 ViewGroup 存在这个方法。在 ViewGroup 的 dispatchTouchEvent 内部调用。
  • onTouchEvent:用于处理点击事件,在 dispatchTouchEvent 内部调用。

二、事件分发机制

2.1 Activity 事件分发机制

  • 当一个点击事件开始时,会进行如下传递 Activity.dispatchTouchEvent() -> PhoneWindow.superDispatchTouchEvent() -> DecorView.superDispatchTouchEvent() -> ViewGroup.dispatchTouchEvent()
  • 我们可以看到在这个过程中,事件传到了 ViewGroup,到此 Activity 的分发过程基本结束了。其实在 DecorView 调用的时候,我们就可以结束 Activity 的传递了,根据源码,此时 DecorView 的 superDispatchTouchEvent 会调用 ViewGroup 的 dispatchTouchEvent。
  • 当 DecorView 的 superDispatchTouchEvent 返回true 的话,则说明有子 View 消费了事件,则 Activity.dispatchTouchEvent 会返回 true,事件分发结束。如果返回 false 的话,会调用 Activity 的 onTouchEvent 方法来消费事件,这个时候无论onTouchEvent 返回什么,事件分发都结束了。

2.2 ViewGroup 事件分发机制

  • ViewGroup 每次事件分发,都会调用 onInterceptTouchEvent 询问是否拦截事件。
  • 如果 ViewGroup.onInterceptTouchEvent 返回 true 代表拦截此事件。返回 true 的情况有两种(一、自己手动重写返回 true。 二、无 View接收事件,即点击空白处时)。
  • 当 ViewGroup 拦截事件时,会调用 ViewGroup 父类的 dispatchTouchEvent 即 View.dispatchTouchEvent。 然后会自己处理该事件,调用自身的 onTouch -> onTouchEvent ->performClick -> onClick,这是 View 的调用过程,具体看 View 的事件分发。
  • 当 ViewGroup 不拦截事件时,会循环子View,找到被点击的相应子 View 控件,然后调用子 View 控件的 dispatchTouchEvent,这个时候也就实现了事件从 ViewGroup 到 View 的传递。
2.2.1 ViewGroup 怎么判断哪个子 View 被点击了

循环子 View 中,有这么一段代码,用来判断当前 View 是否被点击了

     //child可接受触摸事件:是指child是可见的(VISIBLE);或者虽然不可见,但是位于动画状态。
    if (!canViewReceivePointerEvents(child)
            || !isTransformedTouchPointInView(x, y, child, null)) {
                 ev.setTargetAccessibilityFocus(false);
                 continue;
       }

这里面有两个方法用来判断, canViewReceivePointerEventsisTransformedTouchPointInView
其中canViewReceivePointerEvents判断当前 View 是否能接收到 pointer events ,如果不能接收到,那就直接 continue 循环下一个了。

如果上面判断是可以接受触摸事件的,那么就会去判断触摸坐标(x,y)是否在 child 的可视范围之内。
接下来具体看看 isTransformedTouchPointInView

    protected boolean isTransformedTouchPointInView(float x, float y, View child,
            PointF outLocalPoint) {
        // 首先 new 一个 float 数组,用来存放点击的 x、y 坐标
        final float[] point = getTempPoint();
        point[0] = x;
        point[1] = y;
        transformPointToViewLocal(point, child);
        //这是用来判断点击是否在 View 内的具体方法。
        final boolean isInView = child.pointInView(point[0], point[1]);
        if (isInView && outLocalPoint != null) {
            outLocalPoint.set(point[0], point[1]);
        }
        return isInView;
    }
    final boolean pointInView(float localX, float localY) {
        return localX >= 0 && localX < (mRight - mLeft)
                && localY >= 0 && localY < (mBottom - mTop);
    }

通过这个方法可以看到,View 是怎样判断的。

  • localX、localY : 是通过 ev.getX 和 ev.getY 拿到的,在 View 基础篇有讲过,这两个方法拿到的是相对当前 View 的坐标。
  • mRight、mLeft、mBottom、mTop :View 的四个顶点,具体可复习 View 基础篇

2.3 View 事件分发机制

  • 当事件传递到 View 时,会调用 View.dispatchTouchEvent,在这个里面会首先判断 View.onTouch() 所返回的值。注意是 onTouch 方法,并不是我们三大主要方法 onTouchEvent。
  • View.onTouch 返回 true, 则事件被消费,不会再往下传递,即会调用如下代码块,不会调用 onClick 了。
btn.setOnTouchListener(new OnTouchListener) {
    @Override
    public boolean onTouch(View v, MotionEvent evnet) {
    // 若在onTouch()返回true,从而使得View.dispatchTouchEvent()直接返回true,事件分发结束
    // 若在onTouch()返回false,从而使得View.dispatchTouchEvent()中跳出If,执行onTouchEvent(event)
        return true;
    }
}
  • View.onTouch 返回 false,则会去调用自身的 onTouchEvent(),这个方法里会具体判断当前是什么事件,从而做出相应操作,比如当前是 MotionEvent.ACTION_UP 时会调用 performClick(),这个方法里就会消费我们经常写的 setOnClickListener 里的 onClick 方法了,然后返回 true。
  • View 的 onTouchEvent 是事件传递的最后一个地方了,如果该 View 是可点击的,则一定会返回 true,此时事件分发结束。 如果不可点击,会返回 false,此时事件就会回传到 ViewGroup 的dispatchTouchEvent,然后会自己处理该事件,调用自身的 onTouch -> onTouchEvent ->performClick -> onClick。 如果 ViewGroup 也返回 false,则会回传到 Activity 的dispatchTouchEvent,去执行 Activity 的 onTouchEvent 方法来消费事件。如此就完成了事件的分发传递。
  • 最后根据上述分析,可得知 OnTouchListener 优先于 onClickListener, 即 onTouch() 的执行优先于 onClick()。
  • 最后的最后:若1个控件不可点击(即非enable),那么给它注册onTouch事件将永远得不到执行,具体原因看如下代码
    // && 为短路与,即如果前面条件为 false,将不再往下执行
    // 所以 onTouch() 能够执行需2个前提条件:
    //  1. mOnTouchListener 的值不能为空
    //  2. 当前控件必须是 enable 的。
    if (mOnTouchListener != null
          && (mViewFlags & ENABLED_MASK) == ENABLED
               && mOnTouchListener.onTouch(this, event)) 
     {
           result = true;
     }
    // 对于该类 非 enable 控件,若需监听它的 touch 事件,就必须通过在该控件中重写 onTouchEvent()来实现

参考

https://blog.csdn.net/carson_ho/article/details/54136311
http://www.gcssloop.com/customview/dispatch-touchevent-source
http://gityuan.com/2015/09/19/android-touch/

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