android事件分发机制源码简析
2016-10-25 13:06
453 查看
题记
android事件分发这块已有众多大神的各种解析,我就取个“简析”吧。本来就不打算写 得多详细,权当是自己的一点总结。
前言
这块内容网上和书上都看过蛮多,一直都是似懂非懂。幸而找到郭神的两篇大作。立论 清晰、分析入理、总结到位。附上地址: http://blog.csdn.net/sinyu890807/article/details/9097463 如果仅仅是拾人牙慧,甚而把他人写的拿来重新组织下也无甚意思。本文是我自己阅读 源码的一些见解。
正文
先前看过《android群英传》对事件机制的概括,用的是部门模型。大致意思是: Viewgroup到其子view的事件传递,类似于一个公司中:总经理-》部长-》员工之间 的任务下达和工作报告。看过后觉得作者说得很对,但就是有好多疑问没法用这套模型 来解释。然后,通过自己阅读源码,我觉得用“甩锅”模型来描述android的事件分发机 制或者更为贴切。(这里权当是我的大言不惭) 下面就多个控件层叠的情况讨论下事件分发,即:一个Viewgroup中还有子View的情 况。 PS:所有源码基于android_4.4
流程
这是我整理的事件分发流程图,下面的叙述围绕它展开。
Viewgroup.dispatchTouchEvent
从Viewgroup的dispatchTouchEvent走起,当你touch最外层的Viewgroup时,首 先会调用这个,我要叙述的也仅局限于此,不去关心事件如何被采集之类。 下面分段叙述ViewGroup.dispatchTouchEvent中的源码。
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (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; }
如果disallowIntercept没被enable的话,onInterceptTouchEvent将会被首先调用以完成对事件的拦截。intercepted 的值取决于返回值,并作为后续是否分发事件的判断标志,如下代码所示。
if (!canceled && !intercepted) { ...... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { addTouchTarget(...)//包含对mFirstTouchTarget的赋值。 } ...... } ...... if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ...... } /** * Transforms a motion event into the coordinate space of a particular child view, * filters out irrelevant pointer ids, and overrides its action if necessary. * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead. */ private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ...... if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } ...... }
android_4.4的代码和人家博客里几年前的已经大不相同了。
大致流程:
1)通过检测intercepted来判断事件是否被拦截,如未被拦截则找到相应的child来处理本次事件。至于按什么规则来寻找child,我觉得应该是遍历所有child以定位当前点击位置属于那个child(这部分未分析源码验证)。
2)在继续之前先需要明白dispatchTransformedTouchEvent这个函数的作用。其作用就是根据child参数是否为null,来判别是调用child.dispatchTouchEvent还是Viewgroup自身的dispatchTouchEvent。
3)承接1),如果child.dispatchTouchEvent返回false或者未找到相应child,则mFirstTouchTarget ==null,则后续调用dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)。可以看到child参数为null,其实就是调用了Viewgroup自身的dispatchTouchEvent来处理本次事件。
4)如果child.dispatchTouchEvent返回true,则调用addTouchTarget完成对mFirstTouchTarget 的赋值,Viewgroup的dispatchTouchEvent也就不会再被调用。
ps:如果child本身也是Viewgroup则递归此过程。从整体流程可以看出:只要有一个child处理了本次事件,则位于上层的Viewgroup就不再关心本次事件的处理了。就好比:一个事件就是一个锅,一层层甩下去,爱谁接谁接,实在没人接么只好自己来。
然后,我们可以查看下TextView的源码(Button等继承自它),并未重写View的dispatchTouchEvent。也就是说最后事件的处理由View.dispatchTouchEvent完成。
View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) { ...... if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } if (!result && onTouchEvent(event)) { result = true; } ...... }
1)优先mOnTouchListener ,如果你设置了mOnTouchListener 并且返回true的话,onTouchEvent就不会被执行。提前剧透下:onTouchEvent是负责具体检测事件的(如:click等),也就是说你注册的OnClickListener也就无效了。
2)无论是mOnTouchListener或者onTouchEvent返回了true,那dispatchTouchEvent也就返回true,那本次事件就被消费掉了,Viewgroup就不处理了。
3)经测试:以一次滑动为例–按下、滑动、抬起。dispatchTouchEvent开始的返回值会影响后续事件的接收。也就是说:mOnTouchListener.onTouch和onTouchEvent都返回false,那表示该View不想处理本次事件,则后续的MOVE和UP不会再被通知。(此部分非阅读源码分析得到,是由测试得出,后续会附上测试例程)
onTouchEvent
onTouchEvent具体负责事件的处理,就是人家告诉你:按下了、按着呢、抬起来了。然后据此判断是否是:click事件、longClick事件等等。怎么判断的不作讨论。public boolean onTouchEvent(MotionEvent event) { switch (action) { case MotionEvent.ACTION_UP: ...... if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick(); } ...... break; case MotionEvent.ACTION_DOWN: ...... checkForLongClick(0, x, y); ...... break; } return true; } private final class PerformClick implements Runnable { @Override public void run() { performClick(); } } private final class CheckForLongPress implements Runnable { private int mOriginalWindowAttachCount; private float mX; private float mY; @Override public void run() { if (isPressed() && (mParent != null) && mOriginalWindowAttachCount == mWindowAttachCount) { if (performLongClick(mX, mY)) { mHasPerformedLongPress = true; } } } public void setAnchor(float x, float y) { mX = x; mY = y; } public void rememberWindowAttachCount() { mOriginalWindowAttachCount = mWindowAttachCount; } }
此处的代码就很好理解了。当你点击的时候,最后那一下UP触发click事件。按的时间持续足够了触发longClick事件。
尤其注意到的是,android对事件处理的思路就是:应该在线程中进行,以便不影响UI主线程的体验。但click事件会根据post的返回值来确定是否直接performClick,而longClick则必定是通过post来传递。进而引出另外一个问题:post出去的Runnable是在何时被执行。因为mOnClickListener中是可以更新UI的,那此处的Runnable肯定不同于一般的线程,我觉得应该类似于handler的消息机制,这个有待后续深究。
PS:此处还有一点当你同时注册了longClick和click需要注意:longClick是有返回值的,且其返回值影响mHasPerformedLongPress的值,mHasPerformedLongPress决定了click是否被调用,此处应该是为了防止longClick和click在某个临界上被同时触发。所以longClick中最好还是返回true,以避免此类情况。
总结
1,继承自Viewgroup的容器类提供onInterceptTouchEvent方法来决定是否对事件拦截。如果拦截则会调用Viewgroup自身的事件处理。2,如果不拦截则会找到能处理该事件的child,如果找不到或者child不处理还是得Viewgroup来处理。
3,child对起始事件的拒收影响后续的接收。
4,对于onTouchEvent,一旦你处理该次事件它就返回true。所谓的处理就是:只要View是Clickable的就行。也就是说:不管是否注册了click或者longClick事件,只要没有关掉view的Clickable,那本次事件就算你处理了,Viewgroup就收不到事件了。
5,遗留:mOnClickListener和mOnLongClickListener涉及到的post Runnable问题。
6,在下一篇会写一个例程验证本文提到的所有点。
最后的归纳:开头提到的《android群英传》中的部门模型是把onTouchEvent当成向 上层的Viewgroup提交工作报告的途径。可以通过override来决定通知上层本次工作 是否已被完成。本意没错,但总觉得有点痒的不挠挠痛的地方。因为,这没有解释:常 用的注册OnClickListener后,listener怎么被调用,Viewgroup和child同时注 册的话,谁的listener该被调用。 我觉得android遵循的就是:为事件找到处理者(把锅甩出去)。
相关文章推荐
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下) 。
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
- Android事件分发机制完全解析,带你从源码的角度彻底理解
- Android事件分发机制完全解析,带你从源码的角度彻底理解(下)