学习笔记-浅析事件分发

1、一次触摸是如何发生的?

当用户触摸屏幕或者按键操做,首次触发的是硬件驱动,驱动收到事件后,将该相应事件写入到输入设备节点交由内核处理, 产生最原生态的内核事件。内核会将触摸事件包装成EVENT做为文件存储到"/dev/input/event[x]"目录中。java

而后,InputReaderThread会不断的从"/dev/input/event[x]"目录中读取事件,并会把事件交给InputDispatch处理。InputDispatch会把事件分发到须要的地方。android

以上的这些步骤都是C/C++代码实现,只须要对流程作一些简单的了解便可。而真正java开始拿到事件的起点是在ViewRootImpl的内部类WindowInputEventReceiver之中。git

WindowInputEventReceiver.onInputEvent()经过InputDispatch调用接受到事件以后,首先将事件传给了enqueueInputEvent(),将新接受的事件插入到事件队列中,接着调用doProcessInputEvents()缓存

class ViewRootImpl {
    
    ...
    final class WindowInputEventReceiver extends InputEventReceiver {
        ...
        @Override
        public void onInputEvent(InputEvent event, int displayId) {
            enqueueInputEvent(event, this, 0, true);
        }
    }

    void enqueueInputEvent(InputEvent event, InputEventReceiver receiver, int flags, boolean processImmediately) {
       ...
        QueuedInputEvent q = obtainQueuedInputEvent(event, receiver, flags);
        QueuedInputEvent last = mPendingInputEventTail;
        
        last.mNext = q;
       	...
        doProcessInputEvents();
    }
}
复制代码

doProcessInputEvents()循环着取出全部的事件,按照顺序将事件交给deliverInputEvent(),而它的工做就是将事件发送到InputStage的表头中。安全

void doProcessInputEvents() {
    ...
    while (mPendingInputEventHead != null) {
        QueuedInputEvent q = mPendingInputEventHead;
        mPendingInputEventHead = q.mNext;
        ...
        
        deliverInputEvent(q);
    }
}



private void deliverInputEvent(QueuedInputEvent q) {
    ...
    InputStage stage;
    if (q.shouldSendToSynthesizer()) {
        stage = mSyntheticInputStage;
    } else {
        stage = q.shouldSkipIme() ? mFirstPostImeInputStage : mFirstInputStage;
    }
    stage.deliver(q);
    ...
}
复制代码

InputStage是一个单向链表结构,这个链表中有一系列的InputStage,他们都能对事件作出立,事件就在它们上面传递,若是事件没有被前面的InputStage标记Finished,当前的InputStage就会尝试消费它。其中ViewPostImeInputStage就会将事件发送到View处理处理。markdown

ViewPostImeInputStage.onProcess()拿到事件后,交给ProcessPointerEvent(),这就调用到mView.dispatchPointerEvent(),而ViewRootImpl.mView众所周知,也就是DecorView,也就是终于走到了View中进行处理了!(这里还有疑问的翻阅一下上一篇文章)。ide

final class ViewPostImeInputStage extends InputStage {
    ...
    @Override
    protected int onProcess(QueuedInputEvent q) {
        ...
        return processPointerEvent(q);
    }


    private int processPointerEvent(QueuedInputEvent q) {
        final MotionEvent event = (MotionEvent)q.mEvent;
        ...
        boolean handled = mView.dispatchPointerEvent(event);
        ...
        return handled ? FINISH_HANDLED : FORWARD;
    }
}
复制代码

首先ViewRootImpl将事件传递到DecorView.dispatchPointerEvent()DecorView没有实现这个方法,实际调用View.dispatchPointerEvent(),而它又调用了DecorView.dispatchTouchEvent()将事件传回DecorView函数

紧接着DecorView经过Window.getCallback()将事件传递到了Acitivity.dispatchTouchEvent()oop

Activity又将事件又传给了PhoneWindowPhoneWindow将事件直接透传回DecorView,最终调用到super.dispatchTouchEvent()也就是ViewGroup.dispatchTouchEvent()post

///View
public final boolean dispatchPointerEvent(MotionEvent event) {
    ...
    return dispatchTouchEvent(event);
}

///DecorView
public boolean dispatchTouchEvent(MotionEvent ev) {
    final Window.Callback cb = mWindow.getCallback();
    ...
    return  cb.dispatchTouchEvent(ev);
}

///Activity
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    return onTouchEvent(ev);
}

///PhoneWindow
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

///DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
    return super.dispatchTouchEvent(event);
}
复制代码

这一步看起来会有点疑惑行为的感受:

第一点,为何DecorView要把事件传递给Activity而后又传递回来?

Activity.dispatchTouchEvent中首先将事件又交给View树,若是没有被消费,Activity就能够拿到这个事件并作处理,官方注释写到在其中能够处理在窗口范围外发生的触摸事件效果最佳。如此,就能够理解为,Activity但愿作事件分发的起点,因此须要传给Activity由它来启动。

第二个问题,传递的目标是交给Activity而后再传递给DecorView,为何ViewRootImpl将事件传给了DecorView而不是直接传递给Activity?而Activity为何经过Window去传递?并且PhoneWindow中就只是将事件直接透传给DecorView

这个问题在上一篇文章就讨论过,这样设计的目的是单一原则以及解耦,ViewRootImpl并不知道Activity的存在,它只有DecorView的对象,同理Activity并不知道DecorView的存在,它只有Window的对象,上一次说到Window存在的意义就是承载和处理视图,Activity并不关心它内部是怎样实现的,只须要将事件交给他就好了,另外一方面,这几个类尽可能最少的持有对方,最大程度上的实现解耦,而解耦是为了什么,就么有必要再累赘了。

2、事件如何完成分发?

上一节分析到,事件被分发到了DecorView,而且调用了super.dispatchTouchEvent(),也就是ViewGroup.dispatchTouchEvent()。这一节主要分析一下ViewGroup.dispatchTouchEvent()都作了那些事情,又是如何完成了事件的向下传递。

2.1. ViewGroup.dispatchTouchEvent()

ViewGroup.dispatchTouchEvent()比较长,将这个方法大体拆分为三个步骤来进行分析:1. 拦截 2. 寻找分发目标 3. 分发

首先先了解一下流程中十分重要的两个概念:

  • PointerId:触摸点Id,多指操做时,每根手指从按下、移动到离开屏幕,都会拥有一个固定PointerId与对应的手指进行绑定。Id的范围是0..31,官方目前假设这是安全的,由于底层的管道也是这样设计的。

    32个数字范围的优势就是能够用32位的int来存储一组pointerId,而且方便去计算和查询。

  • TouchTarget:缓存触摸的child以及它所消费的触摸点Id集合,也就表示事件派发目标。

    TouchTarget是一个单向链表结构,ViewGroup经过mFirstTouchTarget持有表头。

    TouchTarget自身维护了一个复用池,池子的容量也是32个。

2.1.1. 拦截

分发最首先的操做是看本身是否要拦截这一次事件,若是自身选择了拦截,事件就再也不下发,而是交给本身处理。

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    ///消费标志
    boolean handled = false;
    ///onFilterTouchEventForSecurity():安全检查
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        ///若是是ACTION_DOWN,就表示一组新的事件的开始,清除旧的TouchTarget以及重置滚动状态
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 拦截标志
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            ///判断是否容许进行拦截
            ///子view调用parent.requestDisallowInterceptTouchEvent()影响的就是这里
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                ///onInterceptTouchEvent():拦截的方法,子类通常经过实现这个方法来进行事件拦截
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action);
            } else {
                intercepted = false;
            }
        } else {
            ///不是ACTION_DOWN而且没有touchTarget
            ///也就表示已经以前全部的view都接受到以前ACTION_DOWN事件,但都没有消费
            ///也就是没有视图会愿意接受这个事件,直接拦截掉
            intercepted = true;
        }

        ...

    }
}
复制代码

子view经过调用parent.requestDisallowInterceptTouchEvent()阻止parent拦截事件分析一下前置条件actionMasked == MotionEvent.ACTION_DOWNmFirstTouchTarget != null

  1. 当第一个条件成立,便是一次ACTION_DOWN事件,那么在执行判断以前就会先执行clear操做,不只清除了TouchTarget,也清除了mGroupFlags标志位,而又能够看到阻止拦截就是经过mGroupFlags标志,因此child设置的拦截标志直接被重置掉了。也就是说当是ACTION_DOWN事件的时候,子view的阻止拦截会直接失效,还能够说,child阻止parent拦截最多到下一次ACTION_DOWN事件的时候。
  2. 当第二个条件成立,mFirstTouchTarget不为空,而且不是ACTION_DOWN事件,这个时候才会根据标志判断是否走拦截的方法。

整体分析,若是想要进行拦截,首先就是child要拿到拿到ACTION_DOWN或者稍后的事件,这个时候再调用parent.requestDisallowInterceptTouchEvent()更改标志位来阻止parent拦截这个ACTION_DOWN同一组的事件事件。

2.1.2. 寻找分发目标

当事件不是ACTION_CANCEL而且没有被拦截,并且必须是一个DOWN事件,才会去寻找派发的目标View。首先若是是ACTION_CANCEL或者被拦截,事件都没有了继续传递的必要,其次,DOWN之类的事件标记着一组事件的起始,若是想要对一组事件进行处理,必须在DOWN产生的时候就拿到并处理过它,而这种状况就已经存在了TouchTarget,也是没有查找派发目标的必要。

其次就是遍历全部的子view,寻找符合条件的view。而遍历时的顺序首先遵循Z轴的顺序,也就是视觉上的从前面到后面,其次是遵循绘制顺序,最后绘制的优先级最高,这样在视图有重叠的时候,也是视觉上的从前面到后面。

遍历的时候,须要根据多个条件来筛选判断当前view是否会接受这个事件,筛选的前后顺序:

  1. view是否可见或者在是否在播放动画,而播放动画能够理解为,在未来某一个时刻可能会可见。
  2. 触摸点是否命中到view的范围内。
  3. 查找TouchTarget链表是否已经有了childTouchTarget,若是有了只须要将触摸点Id存入这个TouchTarget,而后跳出循环,走后续的派发流程就可。这种状况会发生在多指操做的时候,第一根手指落在一个view上面以后,第二根手指也落在这个view上面,就只须要继续讲这个事件发送给这个view便可。
  4. 将事件直接派发给当前child去处理,即将事件向下传递,若是当前child或者它的child处理了这个事件,那么当前这个child就是派发目标,新建一个TouchTarget存入到TouchTarget链表的头部。

当遍历完全部的child没有找到派发的目标,而且TouchTarget链表不是空的,那么就会将最先添加的TouchTarget做为派发目标。

public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    ///消费标志
    boolean handled = false;
    ///onFilterTouchEventForSecurity():安全检查
    if (onFilterTouchEventForSecurity(ev)) {
        ...

        ///ACTION_CANCEL事件标志
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        ///分裂标志(是否支持多指操做)
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;///已经分配给新的TouchTarget标志

        ///1. 不是取消而且没被拦截才进行派发
        ///2. 是一个DOWN事件(ACTION_HOVER_MOVE是指针悬浮在view上)
        /// 表示是一个新的事件序列开始了,须要从新找Target
        if (!canceled && !intercepted) {
            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
                ///触摸点ID转换为32位标志
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                        : TouchTarget.ALL_POINTER_IDS;

                ///清除TouchTarget列表中的相同的触摸点Id,由于是一个新的事件队列,以前的就已经失效了
                removePointersFromTouchTargets(idBitsToAssign);

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    ///构建一个子view的列表在原有的顺序基础上,优先根据z轴排序
                    ///这个列表会在全部子view都没有z轴返回空值
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    ///view是否按规定正常绘制的标志
                    final boolean customOrder = preorderedList == null
                            && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        ///preorderedList可能为null,经过这两个方法获的实际的index和child
                        final int childIndex = getAndVerifyPreorderedIndex(
                                childrenCount, i, customOrder);
                        final View child = getAndVerifyPreorderedView(
                                preorderedList, children, childIndex);

                        ...

                        ///判断是否要将事件发给child
                        ///canViewReceivePointerEvents():子类是否能够接收,条件是view可见 或者 正在播放动画
                        ///isTransformedTouchPointInView():触摸点是否命中view范围
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }


                        ///查询已有的TouchTarget列表中是否已经有该child的TouchTarget
                        ///若是有的话只须要给触摸点Id集合添加新的Id便可
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        ///重置child的detached标志
                        ///(child未将事件处理完成就detached,就会将后续的事件设为CANCEL)
                        resetCancelNextUpFlag(child);

                        ///dispatchTransformedTouchEvent():将事件向下传递交给view去处理
                        ///返回true就是view成功消费了这个事件
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            ///若是返回了true,更新一下相关数据,中止循环
                            ///而且建立一个TouchTarget,插入到mFirstTouchTarget前面做为表头
                            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();
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;///已经派发了事件的标志
                            break;
                        }
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                ///若是没有找到派发的目标,可是存在旧的TouchTarget
                ///将事件派发给最先添加的Target
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        ...

    }
}
复制代码

须要注意的是,当是一次ACTION_DOWN事件的时候,大几率在这个过程当中并不会存在TouchTarget,那么在查找派发目标的过程当中,就已经完成了派发的动做,也就是说这个事件到这里已经完成了它的分发流程。

2.1.3. 分发

分发的逻辑比较清晰,遍历TouchTraget,尝试给它分发事件。有几个比较特殊的状况:

  1. TouchTarget链表为空,就直接将事件发送给本身处理。
  2. 在寻找派发目标的时候,已经完成了派发,就不须要再执行派发。
  3. 若是事件被拦截了,给全部的TouchTarget发送取消事件。
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...

    ///消费标志
    boolean handled = false;
    ///onFilterTouchEventForSecurity():安全检查
    if (onFilterTouchEventForSecurity(ev)) {
        ...

        if (mFirstTouchTarget == null) {
            ///没找到派发目标就发给本身来处理
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            ///给TouchTarget派发事件
            while (target != null) {
                final TouchTarget next = target.next;
                if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                    ///已经在上一个步骤派发过
                    handled = true;
                } else {
                    ///派发给对应的TouchTarget
                    ///若是拦截了事件,就会给全部的TouchTarget发送CANCEL事件
                    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;
            }
        }

        /// 当是取消事件或者是ACTION_UP之类的事件,执行清除的操做。
        if (canceled
                || actionMasked == MotionEvent.ACTION_UP
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            resetTouchState();
        } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
            final int actionIndex = ev.getActionIndex();
            final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
            removePointersFromTouchTargets(idBitsToRemove);
        }
    }
    return handled;
}
复制代码

这里发觉到的一个比较有意思的逻辑是,事件按理说应该只交给一个child去处理,为何要给全部的TouchTarget分发?奥秘就在TouchTarget存储的触摸点Id集合中,在查找分发目标的过程当中,首先的步骤就是清除了TouchTarget链表中相同的触摸点Id,而分发的函数dispatchTransformedTouchEvent()中,又会根据Id集合是否包含当前事件的触摸点Id去选择是否下发,因此实际上只将事件发送到了惟一的持有当前事件触摸点Id的TouchTarget上面。而对于拦截了的状况,就会给全部的TouchTarget发送取消事件而且清除掉TouchTarget

2.2. ViewGroup.dispatchTransformedTouchEvent()

在上一节中,时常会提到这个方法ViewGroup.dispatchTransformedTouchEvent(),当肯定了分发目标的时候,就会调用这个方法进行分发,作一些预处理以后进行分发。主要的步骤:

  1. ACTION_CANCEL事件直接下发。
  2. 检查事件的触摸点Id和传入的触摸点Id是否有交集,也就是上一节讨论的为何给全部的TouchTarget分发的问题。若是没有交集,说明这个TouchTarget不是分发目标,直接返回。
  3. 根据事件的触摸点Id集合和上一步获得的交集是否有差别,作一些分裂处理。当二者相同,直接复制一份便可,当量者不相同,只有多是事件的集合比交集的集合多,因此对事件进行分裂。这种状况发生在多指操做时,事件中包含了多个触摸点Id的信息,但当前分发的TouchTarget表示这个child只接受其中的一部分,也就是,多个手指落在了不一样的view上面,须要拆开进行分发。
  4. 将坐标轴偏移到child的坐标轴上面,而后进行下发。
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) {
            handled = super.dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }

    ///触摸点id过滤
    final int oldPointerIdBits = event.getPointerIdBits();
    final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
    ///取和等与0,没有交集,不须要下发,直接返回
    if (newPointerIdBits == 0) {
        return false;
    }

    ///根据状况,对事件进行必定的分裂
    final MotionEvent transformedEvent;
    if (newPointerIdBits == oldPointerIdBits) {
        if (child == null || child.hasIdentityMatrix()) {
            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);

                event.offsetLocation(-offsetX, -offsetY);
            }
            return handled;
        }
        transformedEvent = MotionEvent.obtain(event);
    } else {
        ///当触摸点id不相等的时候,须要对事件进行过滤
        ///只拿到须要下发的触摸点id的事件进行下发
        transformedEvent = event.split(newPointerIdBits);
    }

    ///分发
    if (child == null) {
        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);
    }

    transformedEvent.recycle();
    return handled;
}
复制代码

这个方法中,有不少的child == null判断,而后调用super.dispatchTouchEvent(),而这时候ViewGroup执行super.dispatchTouchEvent()调用的是View.dispatchTouchEvent(),也就是事件交给了本身来处理。

认真看代码还会发现一个奇怪的事情,在分裂的时候,有一段和后面分发彻底一致的代码,也就是在分裂直接进行了分发。为何重复的代码要写两遍?

分析一下走第一部分不走第二部分的条件:newPointerIdBits == oldPointerIdBits

child.hasIdentityMatrix()==true(变换矩阵是单位矩阵,也就是没有变化)

简单分析不考虑多指操做不一样的view,也就是newPointerIdBits == oldPointerIdBits的状况下, 剩下的条件就只有child.hasIdentityMatrix()

没有变换矩阵走第一段代码,有变换矩阵走第二段代码,两段代码的区别也就是后面的那段代码使用的是复制的MotionEvent,进行了矩阵映射,而且使用结束了就直接回收了;而前面的那段代码是直接使用的传入的MotionEvent,使用后进行了坐标系的复原。

而这就体现出两端代码的区别:若是有矩阵映射的状况下,第一段代码会多一个反映射的复原操做,而第二段代码多的是一个MotionEvent建立销毁的操做。我猜想,这可能就是在变换矩阵反映射和MotionEvent建立销毁之间取得的一点性能上得优化吧。

2.3. View.dispatchTouchEvent()

走到这也就到了一次触摸事件分发的尾声了,当View.dispatchTouchEvent()执行的时候,只须要将事件分发给自身处理便可,自身又不止一个处理方法,因此也就会有个优先级来决定前后顺序:

  1. 事件首先交给滚动条去处理,判断是不是拖动滚动条的事件。
  2. 事件交给onTouchListener处理,也就是经过外部设置的监听。
  3. 最后若是前两个步骤都没有消费掉事件,才会交给自身onTouchEvent()处理。
public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;
    final int actionMasked = event.getActionMasked();
    if (actionMasked == MotionEvent.ACTION_DOWN) {
        stopNestedScroll();
    }

    if (onFilterTouchEventForSecurity(event)) {
        ///滚动条处理
        if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
            result = true;
        }
        ///onTouchListener处理
        ListenerInfo li = mListenerInfo;
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        ///onTouchEvent()处理
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    ...
    if (actionMasked == MotionEvent.ACTION_UP ||
            actionMasked == MotionEvent.ACTION_CANCEL ||
            (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
        stopNestedScroll();
    }

    return result;
}
复制代码

写在最后

此次尝试将手指触摸到屏幕上到控件获取到此次触摸的流程作一次完整的剖析,没有想到工做量居然如此大,文章长度达到了5000词。收获颇丰,不只学到了不少源码设计的巧妙之处,同时是源码的阅读能力仍是事件分发的理解,都上了一个大台阶。

入行浅,起步要稳,一步一个脚印,作大作强,再创辉煌。

参考文章

ViewGroup事件分发总结-TouchTarget

原来Android触控机制竟是这样的?

Android Framework 输入子系统 (09)InputStage解读

Android事件分发机制

相关文章
相关标签/搜索