其实事件分发是个看着简单,学起来也不难,讲起来憋死人,用起来需要经验的知识点,我也只能总结一下,需要的时候就需要多琢磨了。
这篇文章写的不错,大家可以看看
https://www.jianshu.com/p/38015afcdb58
事件分发主要包含以下三个方法
//事件分发
public boolean dispatchTouchEvent(MotionEvent ev)
//事件拦截
public boolean onInterceptTouchEvent(MotionEvent ev)
//事件相应
public boolean onTouchEvent(MotionEvent event)
dispatchTouchEvent 事件分发
用来进行事件分发,只要事件到达了当前view就一定会被调用,返回结果受到当前view的onTouchEvent和下级view的dispatchTouchEvent影响。如果返回true就证明消费。
onInterceptTouchEvent 事件拦截
在dispatchTouchEvent种使用,判断事件是否拦截,如果当前view拦截了某个事件,如果不拦截,那么这个事件的一系列事件都不反应。
onTouchEvent 事件处理
处理事件,返回结果表示是否消耗当前事件,如果不消耗,则在同一个事件期内不再接收该事件。
事件?
原谅我无耻的盗了一张图,事件就是按下开始到抬起或者因为异常结束的整个过程。
事件分发的过程
来源android艺术探索如上图伪代码,我们可以理解,当事件下来的时候,事件分发肯定会被触发, 如果想自己处理,自己的onTouchEvent进行处理也就是消耗掉,返回了true,也就相当于该事件不会下传。否则的话事件就会继续下传到子view,如果子view的分发事件就会继续轮回处理,直到事件被最后处理。
简单举个例子,在公司里,CTO老大分活给四个技术小组,只要有一个技术小组里的人把活干了,就相当于这个小组完成了任务,不管是不是组里组长干的还是新来的实习生干的,当然如果组长接到活自己直接给干了,相当于下面的成员就不会响应到该事件,当然如果四个小组都不接这个活,CTO只能自己看着处理了,是改需求还是拒绝,那就是CTO的事了。
onTouch>onTouchEvent>onClick
View不能拦截事件我们继承View发现View没有onInterceptTouchEvent,想下也是,如果view再拦截事件,谁去响应呢?
当最底层的view去处理事件的时候,我们都知道 View是可以设置OnTouchListener,OnClickListener,onTouchEvent,他们的先后级别是OnTouchListener>onTouchEvent>OnClickListener。
从Activity到View
一个点击事件发生的时候,传递顺序是从Activity-->Window-->View,window会将事件给最顶级的View,顶级View再按照事件传递一层层分发下去,当然如果从最底层都返回fasle,那么父容器的onTouch也会被依次调用,最后调用了Activity的onTouch,毕竟所有程序猿都处理不了的问题,就只能CTO处理了。整个事件就好比CTO(Activity)想做一个大需求,找到自己的秘书(wiondow),把需求给了技术部经理(DecorView ),经理对着下面的几个组长(VIewGropu)分配了需求,剩下的事就是组内的事情了。
小总结
1. 事件就是从按下到离开屏幕(抬起或者异常结束)的过程,中间可能包含无数滑动。
2. 正常情况下,一个事件只能被一个view处理,消费,毕竟谁负责的模块谁处理最好,也有例外,比如工期太紧,事件不够了,抓个人帮你写代码,其实也就是利用onTouch强行把事件传给别人。
3.一旦一个View决定处理一个事件,那么该view的在该事件的过程onInterceptTouchEvent不会被该事件再触发,因为一旦开始处理,系统会把该系列事件都交给他处理。也不用去调用onInterceptTouchEvent方法,就好比我们负责某个模块代码,老大也不会天天再吩咐你去负责这个模块。
4. 一个View开始处理事件,如果不消耗ACTION_DOWN,也就是onTouchEvent返回了false,那么该事件所有事件都不会由他处理了,会返回给他父控件的onTouchEvent进行处理,也就是说如果down事件都不处理,那么别的事件也不用处理了,因为开头就把事情办砸了,所有接下来的事情,领导也不敢给他做了,只能领导自己去做。
5. 比如一个View开始处理事件,结果只处理了Down,不消耗其他时间,那么这个点击事件就消失了,父元素的onTouchEvent也不会相应,所有的没处理的消失的事件都会传递给Activity,当然该事件还可以收到后续事件。小程序员成功用一个满代码页面忽悠住了自己组长,CTO却收到了测试汇报说那小伙子根本没干活。
6.ViewGroup默认不拦截任何事件,源码上onInterceptTouchEvent返回的是false。组长一般不会亲自干活,负责分配人物。
7.View 木有 onInterceptTouchEvent,有事件直接被onTouchEvent调用,来活就得干,底层程序猿。
8.View 的onTouchEvent默认为true,会默认消费事件,除非你把他的cliclable和longClickable同时设置为false。View的longClickable默认都为false。通宵可以,但是不能天天通宵干活啊。cliclable看情况,Button就为true,textView默认为false。程序猿基本无法拒绝需求,除非你是老员工,或者你请假或者病倒了。
9. onClick 能发生前提是可以点击,而且收到了 down和Up事件。如果木有需求我怎么写代码,木有Up事件那就是按住了不叫点击。
10. 事件传递总是由外到内,由父元素分发给View,View也可以用requestDisallowInterceptTouchEvent干预事件分发,down事件无法被影响,毕竟需求还是领导先分发,但是组内大牛可以和领导说以后活都给我,你休息。
11.ViewGroup的onTouchEvent事件是什么时候处理的呢?当ViewGroup所有的子View都返回false时,onTouchEvent事件便会执行。由于ViewGroup是继承于View的,它其实也是通过调用View的dispatchTouchEvent方法来执行onTouchEvent事件。
//申请
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
getParent().requestDisallowInterceptTouchEvent(true);
return super.dispatchTouchEvent(ev);
}
Activity 与 Window、PhoneWindow、DecorView
每一个 Activity 都持有一个 Window 对象,但是 Window 是一个抽象类,这里 Android 为 Window 提供了唯一的实现类 PhoneWindow。也就是说 Activity 中的 window 实例就是一个 PhoneWindow 对象。
PhoneWindow 中持有一个 Android 中非常重要的一个 View 对象 DecorView。setContentView可以说是我们最常用的方法。其实就是通过不断的传递,把布局文件对应的资源 id 一直传递到这个 Activity 对应的 decorView 中,decorView 本身是一个 FrameLayout,当 decorView 接受到来自 Activity 传递过来的布局 id 后,通过 inflater(布局填充器),把布局资源 id 转换为一个 View,然后把这个布局 View 添加在自身中。
Activity会将事件传递给window进行分发,如果这里直接返回true,也就没下面啥事了。window再分发给顶级元素View进行分发。
ViewGroup?
我们之前讲到ViewGroup默认会不拦截事件,返回false,当返回true的时候,会按照onTouch>onTouchEvent>onClick 等级进行处理,那么什么时候领导会亲自上战场呢?在ViewGroup的dispatchTouchEvent中
android艺术探索
当事件是down或者mFristTouchTag不为null的时候会拦截事件。down事件我们很好理解,毕竟需求需要领导分配。
mFristTouchTag一开始会空,当事件传给子元素,子元素处理完成的时候mFristTouchTag就不在为null,那么之后的事件move和up也会被我们的判断条件阻挡。其实想想,领导在第一次分发需求的时候,肯定第一时间把活分下去,down就是需求,分发下去有人接活,那么领导就不用干了,结果领导小组里没人了,分下去结果发现没人 还得自己干活进行处理,那么接下来的move和up就被领导接收了。
当然还有一种情况,就是子元素调用了我们前面说的requestDisallowInterceptTouchEvent,那么标示FLAG_DISALLWO_INTERCEPT被标记,就好比需求还没下来,小组新来的大牛说以后有活直接给我就好,那么组长拿到需求,直接给大牛就好,当然down这种发需求的还是到组长手里。在下一个down事件来的时候,FLAG_DISALLWO_INTERCEPT会被重置,毕竟组长也得看大牛有没有继续要活的想法。
在viewGroup不拦截事件的时候,事件就会分发下去,看看子元素能不能接受事件,判断条件2个,第一是不是在动画期间,第二区域是不是在子元素那儿。如果符合要求 事件就分发下去,事件被子元素接受 返回true,我们的mFristTouchTag也就是在这个时候被赋值的。
如果事件下发,所有子元素都返回false,或者直接没有子元素,那么只能viewGropu进行自我处理了,毕竟需求下去,没人能做,或者小组就自己,那么只能自己干活了。
View的努力工作
View身为一线苦逼码农,对所有事件都是积极响应的。事件来临的时候,因为也没下级,只要身体扛得住,电脑抗的住,就是一个字干
android开发艺术探索
看看有木有touch监听,木有就走onTouchEvent,当然不管能不能完成需求,哪怕自己干不了,需求还得看一眼,在ontouchEvent里,down事件怎么都会被消费,只要clickable或者longClickable有一个为true就是返回true,就消费了事件,只要身体或者电脑抗的住,就是干。
比如一个view的clickable和longClickable哪怕设置了为false,只要设置了点击事件或者长按事件,那么longClickable和clickable也会随着被改成true,哪怕病倒了,只要打上吊瓶,就能继续干。
看到这里,我们用程序猿最接地气的方式把事件分发讲完了。任劳任怨的view,苦逼或者幸福组里有大牛的ViewGroup,当CTO的activity,CTO的秘书DecorView。希望能尽可能理解。
冲突???
滑动冲突可以说是比较难处理的,其实我们只要清楚我们的需求,其实还是很好处理的。
1.内外滑动方向不一致
2. 内外方向滑动一致
3. 两者混合
对于第一种情况来说,比如外部需要上下滑动,内部需要左右滑动,那么我们只要让外部拦截,当滑动是上下的时候将事件拦截,左右的时候传递给内部,其实判断上下滑动还是左右滑动,我们计算下滑动开始的到滑动时候的俩点坐标就可以计算出来。可以判断夹角,可以判断距离,比如y轴滑动距离更大那就是上下滑动。
第二种情况和第三种情况,其实一般不好处理,我们只能根据需求去处理。比如在特定区域该怎么滑动或者特殊类型怎么处理。
针对第一种,可以采用外部拦截法和内部拦截法。
外部拦截法其实比较简单,我们可以看下艺术探索给出的伪代码
重写父元素
外部拦截法
以上需要注意的点就是onInterceptTouchEvent里父元素不能拦截Down事件,因为一旦拦截后续move和up就会也有父元素处理。move事件根据需求进行处理,up这里没必要拦截,直接返回false。我们之前讲过 点击事件的其实就是按下抬起,如果我们消费了up,那么可能导致子元素的点击事件无效。作为父元素就算返回了false,只要开始消费了down事件,后续事件也会收到。
这就好比组长拿到需求,看看是谁负责的,就分给下面,该自己上战场的就自己处理了。
内部拦截法
重写子元素
内部拦截法
内部拦截法相对而言复杂很多,需要将所有事件都放给子元素,子元素调用requestDisallowInterceptTouchEvent,父元素就去拦截除了down之外的所有事件,因为down不受FLAG_DISALLWO_INTERCEPT影响,一旦拦截,子元素就啥也拿不到了。
其实这就好像我们组里有大牛,所有活都是大牛干,大牛事件不够了,再丢给组长干,组长不能拦截需求,因为拦截了需求,大牛就不知道干啥了。
父元素
我们遇到第二三种情况也只能尽可能将事情拆分成单一的状况,因为也没可能需求要求一个很小的区域内既能上下也能左右,那就成了位移了,不是么?
android开发艺术探索,可以说是很好的一本入门加进阶的android书籍,希望有时间大家多看看,这篇就先写到这里。
网友评论