ViewGroup的事件分发机制

这两天的文章都是直接正如主题,没有什么开头的描述的,因为刚开始写文章,也不知道说什么。啊,今天天气不错哈。

那么今天呢我讲一下这个View的事件分发机制,这个很多面试的时候都会问到,因为这是View的一个核心点,对View的点击事件的一个分发的机制,所谓的事件分发机制呢,其实就是用户对这个View进行触碰产生的一个运动事件,比如说:点击事件,长按事件这些,这个事件产生了以后呢系统要把这个事件传递给一个View来进行消费,而这个传递的过程就是分发过程。事件分发过程是由三个方法来完成的

public boolean dispatchTouchEvent(MotionEvent ev)

用来进行事件分发,只要你触碰了任何控件,都会调用这个方法,主要是判断是当前view消费还是往下级view传递。

public boolean onInterceptTouchEvent(MotionEvent ev)

这个方法用来判断是否拦截这个事件,默认为false也就是不拦截,需要进行拦截的时候在dispatchTouchEvent内部调用。

public boolean onTouchEvent(MotionEvent event)

用来处理当前事件,返回结果表示是否消费,如果不消费,则这个事件不会再接收到。

那么他们三个有什么不可描述的关系呢,首先我们要先知道一个点击事件产生后的传递过程,当一个事件产生一定会先传递给当前Activity,然后Activity再传递给window,window会传递给当前页面的顶级View,最后这个View接收到事件以后再按照事件分发机制去分发事件。这里有一种情况就是,如果一个View的onTouchEvent返回了false,那么他的父容器的onTouchEvent也会被调用,以此类推,所有的View都不消费当前事件的话,那么最终这个事件会传递给Activity,Activity的onTouchEvent会进行消费掉。这里采用一个很好的例子,是从别的书籍上看到的,一个领导分配了一个很难的任务给你,然后你不会(onTouchEvent返回false),那么怎么办呢,只能交回给上级(上级的onTouchEvent被调用),那如果你的上级也搞不定呢,那就交给上级的上级,到最后最顶级的上级那里,进行消费掉。这个例子是不是很贴近生活,哈哈我也是从别得地方看到的。

那么我讲了这么半天大家也肯定还不是很懂,那么我们从源码入手进行分析,打开你们的studio,找到Activity,在Activity里面有个方法叫

public boolean dispatchTouchEvent(MotionEvent ev) {
if(ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if(getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}

通过上面的代码我们可以看出来,Activity将时间交给附属的Window进行分发,如果返回true的话,那么事件循环就结束了,如果返回false的话就
代表着事件没人处理,那么最后会交给Activity的onTouchEvent处理。

那么我接着Window往下看,Window是怎么处理这个事件的呢,我们看源码知道了Window是个抽象类,抽象类怎么处理呢?所以我们要找他Window的实现类PhoneWindow,我们找到PhoneWindow的dispatchEvent方法

@Override
public boolean superDispatchTrackballEvent(MotionEvent event) {
return mDecor.superDispatchTrackballEvent(event);
}

通过上面的代码我们可以看到PhoneWindow把事件传递给了mDecor,那么这个mDecor是什么呢,我继续往下看

// This is the top-level view of the window, containing the window decor.
private DecorView mDecor;

那么我们可以看到,mDecor是DecorView,那么这个DecorView又是什么呢,注意看上面的注释,翻译过来就是,这是窗口的顶级视图,包含窗口装饰。也就是说,PhoneWindow把事件传递给了DecorView也就是顶级视图,而DecorView是继承FrameLayout,而FrameLayout又是继承的Viewgroup,也就是说,事件一定会传递到Viewgroup里面去,所以我们直接去看Viewgroup的dispatchTouchEvent是怎么处理的。

// Check for interception.

final booleanintercepted;

if(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null) {

final booleandisallowIntercept = (mGroupFlags&FLAG_DISALLOW_INTERCEPT) !=0;

if(!disallowIntercept) {

intercepted = onInterceptTouchEvent(ev);

ev.setAction(action);// restore action in case it was changed

}else{

intercepted =false;

}

}else{

// There are no touch targets and this action is not an initial down

// so this view group continues to intercept touches.

intercepted =true;

}

Viewgroup的dispatchTouchEvent的代码逻辑比较多,我们就分段说明,先看上面这段代码,上面注释Check for interception.表示的意思是检查拦截,也就是这段代码的逻辑是是否拦截处理这个事件,然后我们从代码中可以看出,Viewgroup在两种情况下会判断是否拦截事件,那两种呢,一个是MotionEvent.ACTION_DOWN也就是按下的时候,还有一个是mFirstTouchTarget!=null的时候,那这个是什么情况呢,这里我们可以从后面的代码中可以看出来,在后面的代码中我们可以看出如果ViewGroup的子元素处理成功时,mFirstTouchTarget会被赋值并指向子元素,也就是当Viewgroup不拦截事件交由子元素处理时,mFirstTouchTarget !=null,反过来说,ViewGroup如果拦截了当前事件的话,那么(actionMasked == MotionEvent.ACTION_DOWN||mFirstTouchTarget!=null)这个判断就会不成立返回false,导致ViewGroup中的onInterceptTouchEvent不会再被调用,并且同一事件系列的其他事件也会默认交给他处理。

我们接着再看ViewGroup不拦截事件交由子元素处理的时候,看源码:

finalView[] children =mChildren;

for(inti = childrenCount -1; i >=0; i--) {

final intchildIndex = getAndVerifyPreorderedIndex(

childrenCount, i, customOrder);

finalView child =getAndVerifyPreorderedView(

preorderedList, children, childIndex);

// If there is a view that has accessibility focus we want it

// to get the event first and if not handled we will perform a

// normal dispatch. We may do a double iteration but this is

// safer given the timeframe.

if(childWithAccessibilityFocus !=null) {

if(childWithAccessibilityFocus != child) {

continue;

}

if(!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child,null)) {

ev.setTargetAccessibilityFocus(false);

continue;

}

newTouchTarget = getTouchTarget(child);

if(newTouchTarget !=null) {

// Child is already receiving touch within its bounds.

// Give it the new pointer in addition to the ones it is handling.

newTouchTarget.pointerIdBits|= idBitsToAssign;

break;

}

resetCancelNextUpFlag(child);

if(dispatchTransformedTouchEvent(ev,false, child, idBitsToAssign)) {

// Child wants to receive touch within its bounds.

mLastTouchDownTime= ev.getDownTime();

if(preorderedList !=null) {

// childIndex points into presorted list, find original index

for(intj =0; j < childrenCount; j++) {

if(children[childIndex] ==mChildren[j]) {

mLastTouchDownIndex= j;

break;

}

}

}else{

mLastTouchDownIndex= childIndex;

}

mLastTouchDownX= ev.getX();

mLastTouchDownY= ev.getY();

newTouchTarget = addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget =true;

break;

}

// The accessibility focus didn't handle the event, so clear

// the flag and do a normal dispatch to all children.

ev.setTargetAccessibilityFocus(false);

}

if(preorderedList !=null) preorderedList.clear();

}

上面这段代码呢,我们可以看出来在遍历ViewGroup所有的子元素,然后通过子元素是否在播动画和点击事件的坐标是否在子元素的区域内来判断子元素是否能够接受这个事件。如果这两个条件都满足就交给这个子元素处理,我们看到dispatchTransformedTouchEvent这个方法,点击进去看,你会发现

if(child ==null) {

handled =super.dispatchTouchEvent(event);

}else{

handled = child.dispatchTouchEvent(event);

}

他在判断child也就是子元素是否为空,不为空的话就交给子元素处理了,从而完成了一轮事件分发。如果子元素的dispatchTouchEvent返回true,这时我们暂时不考虑事件在子元素内部是怎么处理的,那么mFirstTouchTarget就会被赋值同时跳出for循环,看源码

newTouchTarget= addTouchTarget(child, idBitsToAssign);

alreadyDispatchedToNewTouchTarget =true;

break;

这里完成了mFirstTouchTarget的赋值并终止对子元素的遍历。吐过dispatchTouchEvent返回false,ViewGroup就会把事件分发给下一个子元素(如果还有下一个子元素的话)。

看到上面,你们一定会想mFirstTouchTarget是在哪里赋值的啊,我们看addTouchTarget这个方法

privateTouchTarget addTouchTarget(@NonNull View child,intpointerIdBits) {

finalTouchTarget target = TouchTarget.obtain(child, pointerIdBits);

target.next=mFirstTouchTarget;

mFirstTouchTarget= target;

returntarget;

}

mFirstTouchTarget是一种单链表结构,mFirstTouchTarget是否被赋值直接影响到ViewGroup对事件的拦截,如果mFirstTouchTarget为空,那么ViewGroup将默认拦截同一序列中的所有点击事件。

今天就先讲ViewGroup的事件分发,如果大家觉得还是不理解的话,就去按照郭神的例子敲一下,这里附上一个连接:http://blog.csdn.net/guolin_blog/article/details/9153747

谢谢大家对我的支持。

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

推荐阅读更多精彩内容