在上篇中,我们分别对dispatchTouchEvent(),onInterceptTouchEvent()以及onTouchEvent()这三个方法进行了比较详细的分析,但是其实在执行onTouchEvent方法前,还有一个比较重要的触摸函数,那就是View.OnTouchListener: boolean onTouch(View v, MotionEvent event),只不过这个函数需要我们在相应的控件中注册OnTouchListener监听接口,这个稍后会分析。
先来看看本篇分分段,共3个部分:
1. activity是如何把事件分发给viewgroup
2.viewgroup的事件分发与拦截过程
3.view的事件分发处理过程
那就从第一个开始呗。
1. Activity是如何把事件分发给ViewGroup?
还记得这张事件传递处理的流程图吗?细心回忆一下后,从图中可以看到入口是Activity,那么我们就从Activity开始吧。
当一个点击事件发生时,事件最先传递给当前Activity,然后由Activity的dispatchTouchEvent()进行对事件分发,因此我们先从Activity的dispatchTouchEvent()开始分析
我们先看一下Activity的dispatchTouchEvent()的源码:
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
从源码可以看出Activity的dispatchTouchEvent()方法也是把事件是交给了Activity所依附的窗体,即调用Window.superDispatchTouchEvent(ev);如果superDispatchTouchEvent()返回了true,那么整个事件传递处理将会结束,返回false就意味着没有处理事件,最终交给Activity的onTouchEvent(ev)处理。这里跟我上一篇分析的流程是相对应的。这里调用前还有个onUserInteraction(),这个方法其实是做一些用户交互操作的初始化内容,该方法是个空方法,需要时我们可以重写该方法。
那么window又是如何把事件传递给viewgroup的呢?
public abstract boolean superDispatchTouchEvent(MotionEvent event);
public abstract class Window {}通过源码我们知道 superDispatchTouchEvent是个抽象方法,也就是说window是个抽象类,所以我们必须找到其实现类才行。我们直接看看getWindow()方法实现:
public Window getWindow() { return mWindow; }
返回了mWindow,那么我直接找mWindow在哪创建的即可,最后找到了:
@Override public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }这里PhoneWindow直接把事件传递给了 DecorView, 调用的是 mDecor #superDispatchTouchEvent(event),那mDecor又是什么?其实我们前面好几次都提到这个类,其实DecorView是PhoneWindow的一个内部类:
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
可以看到DecorView继承自FrameLayout,所以DecorView也是一个ViewGroup,因此到了这里我们就明白了,事件发生后通过Activity#dispatchTouchEvent(event)方法分发给了当前所依附的Window窗口,而Window的实现类是PhoneWindow,通过调用PhoneWindow#superDispatchTouchEvent(event)方法,把事件直接传递了给了其内部类DecorView,而内部类DecorView又是个ViewGroup,此时事件已通过DecorView#superDispatchTouchEvent(event)方法传递给了ViewGroup.
那么有人会问了,DecorView到底是什么呀?下面我随意新建一个项目运行(去掉了标题栏哈)。然后通过android提供的工具hierarchyviewer查看布局关系图:
这样就大概能明白DercoView是什么了吧。DercoView这里就不再过多分析,回归本篇的主题。
通过上面的分析,我们都知道了点击事件是如何通过Activity传递给ViewGroup了。
2.ViewGroup的事件分发和拦截
下面我们接着对ViewGroup是如何分发,拦截和处理进行分析。
我们回忆一下,上一篇文章中事件传递和处理流程:
当TouchEvent发生时,首先Activity将TouchEvent通过getWindow().superDispatchTouchEvent(event)把事件分发到当前活动窗口(PhoneWindow),之后是顶级窗口的DecorView,调用了DecorView的dispatchTouchEvent,DecorView继承自ViewGroup,所以这里实际上就进入了ViewGroup层面的dispatchTouchEvent,然后由dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给当前这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false 则交给当前这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给当前这个view的onTouchEvent来处理,如果 interceptTouchEvent 返回 false ,那么就继续传递给子view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,而且都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话(包括Activity),这个事件就会“消失”, 而且接收不到下一次事件。(这指的是 down 到 up 之间的一系列事件)。Touchevent 中,返回值是 true ,则说明消耗掉了这个事件。
回忆完了,我们先有点印象就行,因为通过下面源码分析后,其实内部还有很多细节,最后我会做一个比较完整的总结,这里我们先从ViewGroup的事件分发开始分析:
查看ViewGroup#dispatchTouchEvent()方法的源码,该方法源码比较多,我们只看主要的(省略部分没必要的源码),然后一段段来分析:
@Override public boolean dispatchTouchEvent(MotionEvent ev) { boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; // Handle an initial down. if (actionMasked == MotionEvent.ACTION_DOWN) { // due to an app switch, ANR, or some other state change. cancelAndClearTouchTargets(ev); //重置mGroupFlags的值-mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT resetTouchState(); } // 先看这里,检测是否拦截事件 final boolean intercepted; 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; }
从源码可以看出ViewGroup在两种情况下会判断是否拦截当前事件:当事件类型为MotionEvent.ACTION_DOWN 或者mFirstTouchTarget != null时。ACTION_DOWN是按下时当触摸事件,但mFirstTouchTarget又是什么鬼!其实当ViewGroup不拦截事件并将事件分发给子类时mFirstTouchTarget会被赋值并且指向子View,当然如果ViewGroup拦截了事件,这也就意味着mFirstTouchTarget肯定为null,那么当ACTION_UP和ACTION_MOVE响应时,条件为if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)肯定不会成立,那么ViewGroup#onInterceptTouchEvent()方法也不会再被调用,也就是MotionEvent.ACTION_DOWN操作被一旦被拦截,同一序列的事件(ACTION_UP和ACTION_MOVE)都会默认交给当前ViewGroup#onTouchEvent()处理。如果MotionEvent.ACTION_DOWN事件没有被拦截那么mFirstTouchTarget!=null是肯定成立的,这时ACTION_UP和ACTION_MOVE是否被拦截就要看disallowIntercept的取值或者ViewGroup#onInterceptTouchEvent()方法本身的返回值了。
顺便说明一下从源码可以看出ViewGroup#onInterceptTouchEvent()默认返回false。
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
关于disallowIntercept,这是一种特殊情况可以控制ViewGroup的永远不拦截子view的事件(ACTION_DOWN除外),从源码我们可以看到
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
因此只有disallowIntercept为false时,才会调用ViewGroup#onInterceptTouchEvent()方法,反之,disallowIntercept为true时,就永远不会调用ViewGroup#onInterceptTouchEvent()方法也就是ViewGroup无法拦截子view的(ACTION_DOWN除外)事件,但 disallowIntercept的取值,取决于mGroupFlags ,而mGroupFlags的值又是那里来的?不知道大家有没用过下面这个函数,这个函数注释写得很明白,如果当你不想子view的事件被父view拦截时,可以在子view中调用父view的requestDisallowInterceptTouchEvent(true)方法,并设置为true(也就是mGroupFlags |= FLAG_DISALLOW_INTERCEPT),这时父view就不会拦截子view的事件了。所以这个函数就是用来设置 mGroupFlags 的值,当传入值为true时,表示父view不拦截子view(ACTION_DOWN除外)的事件。
/** * Called when a child does not want this parent and its ancestors to * intercept touch events with * {@link ViewGroup#onInterceptTouchEvent(MotionEvent)}. * * <p>This parent should pass this call onto its parents. This parent must obey * this request for the duration of the touch (that is, only clear the flag * after this parent has received an up or a cancel.</p> * * @param disallowIntercept True if the child does not want the parent to * intercept touch events. */ public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) { if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) { // We're already in this state, assume our ancestors are too return; } //设置mGroupFlags的值 if (disallowIntercept) { mGroupFlags |= FLAG_DISALLOW_INTERCEPT; } else { mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; } // Pass it up to our parent if (mParent != null) { mParent.requestDisallowInterceptTouchEvent(disallowIntercept); } }
/** * Resets all touch state in preparation for a new cycle. */ private void resetTouchState() { clearTouchTargets(); resetCancelNextUpFlag(this); mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;//重置mGroupFlags的值 mNestedScrollAxes = SCROLL_AXIS_NONE; }
这也就间接说明了调用ViewGroup#requestDisallowInterceptTouchEvent(true)方法并不能影响ViewGroup对ACTION_DOWN事件的处理。
下面是我第一次使用ViewGroup#requestDisallowInterceptTouchEvent(true)方法的情景,提供给大家参考参考。
第一接触这个方法是因为ViewPager,当时用ViewPager来实现左右滑动切换tab,然后在tab的某一项中嵌入了水平可滑动的View就会滑动变得混淆起来,比如想滑动tab项中的可水平滑动的控件,却导致tab切换。当时我就是利用这个方法解决了滑动混淆的问题。
解决方案:嵌套的子view控件中注入ViewPager实例(调用控件的getParent()方法),然后在onTouchEvent里面告诉父View,也就是ViewPager不要拦截该控件上的触摸事件,处理代码如下:
@Override public boolean onTouchEvent(MotionEvent event) { switch (event.getAction()){ case MotionEvent.ACTION_MOVE: getParent().requestDisallowInterceptTouchEvent(true);//不拦截 break; case MotionEvent.ACTION_UP: case MotionEvent.ACTION_CANCEL: getParent().requestDisallowInterceptTouchEvent(false);//拦截,恢复正常 break; } return super.onTouchEvent(event); }
到这里,我们先总结一下viewgroup的事件分发,从ViewGroup#dispatchTouchEvent()中可以知道,事件传递到dispatchTouchEvent()时,先通过(actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null)条件判断是否进行拦截操作,如果满足条件,再根据disallowIntercept的值判断是否可以调用ViewGroup#onInterceptTouchEvent()方法。而disallowIntercept的值取决于mGroupFlags,这个值决定了在ViewGroup不拦截ACTION_DOWN事件的条件下,ViewGroup是否可以拦截子view的事件,如果我们在子view中调用ViewGroup#requestDisallowInterceptTouchEvent(true),并设为true(mGroupFlags|=FLAG_DISALLOW_INTERCEPT)时,ViewGroup将无法拦截除了ACTION_DOWN事件以外的其他事件(ACTION_UP和ACTION_MOVE等),如果设置为false,则ViewGroup可以拦截子View的事件,但是是否拦截还需根据ViewGroup#onInterceptTouchEvent()方法的返回值。当然由于执行ACTION_DOWN事件时,调用resetTouchState()方法重置mGroupFlags的值,所有mGroupFlags的值对ACTION_DOWN事件没影响。如果ViewGroup拦截了ACTION_DOWN事件,那么后续的所有序列事件都默认交给当前ViewGroup处理并不会再调用ViewGroup#onInterceptTouchEvent()。
这里还可以得出一些结论:
1.ViewGroup#onInterceptTouchEvent()方法并不是每次事件都会被调用,如果我们提前处理所有点击事件,应该选中ViewGroup的dispatchTouchEvent()方法。
2.我们可以在子view中调用ViewGroup#requestDisallowInterceptTouchEvent(true)方法来控制父view永远不拦截子view除了ACTION_DOWN事件以外的其他事件(ACTION_UP和ACTION_MOVE等)。当然这里的前提条件是事件可以传递到该viewgroup同时viewgroup没有拦截ACTION_DOWN事件。
//既然是不拦截那么intercepted肯定为false,这时事件一般为不会结束,所以canceled也为false if (!canceled && !intercepted) { View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) : TouchTarget.ALL_POINTER_IDS; // Clean up earlier touch targets for this pointer id in case they // have become out of sync. removePointersFromTouchTargets(idBitsToAssign); //获取子view的数量,准备遍历所有子view final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. //查找可以接收事件的子view final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //开始遍历子view并判断能否接收点击事件 for (int i = childrenCount - 1; i >= 0; i--) { 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; } //判断子view是否在播放动画和点击事件的坐标是否落在子view的区域内 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); //这里是重点哦,把事件分发给子view就是在这个 dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法里面执行的 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 (int j = 0; j < childrenCount; j++) { if (children[childIndex] == mChildren[j]) { mLastTouchDownIndex = j; break; } } } else { mLastTouchDownIndex = childIndex; } mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //如果子view处理点击事件即dispatchTouchEvent返回true,newTouchTarget会被赋值 //这时还记得我们前面提到的mFirstTounchTarget变量吗?此时mFirstTounchTarget //会在addTouchTarget 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不拦截事件,同时这时事件也不是结束事件的话,那么 if (! canceled && ! intercepted ) 必将成立。进入该if的方法体中首先会去遍历ViewGroup的所有子View,然后判断子View能否接收点击事件————通过两个条件判断, 先判断子view是否在播放动画和点击事件的坐标是否落在子view的区域内。如果有子view满足这两个条件,那么事件将交给该子view处理。那么事件到底时怎么样分发给子view的呢?这里其实是在dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法里调用了子view的 child .dispatchTouchEvent( event )方法,就这样子view就接收到点击事件了,这也就完成了一轮事件分发。我们直接看看ViewGroup#dispatchTransformedTouchEvent方法源码:
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { //省略其他代码 //判断子view是否为null,从而决定调用父view的super.dispatchTouchEvent(event) //还是子view的child.dispatchTouchEvent(event) if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } return handled; }
private TouchTarget addTouchTarget(View child, int pointerIdBits) { TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; mFirstTouchTarget = target;//赋值 return target; }
由源码可知mFirstTouchTarget确实是完成了赋值,但这有什么作用啊?
还记得这条判断语句吗?if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) 这也就说明
mFirstTouchTarget将影响ViewGroup除了MotionEvent.ACTION_DOWN以外的其他事件的拦截策略,如果mFirstTouchTarget为null,那么ViewGroup就默认拦截接下来的依序列(ACTION_UP和ACTION_MOVE等)的事件。
这里我们小结一下:
如果ViewGroup不拦截子view的点击事件,那么ViewGroup将会去循环遍历所有的子View, 然后根据子view是否在播放动画和点击事件的坐标是否落在子view的区域内这两个条件判断是否可接收点击事件,如果满足这两个条件,将会通过ViewGroup#dispatchTransformedTouchEvent方法去调用child.dispatchTouchEvent(event),从而将点击事件交给该子view处理,如果子view的dispatchTouchEvent(event)方法返回true(这一般都是因为子view的onTouchEvent()返回了true消费了事件),那么将会执行ViewGroup#addTouchTarget(child, idBitsToAssign)方法完成对mFirstTouchTarget变量的赋值,从而影响ViewGroup的事件拦截策略。当然如果ViewGroup最后都没有遍历到适合到子view处理点击事件,这里分两个情况,一个是没有子view,另一个则是子view对事件处理返回了false即未消费事件,那么事件将交由ViewGroup自己处理,实际上是调用了super.dispatchTouchEvent(event)。
那么,接下来我们来看看,ViewGroup在没有找到适合子类时是如何自己处理事件的(注意哦,这里仍然是ViewGroup未拦截子view事件的情况哈):
在ViewGroup#dispatchTouchEvent()方法体中继续往下查看源码,我们可以发现:
if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); }
确实mFirstTouchTarget为null,仍然会去调用ViewGroup#dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS)方法,但请注意,此时第3个参数child为null!!!很显然这里肯定会去调用super.dispatchTouchEvent(event)也就跳转到GroupView的父view了,那么GroupView父view是谁啊?那还用说么?当然是顶级View啊!!!也就是点击事件交由View来处理。
3. View对点击事件的处理过程
看到了这里时我搜索了一下ViewGroup的源码,结果没发现ViewGroup的onTounchEvent(),也就是说ViewGroup并没有重写父view的onTounchEvent(),因此也可以判断出,ViewGroup通过间接调用super.dispatchTouchEvent(event)方法来调用父view(也可以说是ViewGroup自己)的onTounchEvent().
那我们先看看view#dispatchTouchEvent(MotionEvent event)方法源码:
public boolean dispatchTouchEvent(MotionEvent event) { //其他代码省略,主要看这一段代码 if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { result = true; } //在这里执行了onTouchEvent(event) if (!result && onTouchEvent(event)) { result = true; } } return result; }
代码不算多,但我们不全部看,只看贴出来的源码。
View对点击事件的处理过程不太复杂,因为view只是一个单独的元素,没有子元素因此它只能自己处理事件。
li != null && li.mOnTouchListener != null这个条件是判断监听接口是否有注册
(mViewFlags & ENABLED_MASK)== ENABLED 而这个条件是判断控件是否可用,这个条件一般都为true
li.mOnTouchListener.onTouch(this, event),这个是判断实现这个监听接口的onTouch方法是返回true还是false;
因此主要决定OnTouchEvent()是否被调用的因素还是在onTouch()函数身上,因此有了以下结论:
1.onTouch()方法总是在onTouchEvent()之前执行(还记得文章开头提到过的onTouch吧)
2.只有当onTouch()方法返回false时,才会去调用onTouchEvent()方法
3.当onTouch()方法返回true时,onTouchEvent()方法不会执行
嗯,那么我们在看看onTouchEvent()源码,先贴部分源码,
if ((viewFlags & ENABLED_MASK) == DISABLED) { if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) { setPressed(false); } // A disabled view that is clickable still consumes the touch // events, it just doesn't respond to them. return (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE); }
由代码可知上面是View处于不可用状态点击事件的处理过程,由注释我们可知道即使View处于不可用状态照样会消费点击事件。
下面再看看View处于可用状态下对点击事件对处理
if (((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) { switch (action) { case MotionEvent.ACTION_UP: boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0; if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) { // take focus if we don't have it already and we should in // touch mode. boolean focusTaken = false; if (isFocusable() && isFocusableInTouchMode() && !isFocused()) { focusTaken = requestFocus(); } if (prepressed) { // The button is being released before we actually // showed it as pressed. Make it show the pressed // state now (before scheduling the click) to ensure // the user sees it. setPressed(true, x, y); } if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) { // This is a tap, so remove the longpress check removeLongPressCallback(); // Only perform take click actions if we were in the pressed state if (!focusTaken) { // Use a Runnable and post this rather than calling // performClick directly. This lets other visual state // of the view update before click actions start. if (mPerformClick == null) { mPerformClick = new PerformClick(); } if (!post(mPerformClick)) { performClick();//onClick()点击事件的处理 } } } ................. } break; } return true; }
源码相当长,这里省略一些没必要的代码。
从源码可知道,只要view的CLICKABLE和LONG_CLICKABLE有一个为true就会消费该事件,此时onTouchEvent方法返回true
同时在 MotionEvent.ACTION_UP事件中会触发performClick()(其实方法内部就是对onClick()点击事件的处理),这也说明onTouchEvent事件如果不执行的话,onclick()事件也别想得到执行,直接上performClick()的源码:
public boolean performClick() { final boolean result; final ListenerInfo li = mListenerInfo; if (li != null && li.mOnClickListener != null) { playSoundEffect(SoundEffectConstants.CLICK); li.mOnClickListener.onClick(this);//对onClick()点击事件的处理 result = true; } else { result = false; } sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED); return result; }
可能到这里大家会有疑问,怎么知道view的CLICKABLE和LONG_CLICKABLE是否为true啊?其实这就跟view控件本身有关了。例如TextView本身就不具备可点击状态这时CLICKABLE和LONG_CLICKABLE肯定为false,Button本身就是可点击状态所以CLICKABLE和LONG_CLICKABLE肯定为true。不对啊,有时我们也可以点击TextView啊?确实,但别忘记了,那时是已经为TextView设置了setOnClickListener()事件,所以TextView内部的CLICKABLE会变为true。因此这点也意味着我们可以通过设置setOnClickListener()和setOnLongClickListener()改变view控件CLICKABLE和LONG_CLICKABLE的值,从而是不可点击状态的view变为可点击状态。
看一下两个方法的源码就更清晰了:
public void setOnClickListener(@Nullable OnClickListener l) { if (!isClickable()) { setClickable(true); } getListenerInfo().mOnClickListener = l; }
public void setOnLongClickListener(@Nullable OnLongClickListener l) { if (!isLongClickable()) { setLongClickable(true); } getListenerInfo().mOnLongClickListener = l; }
最后,把两篇文章结合一下,然后来个比较完整的总结:
当TouchEvent发生时,首先Activity将TouchEvent 通过getWindow().superDispatchTouchEvent(event)把事件分发到当前活动窗口(PhoneWindow) , 之后是窗口的DecorView,调用了DecorView的dispatchTouchEvent,DecorView继承自ViewGroup,所以这里实际上就进入了ViewGroup层面的dispatchTouchEvent ,然后由当前ViewGroup#dispatchTouchEvent 方法进行分发,这时当前ViewGroup#dispatchTouchEvent 方法内部会去调用 ViewGroup#onInterceptTouchEvent方法,然后判断是否要拦截当前事件,如果onInterceptTouchEvent()返回true,则表示拦截,这时ViewGroup#dispatchTouchEvent内部会去调用父View.dispatchTouchEvent()(这里是通过ViewGroup# dispatchTransformedTouchEvent()方法间接调用的 ),如果控件注册了 OnTouchListener监听,则交给onTouch方法进行处理, 如果onTouch返回true,则事件被消费,直接返回并结束。 如果onTouch方法返回false, 则交给当前这个顶级View的onTouchEvent来处理(其实这里可以理解为调用的是ViewGroup自己的onTouchEvent(),因为ViewGroup本身并没有去重写父View的onTouchEvent(),至少我在ViewGroup的源码没找到),如果 interceptTouchEvent 返回 false,则表示不拦截,ViewGroup#dispatchTouchEvent 方法内部就继续执行并去遍历子view(前提是ViewGroup有子view),试图找到合适的子view并调用子view的dispatchTouchEvent(),把事件传递给子view。如果GroupView没有子 View或者子view都没有处理事件即子view的dispatchTouchEvent()返回了false,那么ViewGroup#dispatchTouchEvent 方法内部就会去调用super.dispatchTouchEvent方法,这里实际上是去调用父View.dispatchTouchEvent() ,如果控件注册了 OnTouchListener监听,则交给onTouch方法进行处理, 如果onTouch返回true,则事件被消费,直接返回并结束。 如果onTouch方法返回false, 则交给当前这个顶级View(也就是ViewGroup自己)的onTouchEvent来处理,onTouchEvent()返回true,则消费了事件,如果返回了false,则这个事件最终会交给Activity#onTouchEvent来处理。
到此,android 事件机制终于分析完了啊!心好累啊。。。