Android事件的分发和消费机制

事件的分发和消费机制

一、简介:

Activity或View类的onTouchEvent()回调函数会接收到touch事件。

一个完整的手势是从ACTION_DOWN开始,到ACTION_UP结束。

简单的情况下,我们只需要在onTouchEvent()中写个switch case语句,处理各种事件(Touch Down、Touch Move、Touch Up等),但是比较复杂的动作就需要更多的处理了。

ViewGroup作为一个parent是可以截获传向它的child的touch事件的。

如果一个ViewGroup的onInterceptTouchEvent()方法返回true,说明Touch事件被截获,子View不再接收到Touch事件,而是转向本ViewGroup的onTouchEvent()方法处理。从Down开始,之后的Move,Up都会直接在onTouchEvent()方法中处理。

先前还在处理touch event的child view将会接收到一个ACTION_CANCEL。

如果onInterceptTouchEvent()返回false,则事件会交给child view处理。

Android中提供了ViewGroup、View、Activity三个层次的Touch事件处理。

处理过程是按照Touch事件从上到下传递,再按照是否消费的返回值,从下到上返回,即如果View的onTouchEvent返回false,将会向上传给它的parent的ViewGroup,如果ViewGroup不处理,将会一直向上返回到Activity。

即隧道式向下分发,然后冒泡式向上处理。

Android 中与 Touch 事件相关的方法包括:dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)、onTouchEvent(MotionEvent ev);能够响应这些方法的控件包括:ViewGroup、View、Activity。方法与控件的对应关系如下表所示:

Touch 事件相关方法方法功能ViewGroupViewActivity

public booleandispatchTouchEvent(MotionEvent ev)事件分发YesYesYes

public booleanonInterceptTouchEvent(MotionEvent ev)事件拦截YesYes / NoNo

public booleanonTouchEvent(MotionEvent ev)事件响应YesYesYes

从这张表中我们可以看到 ViewGroup 和 View 对与 Touch 事件相关的三个方法均能响应,而 Activity 对 onInterceptTouchEvent(MotionEvent ev) 也就是事件拦截不进行响应。另外需要注意的是 View 对 onInterceptTouchEvent(MotionEvent ev) 的响应的前提是可以向该 View 中添加子 View,如果当前的 View 已经是一个最小的单元View(比如TextView),那么就无法向这个最小 View 中添加子 View,也就无法向子 View 进行事件的拦截,所以它没有onInterceptTouchEvent(MotionEvent ev)。

三个方法的用法:

dispatchTouchEvent()用来分派事件。

其中调用了onInterceptTouchEvent()和onTouchEvent(),一般不重写该方法

onInterceptTouchEvent()用来拦截事件。

ViewGroup类中的源码实现就是{return false;}表示不拦截该事件,

事件将向下传递(传递给其子View);

若手动重写该方法,使其返回true则表示拦截,事件将终止向下传递,

事件由当前ViewGroup类来处理,就是调用该类的onTouchEvent()方法

onTouchEvent()用来处理事件。

返回true则表示该View能处理该事件,事件将终止向上传递(传递给其父View);

返回false表示不能处理,则把事件传递给其父View的onTouchEvent()方法来处理

【注】:ViewGroup的某些子类(GridView、ScrollView...)重写了onInterceptTouchEvent()方法,当发生ACTION_MOVE事件时,返回true进行拦截。

一、Touch事件分析

(一)、事件分发:public booleandispatchTouchEvent(MotionEvent ev)

Touch事件发生时Activity的dispatchTouchEvent(MotionEvent ev)方法会以隧道方式(从根元素依次往下传递直到最内层子元素或在中间某一元素中由于某一条件停止传递)将事件传递给最外层View的dispatchTouchEvent(MotionEvent ev)方法,并由该View的dispatchTouchEvent(MotionEvent ev)方法对事件进行分发。

(二)、事件拦截:public booleanonInterceptTouchEvent(MotionEvent ev)

在外层View的dispatchTouchEvent(MotionEvent ev)方法返回系统默认的super.dispatchTouchEvent(ev)情况下,事件会自动的分发给当前View的onInterceptTouchEvent方法。onInterceptTouchEvent的事件拦截逻辑如下:

•如果onInterceptTouchEvent返回true,则表示将事件进行拦截,并将拦截到的事件交由当前View的onTouchEvent进行处理;

•如果onInterceptTouchEvent返回false,则表示将事件放行,当前View上的事件会被传递到子View上,再由子View的dispatchTouchEvent来开始这个事件的分发;

•如果onInterceptTouchEvent返回super.onInterceptTouchEvent(ev),事件默认不会被拦截,并将拦截到的事件交由当前View的onTouchEvent进行处理。

(三)、事件响应:public booleanonTouchEvent(MotionEvent ev)

在dispatchTouchEvent返回super.dispatchTouchEvent(ev)并且onInterceptTouchEvent返回true或返回super.onInterceptTouchEvent(ev)的情况下onTouchEvent会被调用。onTouchEvent的事件响应逻辑如下:

•如果事件传递到当前View的onTouchEvent方法,而该方法返回了false,那么这个事件会从当前View向上传递,并且都是由上层View的onTouchEvent来接收,如果传递到上面的onTouchEvent也返回false,这个事件就会“消失”,而且接收不到下一次事件。

•如果返回了true则会接收并消费该事件。

•如果返回super.onTouchEvent(ev)默认处理事件的逻辑和返回false时相同。

onInterceptTouchEvent用于改变事件的传递方向。决定传递方向的是返回值,返回为false时事件会传递给子控件,返回值为true时事件会传递给当前控件的onTouchEvent(),这就是所谓的Intercept(拦截)。

正确的使用方法是,在此方法内仅判断事件是否需要拦截,然后返回。即便需要拦截也应该直接返回true,然后由onTouchEvent方法进行处理。

onTouchEvent用于处理事件,返回值决定当前控件是否消费(consume)了这个事件。尤其对于ACTION_DOWN事件,返回true,表示我想要处理后续事件;返回false,表示不关心此事件,并返回由父类进行处理。

可能你要问是否消费了又区别吗,反正我已经针对事件编写了处理代码?答案是有区别!比如ACTION_MOVE或者ACTION_UP发生的前提是一定曾经发生了ACTION_DOWN,如果你没有消费ACTION_DOWN,那么系统会认为ACTION_DOWN没有发生过,所以ACTION_MOVE或者ACTION_UP就不能被捕获。

在没有重写onInterceptTouchEvent()和onTouchEvent()的情况下(他们的返回值都是false)

Android系统中的每个View的子类都具有下面三个和TouchEvent处理密切相关的方法:

1)public boolean dispatchTouchEvent(MotionEvent ev)这个方法用来分发TouchEvent

2)public boolean onInterceptTouchEvent(MotionEvent ev)这个方法用来拦截TouchEvent

3)public boolean onTouchEvent(MotionEvent ev)这个方法用来处理TouchEvent

1、如果dispatchTouchEvent返回true,则交给这个view的onTouchEvent处理, 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。如果最终需要处理事件的view的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。

2、如果dispatchTouchEvent6返回false,则交给这个view的interceptTouchEvent方法来决定是否

要拦截这个事件,如果interceptTouchEvent返回true,表示拦截掉了,则交给它的onTouchEvent来处理,如果interceptTouchEvent返回false,那么就传递给子view,由子view的dispatchTouchEvent再来开始这个事件的分发。

3、如果事件传递到某一层的子view的onTouchEvent上了,这个方法返回了false,那么这个事件

会从这个view往上传递,都是onTouchEvent来接收。如果传递到最上面的onTouchEvent也返回false的话,这个事件就会“消失”,而且接收不到下一次事件。

【备注:】

蓝色区域为MyViewGroup、绿色为MyViewGroupInner、红色为MyView。分别继承于LinearLayout、LinearLayout和TextView。

1、核心代码:

【以下动作均为点击自定义View】

一、无拦截,无消费:12个

二、ViewGroup拦截,无消费:7个

03-31 18:50:26.344: I/MainActivity(15304): --->dispatchTouchEvent: 0

03-31 18:50:26.344: I/MyViewGroup(15304): --->dispatchTouchEvent: 0

03-31 18:50:26.372: I/MyViewGroup(15304): --->onInterceptTouchEvent: 0

03-31 18:50:26.392: I/MyViewGroup(15304): --->onTouchEvent: 0

03-31 18:50:26.402: I/MainActivity(15304): --->onTouchEvent: 0

03-31 18:50:26.526: I/MainActivity(15304): --->dispatchTouchEvent: 1

03-31 18:50:26.526: I/MainActivity(15304): --->onTouchEvent: 1

三、无拦截,View消费:14个

四、无拦截,仅有ViewGroup消费:12个

五、无拦截,Activity消费:12个

六、无拦截、无消费,仅在ViewGroup上设置单击监听,执行单击动作后:13个

七、无拦截、无消费,仅在ViewGroup上设置长按监听和单击监听,执行长按动作后:(如果长按返回false:14个 , 如果长按返回true:13个)

八、无拦截、无消费,仅仅在View上设置长按监听和单击监听,执行单击动作后:15个

九、无拦截、无消费,在View上设置长按监听和单击监听,执行长按动作后:(如果长按返回false:16个 , 如果长按返回true:15个)

十、无拦截、无消费,仅在View上设置clickable=true:14个

十一、无拦截、无消费,仅在ViewGroupInner上设置clickable=true:13个

A:dispatch:0

G:dispatch:0

G:intercept:0

Gi:dispatch:0

Gi:intercepte:0

V:dispatch:0

V:touch:0

Gi:touch:0

A:dispatch:1

G:disaptch:1

G:intercept:1

Gi:dispatch:1

Gi:touch:1

【备注:】

onLongClick按下到一定的时间就调用了,在ACTION_UP之前调用。如果长按返回false,则长按结束的ACTION_UP调用onClick(onClick是ACTION_UP之后调用的);如果长按返回true,onLongClick后不再调用onClick。

Click事件处理

Click事件:View的短按和长按都是注册监听器的(setListener):

onClick是在ACTION_UP之后执行的。

onLongClick则是按下到一定时间之后执行的,这个时间是ViewConfiguration中的:

private static final int TAP_TIMEOUT =180;//180毫秒

这里需要注意onLongClick的返回值,如果是false,则onLongClick之后,手指抬起,ACTION_UP之后还是会执行到onClick;但是如果onLongClick返回true,则不会再调用onClick。

【备注:】

如果一个View是Clickable或者longClickable,onTouchEvent()就直接返回true,表示该View就一直消费Touch事件。也就是说,一个clickable或者longclickable的View是一直消费Touch事件的,而一般的View既不是clickable也不是longclickable的(即不会消费Touch事件,只会执行ACTION_DOWN而不会执行ACTION_MOVE和ACTION_UP)。

Button是clickable的,可以消费Touch事件,但是我们可以通过setClickable()和setLongClickable()来设置View是否为clickable和longClickable。当然还可以通过重写View的onTouchEvent()方法来控制Touch事件的消费与否。

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

推荐阅读更多精彩内容