注:原文来自掘金作者xiangcman

写这篇文章其实是有原因的,说实话这次面试真的很失败,看着身边的人都拿到了高薪的工资,感觉自己还是有些惭愧。也更说明自己在很多方面的知识点还是不够扎实,于是再一次拿起了view的事件分发的源码给看了一遍。

面试官:说说view中的事件分发?

android中事件分发机制是android中常见的问题,一般大家都知道view的分发事件是从view的Viewgroup(Parent)#dispatchTouchEvent到Viewgroup(Parent)#onInterceptTouchEvent再到View#dispatchTouchEvent,然后到view的onTouchEvent,最后又回到了Viewgroup(Parent)#onTouchEvent。如果大家记不住方法名,可以直接说先是parent的分发到拦截再到view的分发,再到view的消费,最后到parent的消费

viewgroup分发

这样回答肯定是很浅显的,因为没有说出是否拦截、是否分发、是否消费的各种条件,没有涉及到各种action的分发情况,上面说的默认分发只是针对action_down的,因为view/viewgroup各种super调用都是不进行分发、拦截、消费的,所以在没找到处理touch事件的view时候,是一直往上层view传递的,一直传到activity里面,下面我们再来整理一下:

如果viewgroup不进行分发,那么action_down、action_move和action_up只会执行到viewgroup的dispatchTouchEvent,不分发的条件是dispatchTouchEvent直接返回true或false,true和false的区别是true会执行action_down、action_move和action_up,而如果直接返回false只会执行到action_down。并且后续的viewgroup的onInterceptTouchEvent后续方法都不会被执行到。

关于为什么view/Viewgroup的dispatchTouchEvent返回true的时候三个action都能执行到,而返回false的话,只能执行到action_down,这个需要到view/Viewgroup的父类中dispatchTouchEvent找答案,该方法中会在action_down的时候调用dispatchTransformedTouchEvent方法,而该方法是通过子view的dispatchTouchEvent方法的返回值来决定父类的dispatchTransformedTouchEvent方法的返回值,而dispatchTransformedTouchEvent的返回值会决定mFirstTouchTarget是否为空,所以在action_down的过程中实际中通过子view的dispatchTouchEvent方法返回值来确定mFirstTouchTarget是否为空。这里贴出viewgroupdispatchTransformedTouchEvent方法的删减代码:

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,        View child, int desiredPointerIdBits) {    ------------------    //省略了cancel部分的代码    ------------------------    //如果child为空,直接调用自己的dispatchTouchEvent方法,此时自己就相当于一个view,touch事件走自己的    if (child == null) {        handled = super.dispatchTouchEvent(transformedEvent);    } else {        final float offsetX = mScrollX - child.mLeft;        final float offsetY = mScrollY - child.mTop;        transformedEvent.offsetLocation(offsetX, offsetY);        if (! child.hasIdentityMatrix()) {            transformedEvent.transform(child.getInverseMatrix());        }        //返回值直接通过孩子来获取返回值        handled = child.dispatchTouchEvent(transformedEvent);    }    transformedEvent.recycle();    return handled;}

所以如果view/viewgroup的dispatchTouchEvent方法返回false,表示在action_down的时候,父类的dispatchTransformedTouchEvent方法返回false;如果返回true会调用addTouchTarget方法,给mFirstTouchTarget设置值:

private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);    target.next = mFirstTouchTarget;    mFirstTouchTarget = target;    return target;}

紧接着在在后面又会调用了:

这句只有在view/viewgroup的dispatchTouchEvent返回false的时候,才会走这里,所以后面的action_move和action_up都会走这里,而此时传入的child=null,从上面代码可以看到,直接调用了父类的dispatchTouchEvent方法。所以从这里不难看出在view/viewgroup的dispatchTouchEvent返回false的时候直接调用了父类的dispatchTouchEvent方法,因此只有action_down事件。

面试官:如果我只想有view的拖拽事件,而不想要view的点击事件,让你重写这个view的拖拽怎么设计

其实这道题考察大家对view的dispatchTouchEvent和view的onTouchEvent事件的处理流程,上面已经分析了想要view能执行到view的touch事件,那么必须要求view的dispatchTouchEvent返回true,而dispatchTouchEvent返回true要么是dispatchTouchEvent直接返回true或者view的onTouchEvent返回true。如果从效率上看,直接将dispatchTouchEvent返回true就ok,而不需要再去关心onTouchEvent方法。

viewgroup拦截

关于拦截无非就是拦截或不拦截,而拦截的条件是返回true,不拦截是返回false或返回super.onInterceptTouchEvent,默认的super是返回false的,因此可以用super表示不拦截

viewgroup拦截实际是通过在dispatchTouchEvent方法中,设置intercepted变量,如果在拦截方法里面返回true,那么intercepted为true,如果为true则在action_down的时候mFirstTouchTarget=null,那么此时是直接调用dispatchTransformedTouchEvent传入的child=null,因此将事件交给了super.dispatchTouchEvent,此时把它当成一个view来处理了。

面试官:有个viewgroup,里面有个view,如果view在dispatchTouchView中不分发事件,并且只在action_move中拦截touch事件向下分发,说说viewgroup到view的各个action是如何分发的?

分析:

先贴出事例代码:

testView在testViewgroup里面,testViewgroup在action_move的时候拦截(onInterceptTouchEvent在move返回true),testView不进行分发(dispatchTouchEvent返回true) 咱们通过log来看结果:

这里执行到TestViewgroup#dispatchTouchEvent的action_move之后就执行了TestView#dispatchTouchEvent的action_cancel,然后后面执行TestViewgroup#dispatchTouchEvent和TestViewgroup#onTouchEvent的action_move和action_up。 从前面viewgroup的dispatchTouchEvent分析知道,如果viewgroup在action_down中发现有子view的dispatchTouchEvent返回true,则mFirstTouchTarget不为空,紧接着在action_move的时候进行了拦截,则intercepted=true,既然在move过程中确定了intercepted=true,mFirstTouchTarget不为空,则可以看viewgroup.dispatchTouchEvent部分代码:

.TouchTarget predecessor = null;TouchTarget target = mFirstTouchTarget;while (target != null) {    final TouchTarget next = target.next;    //alreadyDispatchedToNewTouchTarget=false    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {        handled = true;    } else {        //由于move过程中intercepted=true,则cancelChild=true        final boolean cancelChild = resetCancelNextUpFlag(target.child)                || intercepted;        //看到了没这里就是触发child的dispatchTouchEvent的action_cancel        if (dispatchTransformedTouchEvent(ev, cancelChild,                target.child, target.pointerIdBits)) {            handled = true;        }        if (cancelChild) {            if (predecessor == null) {                //由于next=null,因此mFirstTouchTarget=null,所以在action_move刚进来的时候mFirstTouchTarget=null了,                //待会我们通过反射看下该变量                mFirstTouchTarget = next;            } else {                predecessor.next = next;            }            target.recycle();            target = next;            continue;        }    }    predecessor = target;    target = next;}

上面也说明了在action_move进来的时候先是触发了testView#dispatchTouchEvent的action_cancel,紧接着mFirstTouchTarget=null了,由于mFirstTouchTarget是viewgroup类中私有的变量,我们可以通过反射调用该变量看下是否为空:

//反射代码,关于反射可以看我之前的文章java反射整理

接着在testViewgroup#dispatchTouchEvent中获取mFirstTouchTarget属性:

通过上面可以验证刚才move过程中mFirstTouchTarget为空的判断,日志如下:

看到了没,第一次move的时候mFirstTouchTarget还不是null,第二次move的时候就是null了,因此在后续的move和up过程中,只会此处:

看到了没,这里传进去的child=null,根据上面分析可知,当为null的时候,只会触发super.dispatchTouchEvent,所以到了第二次的move之后,只能看到TestViewgroup的action_move和action_up了。

所以针对上面面试官提的问题,大家知道怎么说了吧,还是针对该问题做个小结:

小结

先是down事件会经过viewgroup的dispatchTouchEvent,再到viewgroup的onInterceptTouchEvent,最后到view的dispatchTouchEvent,此时mFirstTouchTarget不为空,紧接着到了move首先到viewgroup的dispatchTouchEvent,再到viewgroup的onInterceptTouchEvent,由于在move过程中拦截了,紧接着走view的dispatchTouchEvent的action_cancel,此时接着把mFirstTouchTarget至为null,因此后续的move和up事件只会走viewgroup的dispatchTouchEvent和onTouchEvent。 画出一张图来给大家看下:

好了,关于这个问题告一段落了,如果分析有问题,大家可以提出疑问。

面试官:里面的view在onTouchEvent中消费,然后拖动手指,直到手指从其他他viewgroup上挪开手指。

其实这个问题在上面分析中已经分析过了,testview的onTouchEvent中消费,所以在action_down中mFirstTouchTarget不为空,因此在action_move和action_up中mFirstTouchTarget还是不为空,所以不管手指是否已经离开了testview,action_move和action_up还是会走testview的dispatchTouchEvent和onTouchEvent。

小结

首先确定action_down过程中mFirstTouchTarget是否为空,如果不为空,所以不管手指是否已经不在testView上了,action_move和action_up还是会在testView的onTouchEvent上进行消费的。

面试官:view的onTouch和onTouchEvent事件的区别

这个问题就没涉及viewgroup到view的事件传递,onTouch指setOnTouchListener的回调方法,它是优先于onTouchEvent事件的,大家可以看下view的dispatchTouchEvent中有如下代码:

我想这个地方不用多说吧,如果onTouch方法返回true,是不会触发onTouchEvent事件的,所以在开篇第二个问题:如果想屏蔽掉view的点击事件,只想要view的拖拽事件,该怎么处理,其实这里完全可以重写setOnTouchListener的onTouch方法,并且onTouch里面返回true就会屏蔽掉onClickListener事件。

面试官:view的onClick事件在什么时候触发的,和onTouch有什么区别

onClick事件是在onTouchEvent消费事件中的action_up触发的,onTouch是在dispatchTouchEvent中触发的,所以onTouch要先于onClick事件,我们也可以通过onTouch返回true来屏蔽掉onClick事件。

好了,关于这次我面试中遇到的事件分发主要是上面这几个问题,大家有什么其他的问题,可以在评论区互动。

技术总结

其实android事件分发核心是在viewgroup的dispatchTouchEvent的action_down过程中找到mFirstTouchTarget是否为空,通过反序遍历子view的dispatchTouchEvent的方法,如果发现有一个子view的dispatchTouchEvent方法返回true,那么mFirstTouchTarget就不为空,否则为空。如果mFirstTouchTarget不为空,那么action_move和action_up才会往下传递,如果在action_move和action_up过程中有viewgroup拦截了事件,则此时先向子view的dispatchTouchEvent传递一个action_cancel,并且将mFirstTouchTarget至为null,所以此时action_move和action_up只会走viewgroup的dispatchTouchEvent和onTouchEvent;如果mFirstTouchTarget在action_down过程中就已经null的话,则从action_down一直向上层view传递,不会有后续的action_move和action_up了。

文末

不管怎么样,不论是什么样的大小面试,要想不被面试官虐的不要不要的,只有刷爆面试题题做好全面的准备,当然除了这个还需要在平时把自己的基础打扎实,这样不论面试官怎么样一个知识点里往死里凿,你也能应付如流啊~

小编将自己6年以来的面试经验和学习笔记都整理成了一个937页的PDF,以及我学习进阶过程中看过的一些优质视频教程。如有需要,关注我后私信【面试】即可无偿分享

其实看到身边很多朋友抱怨自己的工资很低,包括笔者也是一样的,其原因是在面试过程中没有给面试官一个很好的答案。所以笔者会持续更新面试过程中遇到的问题,也希望大家和笔者一起进步,一起学习。

.net 怎么在控制器action中返回一个试图_一个view事件分发,面试官6连问直击灵魂,我被虐的体无完肤...相关推荐

  1. 一个view事件分发,面试官6连问直击灵魂,我被虐的体无完肤

    关于拦截无非就是拦截或不拦截,而拦截的条件是返回true,不拦截是返回false或返回super.onInterceptTouchEvent,默认的super是返回false的,因此可以用super表 ...

  2. java struts2 ajax_在struts2的Action中返回Ajax数据

    author:z_xiaofei168 如何在struts2的action中返回数据(普通字符串.图片)给ajax核心中的XMLHttpRequest对象. 今天下午做项目,就是用户注册是时候,登录名 ...

  3. js获取action中返回的值

    因为老是忘记,所以记录下, var hostname = '${hostname}'; hostname= eval("("+hostname+")"); va ...

  4. 今天来谈谈面试官最喜欢问JS中的闭包问题吧

    今天来谈谈面试官最喜欢问JS中的闭包问题吧 1.什么是闭包? 闭包是指有权访问另外一个函数作用域中的变量的函数,闭包就是函数的局部变量集合,只是这些局部变量在函数返回后会继续存在.闭包就是函数的&qu ...

  5. 靠一个HashMap的讲解打动了头条面试官,我的秘诀是

    最近收集了一份github标星81.6k的Java面试突击手册,文末查看 关注 转发+转发+转发 私信回复关键词 [学习]即可获取~ 预备知识 位运算知识 位运算操作是由处理器支持的底层操作,底层硬件 ...

  6. 十大面试问题解惑,秒杀一切HR、技术面试。程序员必读! 最能体现求职者能力的就是面试,能不能拿到Offer,取决于你面试时的表现,只有有准备才能在面试过程中游刃有余。小编收集了10个面试官最爱提的问题

    十大面试问题解惑,秒杀一切HR.技术面试.程序员必读! 最能体现求职者能力的就是面试,能不能拿到Offer,取决于你面试时的表现,只有有准备才能在面试过程中游刃有余.小编收集了10个面试官最爱提的问题 ...

  7. asin c语言中 返回值范围_大学C语言考试易错知识点总结

    作者:cggwz 来源:https://blog.csdn.net/cggwz/article/details/103740713?utm_medium=distribute.pc_relevant. ...

  8. 触发一个断点_一个补丁引发的RCE: 对CVE20191208的深入分析

    前言 CVE-2019-1208是趋势科技的@elli0tn0phacker在今年6月发现的一个vbscript漏洞,报告中提到这个漏洞是通过补丁比对发现的,这引起了笔者的兴趣.最近,笔者花了一些时间 ...

  9. https开头的网址是什么意思_我想打这个面试官,他给我挖坑,问我:URI中的 “//” 有什么用?...

    我们在浏览网页的时候,需要在浏览器中输入http://或者https://开头的URL地址,类似http://honeypps.com,那么这里的"://"或者"//&q ...

最新文章

  1. 选中条目android spinner的使用
  2. PuTTY 命令行改进 有效解决 中文乱码
  3. spark的python开发安装方式_python 安装spark_Spark环境搭建 (Python)
  4. linux vim ctags,Linux环境上代码阅读与编写的利器-vim+ctags+cscope
  5. centos配置ssh免密码登录后仍要输入密码的解决方法
  6. 模块化开发之sea.js实现原理总结
  7. 人工智能大火能否烧开智能家居这锅水?
  8. 使用C#创建ActiveX控件(译文)
  9. 算法知识点——(5)集成算法—GBDT详解
  10. python网络编程相关
  11. lammps教程:EAM势参数设置详解
  12. 系统明文密码加密传输
  13. css3动画2D|3D
  14. 云原生和云计算的区别介绍
  15. 多媒体计算机系统中的媒体
  16. Python 读取.msg文件中的附件和内容
  17. python电脑推荐_kk视频app下载安装|腾讯视频app下载_电脑知识学习网
  18. 项目实战!Python分析广州房地产市场,房价还会再涨吗?
  19. 百度笔经面经(Java)
  20. 知道创宇研发技能表v3.1

热门文章

  1. 如何提升springboot服务吞吐量
  2. Request method 'GET' not supported解决方式
  3. java基础---System类
  4. Purpose of cmove instruction in x86 assembly? | cmove 指令如何避免错误的分支预测带来的开销?
  5. 【Servlet】HTTP 协议之请求方式、Servlet介绍、Servlet 的生命周期
  6. vb 如何给静态变量赋初值
  7. PAT1046 划拳 (15 分)
  8. Spring的WebClient基本使用
  9. 如何解决分布式系统中的“幽灵复现”?-转载自 阿里技术 微信公众号
  10. MyBatis的association示例