Moosphan / Android-Daily-Interview

:pushpin:每工作日更新一道 Android 面试题,小聚成河,大聚成江,共勉之~
5.47k stars 776 forks source link

2019-04-22:谈谈Android的事件分发机制? #35

Open Moosphan opened 5 years ago

SuDreamer commented 5 years ago

Android:你摸我,我就给你发。你摸我上面上个Activity,我就先传递到我的中间Viewgroup,然后它再到最下面,最私密的地方View,然后就像小溪流水一样,分给这边,分给那边。

429329513wanting commented 5 years ago

Android:你摸我,我就给你发。你摸我上面上个Activity,我就先传递到我的中间Viewgroup,然后它再到最下面,最私密的地方View,然后就像小溪流水一样,分给这边,分给那边。

老司机,我要下车

zwonb commented 5 years ago

当点击的时候,会先调用顶级viewgroup的dispatchTouchEvent,如果顶级的viewgroup拦截了此事件(onInterceptTouchEvent返回true),则此事件序列由顶级viewgroup处理。如果顶级viewgroup设置setOnTouchListener,则会回调接口中的onTouch,此时顶级的viewgroup中的onTouchEvent不再回调,如果不设置setOnTouchListener则onTouchEvent会回调。如果顶级viewgroup设置setOnClickListener,则会回调接口中的onClick。如果顶级viewgroup不拦截事件,事件就会向下传递给他的子view,然后子view就会调用它的dispatchTouchEvent方法。

ADrunkenLiBai commented 5 years ago

Android:你摸我,我就给你发。你摸我上面上个Activity,我就先传递到我的中间Viewgroup,然后它再到最下面,最私密的地方View,然后就像小溪流水一样,分给这边,分给那边。

老司机,我要下车

open car

StefanShan commented 5 years ago

1.触发过程:Activity->Window->DocerView->ViewGroup->View,View不触发再返回由父级处理依次向上推。 2.在每个阶段都要经过三个方法 dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截)、onTouch(处理)

大体流程: Activity中走了Window 的 dispatch,Window 的 dispatch 方法直接走了 DocerView 的 dispatch 方法,DocerView 又直接分发给了 ViewGroup,ViewGroup 中走的是 onInterce 判断是否拦截,拦截的话会走 onTouch 来处理,不拦截则继续下发给 View。到 View 这里已经是最底层了,View 若继续不处理,那就调用上层的 onTouch 处理,上层不处理继续往上推。

推荐一篇介绍非常详细的博客:https://www.jianshu.com/p/38015afcdb58

MoJieBlog commented 5 years ago

给大家举个形象的例子吧。ViewGroup相当于老板或者部门管理。View相当于员工。onTouchEvent相当于接业务。dispatechTouchEvent相当于处理任务。onIterceptTouchEvent相当于拦截任务。 先来分配角色

现在模拟来业务了(点击事件) 老板(ViewGroup)接到业务(onTouchEvent),他有两个选择

futureyang commented 5 years ago

责任链模式

18361237136 commented 5 years ago
  1. 会经过Activity->ViewGroup->view,一次往下传递事件,如果一直不拦截再回调回来。
  2. 主要经过三个方法,dispatchTouchEvent(分发事件),oninterceptTouchEvent(是否拦截View中不存在),onTouchEvent(处理)。
  3. 三个方法的用法是,先用dispatchTouchEvent来分发事件,然后用oninterceptTouchEvent来判断是否拦截该任务(此方法在dispatchTouchEvent内部),如果不拦截直接dispatch向下回调,如果拦截就调用自己的onTouchEvent来处理事件。
  4. 如果由setOnClickListener方法会先执行onClick.
qianxin2016 commented 5 years ago

事件来源:input -> window -> ViewRoot -> DecorView -> Activity -> ... 事件处理:责任链模式,先从顶层传到底层,再从底层传回顶层,如果中间被消费或拦截,则流程结束 从Activity.dispatchTouchEvent()开始 --向下--> ViewGroup.dispatchTouchEvent(),可通过onInterceptTouchEvent()拦截 --向下--> View.dispatchTouchEvent() --向下--> View.onTouchEvent() --向上--> ViewGroup.onTouchEvent() --向上--> Activity.onTouchEvent() 注意点:如果View没有消费ACTION_DOWN,后面的其他事件就不会再传过来

Techvisionbest commented 5 years ago

1.触发过程:Activity->Window->DocerView->ViewGroup->View,View不触发再返回由父级处理依次向上推。 2.在每个阶段都要经过三个方法 dispatchTouchEvent(分发)、onInterceptTouchEvent(拦截)、onTouch(处理)

大体流程: Activity中走了Window 的 dispatch,Window 的 dispatch 方法直接走了 DocerView 的 dispatch 方法,DocerView 又直接分发给了 ViewGroup,ViewGroup 中走的是 onInterce 判断是否拦截,拦截的话会走 onTouch 来处理,不拦截则继续下发给 View。到 View 这里已经是最底层了,View 若继续不处理,那就调用上层的 onTouch 处理,上层不处理继续往上推。

推荐一篇介绍非常详细的博客:https://www.jianshu.com/p/38015afcdb58

View是没有onInterceptTouchEvent(拦截)的方法的

maoqitian commented 5 years ago

之前写过一篇文章回顾时间分发机制,希望能有补充。 深入理解Android事件分发机制

chenqi5256969 commented 5 years ago

1.当我们按下手指,ViewGroup的onInterceptTouchEvent会最先处理down事件。 假设1:onInterceptTouchEvent返回false,子view的onTouchEvent返回true,后续的move和up事件都将传到onInterceptTouchEvent中,然后一层一层的传递下去交给子view处理 假设2:onInterceptTouchEvent返回false,子view的onTouchEvent返回的是false,则后续的move和up事件都将交给ViewGroup的onTouchEvent处理,如果ViewGroup的onTouchEvent返回为true,ViewGroup将会自己处理,如果ViewGroup的onTouchEvent返回false,则会继续向上传递,交给ViewGroup的父亲来处理。 假设3:onInterceptTouchEvent返回true,那么down和后续的move和up事件将会交给ViewGroup的onTouchEvent处理

devzhan commented 4 years ago

当顶级的ViewGroup接收到事件消息之后,会调用dispatchTouchEvent 方法进行分发事件, 在分发时会先去判断该事件是否拦截onInterceptTouchEvent。如拦截,则事件不继续进行 分发,自己进行消费。如不拦截,事件继续进行向下传递到子控件,如果子控件是ViewGroup 则继续走上述操作,如果是View则会走onTouchEvent方法。 其中ViewGroup不拦截之后会先走onTouch方法,该方法会影响其onTouchEvent。整体传递是一个责任链模式

qweenhool commented 3 years ago

事件分发的细节真的非常多,用老板员工的例子,最早开始看的时候我是没记住过。如果你只是看别人得出来的结论,那么十个人他能总结出十种自己的结论,看似好像明白了,过一段时间自己去看还是会忘记,所以我觉得想要真的明白就得自己去跟踪源码,然后用关键代码提炼成伪代码,打log看结果,做好笔记。由于你没法获得自己手机的系统源码,所以最好用模拟器来调试,用模拟器对应的sdk版本编译运行代码,自定义几个简单的View和ViewGroup,从ViewGroup的dispatchTouchEvent()方法处开始打断点。这里分享一个小技巧,如果你想要程序在执行到你自定义ViewGroup的dispatchTouchEvent()处暂停,可以在断点的condition处加上this.getClass().getSimpleName().equals("CustomViewGroup1");这样的条件语句。后续总结完毕,我会发文章出来给大家参考的,大家有问题可以互相交流,共同进步。

mlinqirong commented 2 years ago

事件分发机制是责任链模式,先从顶层传到底层,再从底层传回顶层,如果中间被消费或拦截,则流程结束: Activity dispatchTouchEvent- >ViewGroup dispatchTouchEvent ->ViewGroup onInterceptTouchEventview->View dispatchTouchEvent -> ->View onTouchEvent-> ViewGroup onTouchEvent->Activity onTouchEvent 事件监听分别有 dispatchTouchEvent(分发)onInterceptTouchEvent(拦截)onTouchEvent(响应)事件 Activity和view只有dispatchTouchEvent(分发)onTouchEvent(响应)事件 因为View没有子View,所以不需要拦截事件。而ViewGroup里面可以包裹子View,所以通过onInterceptTouchEvent方法 在dispatchTouchEvent中调用super.dispatchTouchEvent有底层执行底层dispatchTouchEvent 没有底层执行本层的onTouchEvent响应事件 dispatchTouchEvent返回true则表示响应事件不上发 返回false继续上发执行顶层onTouchEvent

yline commented 1 year ago

① 驱动层 -> Framework 层 : 用户触摸 , 或按键 后 , 事件在硬件中产生 , 从 硬件驱动层 , 传递到 Framework 层 ;

② WMS -> View 层 : WindowManagerService ( 简称 WMS ) 将事件传递到 View 层 ;

③ View 层内部 : 事件在 View 的容器及下层容器 / 组件 之间传递 ;

https://cloud.tencent.com/developer/article/1154110

flyisme commented 3 months ago

伪代码: 单指操作下的:

// ViewGroup 的 dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;

    // DOWN 事件重置触摸状态
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        mFirstTouchTarget = null; 
    }

    boolean intercept = onInterceptTouchEvent(ev); // 拦截事件

    if (!intercept) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 查找 mFirstTouchTarget
            for (View child : childs) {
                if (mFirstTouchTarget!=null&&child.pointInView(ev.getX(), ev.getY()) && child.dispatchTouchEvent(ev)) {
                    // 初始化 mFirstTouchTarget 链表,并添加 child
                    mFirstTouchTarget = new TouchTarget(child);
                    break;
                }
            }
        }
    }

    if (intercept || mFirstTouchTarget == null) {
        consume = super.dispatchTouchEvent(ev); // 最终调用View.dispatchTouchEvent
    } else {
        consume = mFirstTouchTarget.child.dispatchTouchEvent(ev); // 分发给 mFirstTouchTarget
    }

    return consume;
}

// View 的 dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    boolean consume = false;

    if (this.mOnTouchListener != null) {
        consume = mOnTouchListener.onTouch(this, ev);
    } 

    if (!consume) {
        consume = onTouchEvent(ev);
    }

    return consume;
}