该文章是一个系列文章,是本人在Android开发的漫漫长途上的一点感想和记录,若是能给各位看官带来一丝启发或者帮助,那真是极好的。java
在上一篇文章中咱们上了一个小例子来自定义View,文章比较简单,阅读量几乎没有,有灌水的嫌疑,(实际上没有,每一篇文章我都是用心在写)。这一篇文章呢,咱们来看一下Android事件的分发机制。关于这方面的知识大概已经被讲烂了。我本人也看了好多关于这方面优质的文章和博客。能够说是受益不浅,可是但是总以为没有掌握彻底。因此我去看了关于底层源码的一些知识。而后在这里分享给你们。网络
当咱们的手指从触摸到屏幕上的各类View开始到这个点击事件结束到底经历了什么,咱们来详细分析一下。(Android的输入系统处理了不少事件,包括按键,触摸,以及外接设备,可是咱们这篇文章只分析咱们最熟悉也是最经常使用的触摸事件,这里的描述也许不太精确,可是却最为直观)
咱们先上一个整体流程图app
注:上图中绿色线条表示默认的事件处理流程,即咱们没有作任何处理,事件会按照绿色线条所示的方向由Activity->...ViewGroup..->View->...ViewGroup..->Activity这个U型图进行传递。即一直默认调用super.XXX方法。ide
上图中黑色线条表示默认Activity->...ViewGroup..->View->...ViewGroup..->Activity这个U型图的任一节点中(不包括onInterceptTouchEvent)返回了true,事件即结束,再也不向下一节点传递。函数
上图中红色线条表示一些特殊状况,尤为是ViewGroup,ViewGroup.onInterceptTouchEvent表示询问当前ViewGroup是否须要拦截此事件即要不要处理,为何要“画蛇添足”呢,由于ViewGroup.dispatchTouchEvent这个函数的特殊,从上图可知,该函数返回true,是消费事件,返回false是交由上一级的ViewGroup或者Activity的onTouchEvent。那么它怎么向下传递事件或者想把事件交给本身的onTouchEvent处理呢,因此ViewGroup多了个onInterceptTouchEvent(View是没有该函数的),onInterceptTouchEvent起到做用的是分流。onInterceptTouchEvent返回false或者返回super.xxx是向下级View或者ViewGroup传递,返回true呢是把事件交给本身的onTouchEvent处理。oop
咱们知道了上图,,可是Activty的事件又是从哪获得的呢,事件最终返回到Activity的onTouchEvent中又作了什么呢。。下面咱们来。。。。。源码分析
咱们知道Android是基于Linux系统的。当输入设备可用时(这里的输入设备包括不少设备,好比触摸屏和键盘是Android最广泛也是最标准的输入设备,另外它还包括外接的游戏手柄、鼠标等),Linux内核会为输入设置建立对应的设备节点。当输入设备不可用时,就把对应的设备节点删除,这也是若是咱们的屏幕意外摔碎了或者其余缘由致使触摸屏幕不可用时触摸没有反应的根本缘由。当咱们的输入设备可用时(咱们这里只来说解触摸屏),咱们对触摸屏进行操做时,Linux就会收到相应的硬件中断,而后将中断加工成原始的输入事件并写入相应的设备节点中。而咱们的Android 输入系统所作的事情归纳起来讲就是**监控这些设备节点,当某个设备节点有数据可读时,将数据读出并进行一系列的翻译加工,而后在全部的窗口中找到合适的事件接收者,并派发给它。布局
注:上述第2第3步与第1步里的处理基本相同,可是须要注意的是Android是串行处理事件的,也就是说按下的动做(ACTION_DOWN|ACTION_POINTER_DOWN)处理完成以前是不会处理后续的ACTION_MOVE|ACTION_POINTER_MOVE和ACTION_UP|ACTION_POINTER_UP事件的。而且后续的ACTION_MOVE|ACTION_POINTER_MOVE和ACTION_UP|ACTION_POINTER_UP事件会根据对ACTION_DOWN|ACTION_POINTER_DOWN事件的不一样而稍有不一样。下面咱们先来分析按下的事件ACTION_DOWN|ACTION_POINTER_DOWN的分发。this
下面咱们来详细分析,请注意,前方高能,请自备纸巾(草稿纸)spa
上面咱们说到了Android 输入系统所作的事情归纳起来讲就是监控设备节点,当某个设备节点有数据可读时,将数据读出并进行一系列的翻译加工,而后在全部的窗口中找到合适的事件接收者,并派发给它。那么它是如何作的呢,,咱们来具体分析一下。Android 的输入系统InputManagerService(如下简称为IMS)做为系统服务,它像其余系统服务同样在SystemServer进程中建立。
Linux会为全部可用的输入设备在/dev/input目录在创建event0~n或者其余名称的设备节点,Android输入系统会监控这些设备节点,具体是经过INotify和Epoll机制来进行监控。而不是经过一个线程进行轮询查询。
咱们先来看一下INotify和Epoll机制(这里咱们只进行简单的描述,读者若是有兴趣能够留言,我单开一篇文章)
INotify是Linux内核提供的一种文件系统变化通知机制。它能够为应用程序监控文件系统的变化,如文件的新建,删除等。
//建立INotify对象,并用描述符inotifyFd 描述它 int inotifyFd = inotify_init(); /* 添加监听 inotify_add_watch函数参数说明 inotifyFd:上面创建的INotify对象的描述符,当监听的目录或文件发生变化时记录在INotify对象 “/dev/input”:被监听的文件或者目录 IN_CREATE | IN_DELETE:事件类型 综合起来下面的代码表示的意思就是当“/dev/input”下发生IN_CREATE | IN_DELETE(建立或者删除)时即把这个事件写入到INotify对象中 */ int wd = inotify_add_watch(inotifyFd, "/dev/input", IN_CREATE|IN_DELETE )
在上述INotify机制中咱们知道了咱们只需关心inotifyFd这个描述符就好了,但是事件是随机发生的,咱们也不会本末倒置的采用轮询的方式轮询这个描述符,由于若是这样作的话会浪费大量系统资源。这时候咱们Linux的另外一个机制就派上用场了,即Epoll机制。Epoll机制简单的说就是使用一次等待来获取多个描述的可读或者可写状态。这样咱们没必要对每个描述符建立独立的线程进行阻塞读取,在避免了资源浪费的同时得到较快的相应速度。
至此原始输入事件已经读取完毕,Android输入系统对原始输入事件进行翻译加工以及派发的详细过程很复杂。咱们这里只分析其中一部分——IMS与窗口。
上文中咱们也说到了IMS会在全部的窗口中找到合适的事件接收者。IMS是运行在SystemServer进程中,而咱们的窗口呢,是在咱们的应用进程中。这就引出了咱们在上文中留下的悬念
// ② 初始化mInputChanel。InputChannel是窗口接收来自InputDispatcher的输入事件的管道。这部份内容咱们将在下一篇介绍。 if ((mWindowAttributes.inputFeatures & WindowManager.LayoutParams.INPUT_FEATURE_NO_INPUT_CHANNEL) == 0) { mInputChannel = new InputChannel(); } ... ... // ③ 若是mInputChannel不为空,则建立mInputEventReceiver用于接收输入事件。 if (mInputChannel != null) { mInputEventReceiver = new WindowInputEventReceiver(mInputChannel, Looper.myLooper()); }
读者看到这里该疑惑了,这个不是在ViewRootImpl.setView方法中说的吗,跟如今讲的有关系吗?且听我娓娓道来。在上几篇博客中咱们介绍了Avtivity,Window,PhoneWindow,以及ViewRootImpl这些概念之间 到底有什么关系呢。
咱们从前几篇中就知道了Activity的启动流程,Activity对象最早建立,可是Activity的显示是依靠其内部对象Window mWindow,而Window是个抽象类,因此mWindow指向的其实是Window的实现类PhoneWindow的对象。PhoneWindow做为显示的载体,ViewRootImpl的measure、layout以及draw才是View显示的动力所在。咱们运行项目,看到了一个MainActivity,咱们点击MainActivity的某个View(如Button了或者其余),实际上咱们是点击了屏幕上的某个点。由IMS对这个原始事件进行翻译加工并找到咱们的PhoneWindow,并向PhoneWindow派发事件。整个过程可用以下流程图表示。
注:上面的流程图中省略了不少细节,意在让读者对Android输入系统有个更总体的把控。
经过上面的流程图咱们知道,当咱们的PhoneWindow建立完成以后,咱们也在该Window上注册了InputChannel并与IMS通讯,IMS把事件写入InputChannel,WindowInputEventReceiver对事件进行处理并最终仍是经过InputChannel反馈给IMS。
下面咱们来稍微介绍下InputChannel和WindowInputEventReceiver。
InputChannel的本质是一对SocketPair(非网络套接字)。套接字能够用于网络通讯,也能够用于本机内的进程通讯。进程间通讯的一种方式,具体解释读者可自行参看《深刻理解Android 卷Ⅲ》》中的5.4.1节。
获得InputChannel后,便用它建立WindowInputEventReceiver,WindowInputEventReceiver继承于InputEventReceiver,InputEventReceiver对象能够接收来自InputChannel的输入事件,并触发其onInputEvent方法的回调。咱们这里的是WindowInputEventReceiver,因此咱们来看一下这个类
final class WindowInputEventReceiver extends InputEventReceiver { public WindowInputEventReceiver(InputChannel inputChannel, Looper looper) { super(inputChannel, looper); } //重写了onInputEvent方法,因此当InputChannel有事件时,会触发 //WindowInputEventReceiver.onInputEvent(),而其内部直接调用enqueueInputEvent @Override public void onInputEvent(InputEvent event) { enqueueInputEvent(event, this, 0, true); } @Override public void onBatchedInputEventPending() { if (mUnbufferedInputDispatch) { super.onBatchedInputEventPending(); } else { scheduleConsumeBatchedInput(); } } @Override public void dispose() { unscheduleConsumeBatchedInput(); super.dispose(); } }
那咱们来看一下enqueueInputEvent
void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) { ... //① 将InputEvent对应的InputEventReceiver封装为一个QueuedInputEvent QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags); //② 将新建的QueuedInputEvent 追加到mPendingInputEventTail所表示的一个单向链表中 QueuedInputEvent last = mPendingInputEventTail; if (last == null) { mPendingInputEventHead = q; mPendingInputEventTail = q; } else { last.mNext = q; mPendingInputEventTail = q; } mPendingInputEventCount += 1; if (processImmediately) { //③ 若是第三个参数为true,则直接在当前线程中开始对输入事件的处理工做 doProcessInputEvents(); } else { //④ 不然将处理事件的请求发送给主线程的Handler,随后进行处理 scheduleProcessInputEvents(); } }
咱们来看doProcessInputEvents
void doProcessInputEvents() { //遍历整个输入事件队列,并逐一处理 while (mPendingInputEventHead != null) { QueuedInputEvent q = mPendingInputEventHead; mPendingInputEventHead = q.mNext; if (mPendingInputEventHead == null) { mPendingInputEventTail = null; } q.mNext = null; mPendingInputEventCount -= 1; Trace.traceCounter(Trace.TRACE_TAG_INPUT, mPendingInputEventQueueLengthCounterName, mPendingInputEventCount); long eventTime = q.mEvent.getEventTimeNano(); long oldestEventTime = eventTime; if (q.mEvent instanceof MotionEvent) { MotionEvent me = (MotionEvent)q.mEvent; if (me.getHistorySize() > 0) { oldestEventTime = me.getHistoricalEventTimeNano(0); } } mChoreographer.mFrameInfo.updateInputEventTime(eventTime, oldestEventTime); //deliverInputEvent()方法会将完成单个事件的整个处理流程 deliverInputEvent(q); } ... }
而deliverInputEvent方法进行一系列调用最终会调用咱们的processPointerEvent()方法
private int processPointerEvent(QueuedInputEvent q) { final MotionEvent event = (MotionEvent)q.mEvent; mAttachInfo.mUnbufferedDispatchRequested = false; mAttachInfo.mHandlingPointerEvent = true; // 此时ViewRootImpl会将事件的处理权移交给View树的根节点,调用dispatchPointerEvent函数 boolean handled = mView.dispatchPointerEvent(event); maybeUpdatePointerIcon(event); maybeUpdateTooltip(event); mAttachInfo.mHandlingPointerEvent = false; if (mAttachInfo.mUnbufferedDispatchRequested && !mUnbufferedInputDispatch) { mUnbufferedInputDispatch = true; if (mConsumeBatchedInputScheduled) { scheduleConsumeBatchedInputImmediately(); } } return handled ? FINISH_HANDLED : FORWARD; }
在processPointerEvent咱们看到ViewRootImpl会将事件的处理权移交给View树的根节点,调用dispatchPointerEvent函数,即mView,而这个mView就是咱们熟知的DecorView
在ActivityThread.handleResumeActivity方法中有以下代码
//decor即DecorView,l是布局参数WindowManager.LayoutParams wm.addView(decor, l);
咱们下面即分析DecorView,咱们打开DecorView源码并无发现dispatchPointerEvent,别着急,别上火,,那么这个dispatchPointerEvent确定在DecorView父类里面了,,咱们打开View源码,,果真找到了,该函数以下
public final boolean dispatchPointerEvent(MotionEvent event) { if (event.isTouchEvent()) { //事件若是是Touch事件,毫无疑问咱们的是啊 return dispatchTouchEvent(event); } else { return dispatchGenericMotionEvent(event); } }
这个时候咱们要去看View.dispatchTouchEvent吗??NO!!!!!咱们应该看DecorView.dispatchTouchEvent(DecorView重写了dispatchTouchEvent)
DecorView.dispatchTouchEvent声明以下
DecorView.java
@Override public boolean dispatchTouchEvent(MotionEvent ev) { /**获取Window.Callback,Window.Callback是个接口,这里的mWindow是PhoneWindow,调用* *PhoneWindow.getCallback(),可是PhoneWindow并无实现该方法,因此咱们找到了 *Window.getCallBack()方法。Window.getCallBack()方法返回Callback类型的mCallback */ final Window.Callback cb = mWindow.getCallback(); /** *若是cb不为空而且window没有被销毁 mFeatureId < 0 表示是application的DecorView, *好比Activity、Dialog把事件传给cb,不然把事件传递给父类的dispatchTouchEvent */ return cb != null && !mWindow.isDestroyed() && mFeatureId < 0 ? cb.dispatchTouchEvent(ev) : super.dispatchTouchEvent(ev); }
Window.java
public final Callback getCallback() { return mCallback; }
Callback.java
public interface Callback { ...//省略一部分函数 public boolean dispatchTouchEvent(MotionEvent event); ... }
咱们来看这个Window.Callback ,既然有getCallback(),那么应该有setCallback为mCallback赋值。咱们
咱们在Activity的attach方法中看到以下代码
Activity.java
final void attach(Context context, ActivityThread aThread, Instrumentation instr, IBinder token, int ident, Application application, Intent intent, ActivityInfo info, CharSequence title, Activity parent, String id, NonConfigurationInstances lastNonConfigurationInstances, Configuration config, String referrer, IVoiceInteractor voiceInteractor, Window window, ActivityConfigCallback activityConfigCallback) { ...... //建立PhoneWindow mWindow = new PhoneWindow(this, window, activityConfigCallback); ...... //设置当前Activity为Window.Callback,那么毫无疑问,Activity类或者其父类实现了Window.Callback接口 mWindow.setCallback(this); ...... }
咱们来看Activity类的声明
果真如此。那么咱们就来看看Activity.dispatchTouchEvent
Activity.java
public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } //在这里咱们又把事件给了PhoneWindow.superDispatchTouchEvent方法根据其返回值, //若返回值为true,那么dispatchTouchEvent返回true,咱们Activity的onTouchEvent方法没法获得执行 if (getWindow().superDispatchTouchEvent(ev)) { return true; } //这里就是咱们的Activity的onTouchEvent方法 return onTouchEvent(ev); }
那咱们要看PhoneWindow.superDispatchTouchEvent
@Override public boolean superDispatchTouchEvent(MotionEvent event) { //兜兜转转一大圈,仍是把事件交给咱们的DecorView, //DecorView继承自FrameLayout,FrameLayout呢又继承自ViewGroup, //因此做为一个ViewGroup,DecorView继续向其子View派发事件,其流程我在文章的开头就已经给了 return mDecor.superDispatchTouchEvent(event); }
总结:兜兜转转一大圈咱们神经都被绕弯了,咱们在这里总结一下,当咱们触摸(点击)屏幕时,Android输入系统IMS经过对事件的加工处理再合适的Window接收者并经过InputChannel向Window派发加工后的事件,并触发InputReceiver的onInputEvent的调用,由此产生后面一系列的调用,把事件派发给整个控件树的根DecorView。而DecorView又上演了一出偷梁换柱的把戏,先把事件交给Activity处理,在Activity中又把事件交还给了咱们的DecorView。自此沿着控件树自上向下依次派发事件。
咱们总算把ACTION_DOWN的事件分发分析完毕了,ACTION_DOWN事件能够说是全部触摸事件的起点。咱们触摸了屏幕,并引起ACTION_DOWN的事件,而后可能通过一系列的ACTION_MOVE事件,最后是ACTION_UP事件,至ACTION_UP,这整个事件序列算是完成了。咱们前面分析了ACTION_DOWN事件,那么ACTION_MOV和ACTION_UP呢,ACTION_MOV和ACTION_UP的事件分发与ACTION_DOWN并不彻底相同。为何这么说呢,是由于他们很类似,可是稍微有些不一样。你在执行ACTION_DOWN的时候返回了false,后面一系列其它的action就不会再获得执行了。简单的说,就是当dispatchTouchEvent在进行事件分发的时候,只有前一个事件(如ACTION_DOWN)返回true,才会收到ACTION_MOVE和ACTION_UP的事件。那么这句话是什么意思呢?咱们来看一下不一样状况下事件派发图。
咱们在ViewGroup1中的dispatchTouchEvent中消费事件!
咱们在ViewGroupX中的dispatchTouchEvent中消费事件!
咱们在View中的dispatchTouchEvent中消费事件!
咱们在View中的onTouchEvent中消费事件!
特殊状况1 :咱们在ViewGroupX中的onTouchEvent中消费事件!
特殊状况2 :咱们在ViewGroupX中的dispatchTouchEvent中返回false并在ViewGroup1中的onTouchEvent中消费事件!
还有种种状况我就不画图了。。为何会产生上面的结果呢?咱们仍是来看一下ViewGroup的dispatchTouchEvent源码把。
@Override public boolean dispatchTouchEvent(MotionEvent ev) { ...... boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { //表示窗口是否为模糊窗口(FILTER_TOUCHES_WHEN_OBSCURED), //若是是窗口,则表示不但愿处理改事件。(如dialog后的窗口) if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; /** 第①步 从新设置状态 开始*/ // 处理初始的按下动做 if (actionMasked == MotionEvent.ACTION_DOWN) { //从新设置状态等,比较重要的是设置mFirstTouchTarget == null, cancelAndClearTouchTargets(ev); resetTouchState(); } /** 第①步 从新设置状态 结束*/ /** 第②步 检查是否拦截 开始*/ // 检查是否拦截 final boolean intercepted; if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {//若是是ACTION_DOWN事件或者mFirstTouchTarget != null //这里咱们去问ViewGroup是否容许拦截,若是容许拦截,咱们再去问onInterceptTouchEvent ...... } else { //若是不是MotionEvent.ACTION_DOWN事件而且mFirstTouchTarget 为空,直接拦截 intercepted = true; } /** 第②步 检查是否拦截 结束*/ ...... /** 第③步 向子View派发 开始*/ if (!canceled && !intercepted) {//若是没有取消而且当前ViewGroup没有拦截事件 ...... if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { //判断事件类型,若是是ACTION_DOWN或者ACTION_POINTER_DOWN或者ACTION_HOVER_MOVE则进入 if (newTouchTarget == null && childrenCount != 0) { ...... ...... //获取子View并循环向子View派发事件 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { //若是当前ViewGroup的子View消费了事件,则进入if体 ...... //赋值newTouchTarget和mFirstTouchTarget newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break; } } ...... } } /** 第③步 向子View派发 结束*/ /** 第④步 额外的处理 开始*/ // Dispatch to touch targets. if (mFirstTouchTarget == null) { /**这个判断十分重要: * *咱们在上面的过程当中就知道假若咱们没有拦截即intercepted = false;若是事件是ACTION_DOWN *或者ACTION_POINTER_DOWN或者ACTION_HOVER_MOVE咱们会进入循环子View并派发事件的过程, *若是子View也不想处理该事件即dispatchTransformedTouchEvent()函数返回了false,那么此时ViewGroup的mFirstTouchTarget == null *假若咱们重写了onInterceptTouchEvent并返回true,那么intercepted = true即进行拦截, *那么就不会进入咱们的第③步,直接来到第④步,这时当前ViewGroup的mFirstTouchTarget == null mFirstTouchTarget == null的条件下会调用dispatchTransformedTouchEvent */ handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else { final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } /** 第④步 额外的处理 结束*/ ...... return handled; }
咱们从上面的代码能够更清晰的了解到ACTION_DOWN的派发过程,如今还存疑的就是这个mFirstTouchTarget了,咱们在触发ACTION_DOWN的时候,ViewGroup会根据事件掩码actionMask判断ACTION_DOWN,并重置一些状态,重置状态的过程当中就包括把mFirstTouchTarget设为null,咱们第一次进入第三步时找到合适的子View并向其派发事件,若是子View消费了ACTION_DOWN事件,则调用addTouchTarget进行赋值,咱们来看一下这个函数
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) { final TouchTarget target = TouchTarget.obtain(child, pointerIdBits); target.next = mFirstTouchTarget; //在这里咱们能够看到mFirstTouchTarget 指向了子View mFirstTouchTarget = target; return target; }
有上面的代码可知mFirstTouchTarget是ViewGroup的一个成员变量,每个ViewGroup都持有这个mFirstTouchTarget。
这个mFirstTouchTarget是个单向链表,表示的是当前ViewGroup的子View有没有消费ACTION_DOWN事件,若是消费了ACTION_DOWN事件,就如上面代码中第③步的时候描述的同样给mFirstTouchTarget赋值,若是当前ViewGroup的子View没有消费ACTION_DOWN事件,即把事件分发给子View的这个dispatchTransformedTouchEvent()函数返回了false,不进入if体,mFirstTouchTarget仍是为null。
咱们接着来看第④步,结合上图中的特殊状况1,咱们在ViewGroupX中的onTouchEvent中消费了事件。那么对于ViewGroupX来讲,它的mFirstTouchTarget==null,由于它的子View并无消费事件,对于ViewGroup1来讲它的mFirstTouchTarget != null,由于它的子View ViewGroupX消费了事件,以此类推最后获得的mFirstTouchTarget 链表相似于下图
因为ACTION_MOVE|ACTION_UP事件不符合第③步时进入获取子View并循环派发的条件,当是ACTION_MOVE|ACTION_UP事件会直接来到第④步,判断当前ViewGroup的mFirstTouchTarget 是否为空,由上图可知不为空,那么进入第④步else体,在第④步else体内依据下图的链表逐一贯子View派发事件。因此ACTION_MOVE|ACTION_UP事件只派发到ViewGroupX并交由ViewGroupX的onTouchEvent处理,再也不向下派发。
那咱们再来看一下mFirstTouchTarget == null的条件下调用的dispatchTransformedTouchEvent函数
参数分别是ev, canceled, null, TouchTarget.ALL_POINTER_IDS
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { //child为空调用父类即View的dispatchTouchEvent handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } final int oldPointerIdBits = event.getPointerIdBits(); final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits; if (newPointerIdBits == 0) { return false; } final MotionEvent transformedEvent; if (newPointerIdBits == oldPointerIdBits) { if (child == null || child.hasIdentityMatrix()) { if (child == null) { //child为空调用父类即View的dispatchTouchEvent 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; } transformedEvent = MotionEvent.obtain(event); } else { transformedEvent = event.split(newPointerIdBits); } if (child == null) { //child为空调用父类即View的dispatchTouchEvent handled = super.dispatchTouchEvent(transformedEvent); } else { final float offsetX = mScrollX - child.mLeft; final float offsetY = mScrollY - child.mTop; transformedEvent.offsetLocation(offsetX, offsetY); if (! child.hasIdentityMatrix()) { transformedEvent.transform(child.getInverseMatrix()); } handled = child.dispatchTouchEvent(transformedEvent); } // Done. transformedEvent.recycle(); return handled; }
上面的函数咱们就不仔细分析了,不过注释里写的很明白,只要流程正常的话,咱们都会调用父类的dispatchTouchEvent
咱们来看一下View的dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
......
if (onFilterTouchEventForSecurity(event)) { ...... ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) {//这里判断有没有为View是否可用Enabled而且检查是否设置了TouchListener,若是设置了,则触发TouchListener的onTouch result = true; } if (!result && onTouchEvent(event)) {//若是当前View没有设置listener信息,事件也没有被滚动条消费这里回调了咱们的onTouchEvent。因此若是为当前View设置了TouchListenerb并在TouchListener的onTouch函数中返回了true,那么,该View的onTouchEvent将没法获得回调。 result = true; } } ...... return result; }
本篇文章详细分析了View的事件体系(写这一篇文章真是不容易啊)。做为全部触摸事件的起点ACTION_DOWN|ACTION_POINTER_DOWN来讲,Android对其的处理很精细,尤为是ViewGroup对其的处理。
首先重置状态,这是由于一个新的事件序列开始了,重置状态中比较重要的就是这个mFirstTouchTarget了,mFirstTouchTarget做为ViewGroup的成员变量记录当前ViewGroup下的子View是否消费了该ACTION_DOWN|ACTION_POINTER_DOWN事件。这个子View的意思也不只仅是直接子View。假若有这样一个结构
<ViewGroup1>
<ViewGroup2> <ViewGroup3> <ViewGroup4> <View> </View> </ViewGroup4> </ViewGroup3> </ViewGroup2>
</ViewGroup1>
假设是View消费了ACTION_DOWN|ACTION_POINTER_DOWN事件,那么ViewGroup1的mFirstTouchTarget就是ViewGroup2->ViewGroup3->ViewGroup4->View
但愿读者能多看几遍上面的分析。相信你必定会有收获的
在下一篇文章中咱们将进行实战项目,也是对咱们前几篇文章的实际应用。老话说的好,纸上得来终觉浅,绝知此事要躬行。下一篇甚至几篇咱们就来自定义ViewGroup并重点探讨滑动冲突如何解决。滑动冲突解决的基础是今天这篇的View事件体系
此致,敬礼