android
事件分发机制很多人都写,但我看的感觉不是很明白。讲解的时候经常略过一大段,有的还是用的老版代码,弄的我似懂非懂的。不如我自己研究下源码,完整的把流程捋一遍。算是巩固一下吧!
Android
事件分发机制,有点基础的人都知道。事件的分发是由Activity
到ViewGroup
到View
传递的。PhoneWindow
和DecorView
只是起到中转的作用。下面我们就来分析这一流程是怎么样走的.
一、ViewGroup拦截事件的执行流程
首先,当触摸屏幕事件发生时,是底层传感器最先接收到的。然后传感器会调用Activity
的dispatchTouchEvent()
开始进行分发。所以也可
以说,Activity
是最先接收到事件的。打开Activity
查找dispatchTouchEvent()
:
public boolean dispatchTouchEvent(MotionEvent ev){ if(ev.getAction()==MotionEvent.ACTION_DOWN){ onUserInteraction(); } if(getWindow().superDispatchTouchEvent(ev)){// 关键语句 returntrue; } return onTouchEvent(ev); }
看关键代码,getWindow().superDispatchTouchEvent(ev)
,我们都知道,Window
是个抽象类,其具体实现是PhoneWindow
。再来查
找PhoneWindow
的superDispatchTouchEvent()
:
public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
而PhoneWindow又调用了内部类DecorView的superDispatchTouchEvent方法。
public boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }
DecorView又调用super父类,DecorView是继承自FrameLayout,但是FrameLayout并没有实现这个方法。其实最终是在ViewGroup里
实现的。 我们来看看ViewGroup里的dispatchTouchEvent()方法,这个方法非常长,我们看关键的部分:
public boolean dispatchTouchEvent(MotionEvent ev) { ... intercepted = onInterceptTouchEvent(ev); // 接收是否被拦截,这里假设被拦截了,也就是intercepted为true。 if (!canceled && !intercepted) { // 如果拦截了这里是不会执行的 ... } // 第一次执行时,这里肯定是为null的,所以这里肯定是执行的。那么来看看dispatchTransformedTouchEvent方法。 if (mFirstTouchTarget == null) { handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... } ... return handled; }
这里注意,第三个参数传的是null,也就是没有子View。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... if (child == null) { // 没有子View那么就走的这里 handled = super.dispatchTouchEvent(event); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); event.offsetLocation(-offsetX, -offsetY); } return handled; ... return handled; }
ViewGroup
调super.dispatchTouchEvent
。注意,ViewGroup
的父类是View
。所以就调到了View
里去了。
public boolean dispatchTouchEvent(MotionEvent event) { // View的dispatchTouchEvent方法 ... if (onFilterTouchEventForSecurity(event)) { ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { // 这里的判断说明,我们调用了setOnTouchListener。 result = true; // 然后将这个标志置为true } if (!result && onTouchEvent(event)) { // 短路与操作符,第一个判断为假,第二个onTouchEvent就不会执行了 result = true; } } ... return result; }
从这里也可以知道,为什么我们给自定义View
设置OnTouchListener
后就不会再调用onTouchEvent
方法了。因为短路与的关系
onTouchEvent()
调用不到。以上是ViewGroup
里的onInterceptTouchEvent
为true
,也就是被拦截了。然后,如果ViewGroup
设置了onTouchEvent
,那么就交给
ViewGroup
的onTouchEvent
处理。但由于ViewGroup
没有实现onTouchEvent
,它最终会调用父类View
的onTouchEvent
处理。LinearLayout
、RelativeLayout
的onTouchEvent
都是调的View
的onTouchEvent
。而如果返回false
,那么就按照方法的传递顺序反向传递。最终传到:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { // 关键语句 return true; } return onTouchEvent(ev); }
是的,最终传到这里。getWindow().superDispatchTouchEvent(ev)
为false
,那么调用Activity
的onTouchEvent()
。
二、ViewGroup不拦截,正常的执行流程
(1)View的onTouchEvent()返回true
现在来看ViewGroup
里的onInterceptTouchEvent
为false
,也就是没有被拦截。 (以下是重复上面的代码,只不过走的逻辑不一样)
public boolean dispatchTouchEvent(MotionEvent ev) { ... if (!canceled && !intercepted) { // 因为没有拦截,也没有取消。所以走这块逻辑 ... final View[] children = mChildren; for (int i = childrenCount - 1; i >= 0; i--) { // 这里是遍历所有的子View final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } if (!canViewReceivePointerEvents(child) // 这里是判断子View是否处在触摸边界范围内 || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } // 这里是关键的一步,第一次流程时,调用这个方法传的第三个参数传的是null,而这里 // 因为遍历,所以每次传的都是有值的. if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... } ... } ... return handled; }
找到ViewGroup
的dispatchTransformedTouchEvent
这个方法
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { ... if (child == null) { handled = super.dispatchTouchEvent(event); // 不走这里 } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; event.offsetLocation(offsetX, offsetY); handled = child.dispatchTouchEvent(event); // 有子View,所以这次走的是这里 event.offsetLocation(-offsetX, -offsetY); } return handled; ... return handled; }
可以看到,这次走的是child.dispatchTouchEvent(event)
.顺便一提,offsetX
和offsetY
是相对于手机左上角原点的偏移量,也就是相对于绝对坐标的偏移量。 event.offsetLocation(offsetX, offsetY)
,将这两个偏移量传进去。子View
获取event.getX()
,getY()
时才会获取的是相对于ViewGroup
的值。
event.offsetLocation(-offsetX, -offsetY)
,这么做取反再设置会原来的样子。是因为后面还会有使用到 的,责任链模式传递中间不能修改传递的东西。
关于偏移量计算不懂的看下面这张图:
好了,言归正传。现在走的是child.dispatchTouchEvent(event)
。我们来看看View
的dispatchTouchEvent()
:
public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; ... if (onFilterTouchEventForSecurity(event)) { ... if (!result && onTouchEvent(event)) { // 第一个判断肯定能过去,第二个判断就是调用我们自定义的onTouchEvent方法 result = true; // 而如果我们自定义View的onTouchEvent返回true,那么result就为true了。 } } ... return result; // 然后就直接把result返回。 }
现在,我们自定义View
的onTouchEvent()
返回true
,那么View
的dispatchTouchEvent
也返回true
。ViewGroup
里的dispatchTransformedTouchEvent
也会返回true
。返回到ViewGroup
的dispatchTouchEvent()
方法里。
public boolean dispatchTouchEvent(MotionEvent ev) { ... if (!canceled && !intercepted) { ... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 进到这个方法里 ... newTouchTarget = addTouchTarget(child, idBitsToAssign); // 这里是关键的地方 ... break; } ... } ... return handled; }
这个addTouchTarget()
非常重要。当我们自定义View
的onTouchEvent()
返回true
时,就代表所有触摸事件由这个自定义View来处理。
那么后续的触摸事件,系统是不会在经过一大堆遍历和判断,而是直接将事件传给自定义View
。原因就是addTouchTarget()
。
private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target; return target; }
它是个链表,android
中应用责任链模式的地方非常多。注意,只有调了这个addTouchTarget()
方法后mFirstTouchTarget
才被赋值,
只有这一个地方。addTouchTarget()
添加完子View
之后,就直接break
了,所以其它子View
是没有机会接触到触摸事件的。再回到ViewGroup
的dispatchTouchEvent()
。
public boolean dispatchTouchEvent(MotionEvent ev) { ... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { ... newTouchTarget = addTouchTarget(child, idBitsToAssign); ... break; } ... if (mFirstTouchTarget == null) { // 我们看到mFirstTouchTarget刚刚已经赋值了,所以走的是else语句块 ... } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; // 这是头节点 while (target != null) { final TouchTarget next = target.next; // 不断的while循环,找下一个节点 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { // 当MOVE事件不断发生时,这里也是不断的调用 handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } ... return handled; }
好,关于这个ViewGroup
的dispatchTouchEvent()
,我们再来梳理一下关于DOWN
事件和MOVE
事件的调用:
public boolean dispatchTouchEvent(MotionEvent ev) { ... if (!canceled && !intercepted) { // 如果没有被取消也没有被拦截 ... if (actionMasked == MotionEvent.ACTION_DOWN // 从这里的判断看出,如果是DOWN事件就走这个语句块 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { ... } } if (mFirstTouchTarget == null) { ... } else { // 那么MOVE事件只有从这里走了 ... } ... return handled; }
现在我们可以知道,如果是DOWN
事件,就会按部就班的一步一步dispatch
,intercept
,ontouchevent
这样来。 如果是MOVE
事件,就会直接到自定义View
的onTouchEvent()
。因为addTrouchTarget()
了,mFirstTouchTarget
就指向被点击的View
了。
(2)View的onTouchEvent()返回false
现在是第二种情况,View
的onTouchEvent()
返回false
,表示子View
不处理了。 回到ViewGroup
的dispatchTransformedTouchEvent
调用child.dispatchTouchEvent(event)
,也就是View
的dispatchTouchEvent()
的时候。
public boolean dispatchTouchEvent(MotionEvent event) { ... boolean result = false; ... if (onFilterTouchEventForSecurity(event)) { ... if (!result && onTouchEvent(event)) { // 第一种情况判断的是自定义View的onTouchEvent()返回true,代表处理。而现在返回false,代表不处理。 result = true; } } ... return result; // 然后就直接把result返回。返回false. }
View
的dispatchTouchEvent
返回false
。回到ViewGroup
的dispatchTouchEvent()
里。
public boolean dispatchTouchEvent(MotionEvent ev) { ... if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { // 返回false代表这里不走 ... } ... if (mFirstTouchTarget == null) { // 就会走到这里 handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { ... } ... }
而走到if (mFirstTouchTarget == null)
这个语句块就意味着,轮到ViewGroup
来判断了,然后会一直返回到Activity
里去。调用Activity
的onTouchEvent()
方法。
以上是在Android 5.x
版本源码分析,如果没有源码可以从这个网址保存或下载,里面从2.x - 7.x
版本的源码都有:
无密码,注意压缩格式是7z
格式,如果用普通解压工具解压出错。安装这个7-Zip
试试!
解压源码非常耗时间,因为里面大概有40多万个文件,大概需要1个小时的时间。
如果嫌下载和解压太麻烦,可以到打开这个网址。在线android 5.x
源码查看网站:
总结
事件分发从Activity
到ViewGroup
再到View
。这一流程,如果一开始Activity
就拦截就没什么好说的了。ViewGroup
拦截后对于是否处理做了源码分析。处理就处理,不处理就一直返回false
到Activity
的dispatchTouchEvent()
里去,最终调用Activity
的onTouchEvent()
方法。View
也是类似的,处理就处理,不处理最终也是调用Activity
的onTouchEvent()
方法。