事件分发机制

作者: 小川君 | 来源:发表于2018-11-23 19:28 被阅读592次

    事件的产生是由用户触摸屏幕而产生的触摸事件
    事件的传递过程是由Activity -> ViewGroup -> View ->ViewGoup ->Activity;其中Activity ->ViewGoup ->View是一个传递分发的过程,是比较重要的一个过程;而View -> ViewGroup -> Activity
    当用户触摸屏幕的时候最先接收到的Activity,然后再一级一级的向下分发

    // Activity#dispatchTouchEvent
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                // 当用户触摸屏幕时会触发这个回调,但却不能进行拦截,只是在第一时间告诉Activity
                // 产生了事件,并且这个事件是一个ACTION_DOWN事件,可以做些其他的事情
                onUserInteraction();
            }
            if (getWindow().superDispatchTouchEvent(ev)) {
                // 如果这个判断成立,表示这个事件被消费掉了,最终返回了true,
                // 代表事件不会再向下传递了,事件结束
                return true;
            }
            return onTouchEvent(ev);
        }
    

    getWindow获取到的是PhoneWindow对象
    PhoneWindow#superDispatchTouchEvent -> DecorView#superDispatchTouchEvent -> ViewGroup#dispatchTouchEvent

    // ViewGroup#dispatchTouchEvent
     @Override
        public boolean dispatchTouchEvent(MotionEvent ev) {
    
            boolean handled = false;
            // 如果这里返回true标识可以接收触摸事件,反之则代表不可接收
            if (onFilterTouchEventForSecurity(ev)) {
                final int action = ev.getAction();
                final int actionMasked = action & MotionEvent.ACTION_MASK;
    
                // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // 有新事件产生事,要清空原先的事件,并重置触摸状态
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    
                // Check for interception.
                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 {
                    intercepted = true;
                }
    
                // Check for cancelation.
                final boolean canceled = resetCancelNextUpFlag(this)
                        || actionMasked == MotionEvent.ACTION_CANCEL;
    
                // Update list of touch targets for pointer down, if needed.
                final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
                TouchTarget newTouchTarget = null;
                boolean alreadyDispatchedToNewTouchTarget = 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 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的子view进行排序,顺序从上往下,如果两个view的位置重合
                            // 则根据z判断
                            final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                            final boolean customOrder = preorderedList == null
                                    && isChildrenDrawingOrderEnabled();
                            final View[] children = mChildren;
                            //倒序遍历子view
                            for (int i = childrenCount - 1; i >= 0; i--) {
                                // 获取到当前view的绘制顺序的下标
                                final int childIndex = getAndVerifyPreorderedIndex(
                                        childrenCount, i, customOrder);
                                // 获取到子view对象
                                final View child = getAndVerifyPreorderedView(
                                        preorderedList, children, childIndex);
                                // 如果子view不能接收触摸事件或者触摸点所在的位置
                                // 不在当前子view中则终止本次循环,开始下一次循环
                                if (!canViewReceivePointerEvents(child)
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                                    ev.setTargetAccessibilityFocus(false);
                                    continue;
                                }
                                // 获取到子view的事件链
                                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是否对事件进行响应
                                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                 
                                    mLastTouchDownX = ev.getX();
                                    mLastTouchDownY = ev.getY();
                                    // 如果响应 则将子view添加进事件链,并结束事件的传递
                                    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();
                        }
    
                        if (newTouchTarget == null && mFirstTouchTarget != null) {
                            // Did not find a child to receive the event.
                            // Assign the pointer to the least recently added target.
                            newTouchTarget = mFirstTouchTarget;
                            while (newTouchTarget.next != null) {
                                newTouchTarget = newTouchTarget.next;
                            }
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                        }
                    }
                }
    
                // Dispatch to touch targets.
                if (mFirstTouchTarget == null) {
                    // No touch targets so treat this as an ordinary view.
                    //根据上面 如果找到了可以接收事件的view则会添加进事件链,
                    // 为空的话说明所有的子view都不接受处理事件或者触摸点不在
                    // 任何子view内或当前view将事件拦截了
                    // 只能调用super.dispatchTouchEvent()  也就是View.dispatchTouchEvent()
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
                } else {
                    // Dispatch to touch targets, excluding the new touch target if we already
                    // dispatched to it.  Cancel touch targets if necessary.
                    TouchTarget predecessor = null;
                    // 获取到事件链的头结点
                    TouchTarget target = mFirstTouchTarget;
                    // 遍历事件链找到处理事件的view
                    while (target != null) {
                        final TouchTarget next = target.next;
                        // 如果当前view的子view中有消费事件的view并且事件链的头结点等于尾节点
                        // 设置handled  = true
                        if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                            handled = true;
                        } else {
                            // 一般到else的都是 target != newTouchTarget
                            final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                    || intercepted;
                            // 然后判断当前子view是否消费事件,如果消费则设置handled = true
                            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;
        }
    
    

    ViewGroup#dispatchTouchEvent中首先判断当前view是否会拦截事件,如果拦截则会返回true,表示事件不再继续向下传递,如果不拦截,则会获取到当前view中所有的子view,并按照绘制顺序进行排序,筛选掉不再触摸点的view,然后通过dispatchTransformedTouchEvent调用子view的dispatchTouchEvent判断子view是否对事件进行消费拦截,将消费事件的view则加入到事件链中,最后遍历事件链,再次调用dispatchTransformedTouchEvent。

    事件链:将能够处理事件的view以及事件id保存到TouchTarget中,而TouchTarget则是一个单链表,这样在处理事件的时候只需要遍历这个单链表就可以拿到所有可以处理此事件的view
    onInterceptTouchEvent
    dispatchTransformedTouchEvent

    onInterceptTouchEvent容易理解,表示是否拦截,true表示拦截,事件不再下发;而false和super.xx()则表示事件可以继续向下分发,另外onInterceptTouchEvent只是在ViewGroup中存在,View和Activity中并没有;我们来看dispatchTransformedTouchEvent

    // ViewGroup#dispatchTransformedTouchEvent
        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;
            }
            // Calculate the number of pointers to deliver.
            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) {
                        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);
            }
    
            // Perform any necessary transformations and dispatch.
            if (child == null) {
                // 子view为空有两种情况,点击区域内没有子view或者没有子view可以接受事件
                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;
        }
    
    

    该方法的大致意思就是将事件下发到子view的dispatchTouchEvent中,如果子view为空就传递到父类的dispatchTouchEvent中,也就是View.dispatchTouchEvent

    // View#dispatchTouchEvent
      public boolean dispatchTouchEvent(MotionEvent event) {
       
            boolean result = false;
            // 如果当前view没有被遮挡则返回true
            if (onFilterTouchEventForSecurity(event)) {
                // handleScrollBarDragging 滚动条是否消费了事件,如果消费了话则result = true
                // 而下面的onTouchEvent也就不会执行
                if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                    result = true;
                }
                //noinspection SimplifiableIfStatement
                ListenerInfo li = mListenerInfo;
                // 如果设置了onTouchListener事件监听并且返回了true,那么这里 result = true
                // 下面的onTouch同样不会执行
                if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }
    
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
    
            if (!result && mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
            }
    
            // Clean up after nested scrolls if this is the end of a gesture;
            // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
            // of the gesture.
            if (actionMasked == MotionEvent.ACTION_UP ||
                    actionMasked == MotionEvent.ACTION_CANCEL ||
                    (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
                stopNestedScroll();
            }
    
            return result;
        }
    
    

    上面经过一系列的判断就是判断能不能调用onTouchEvent(),当前我们还在ViewGroup中,但是ViewGroup本身并没有实现onTouchEvent,

    // View#onTouchEvent
      public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
    
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
            // TouchDelegate可以增大触摸范围,这里不多说
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
    
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
              
                        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                     
                            boolean focusTaken = false;
                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                                focusTaken = requestFocus();
                            }
    
                            if (prepressed) {
                             
                                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)) {
                                        // 这里调用了onClick回调
                                        performClick();
                                    }
                                }
                            }
    
                            if (mUnsetPressedState == null) {
                                mUnsetPressedState = new UnsetPressedState();
                            }
    
                            if (prepressed) {
                                postDelayed(mUnsetPressedState,
                                        ViewConfiguration.getPressedStateDuration());
                            } else if (!post(mUnsetPressedState)) {
                                // If the post failed, unpress right now
                                mUnsetPressedState.run();
                            }
                            // 清空事件(主要是长按延迟事件通知)
                            removeTapCallback();
                        }
                        mIgnoreNextUpEvent = false;
                        break;
    
                    case MotionEvent.ACTION_DOWN:
                        if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                            mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                        }
                        mHasPerformedLongPress = false;
    
                        if (!clickable) {
                            // 如果view不可点击,检查长按事件,如果可以长按
                            //  则延迟500s发送一个消息,会回调OnLongClick
                            checkForLongClick(0, x, y);
                            break;
                        }
    
                        if (performButtonActionOnTouchDown(event)) {
                            break;
                        }
    
                        // Walk up the hierarchy to determine if we're inside a scrolling container.
                        boolean isInScrollingContainer = isInScrollingContainer();
    
                        if (isInScrollingContainer) {
                            // 如果view在一个滚动的容器中,则
                            // 延迟发送事件
                            mPrivateFlags |= PFLAG_PREPRESSED;
                            if (mPendingCheckForTap == null) {
                                mPendingCheckForTap = new CheckForTap();
                            }
                            mPendingCheckForTap.x = event.getX();
                            mPendingCheckForTap.y = event.getY();
                            postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                        } else {
                            // 否则的话,直接按下,并检查长按
                            setPressed(true, x, y);
                            checkForLongClick(0, x, y);
                        }
                        break;
    
                    case MotionEvent.ACTION_CANCEL:
                        if (clickable) {
                            setPressed(false);
                        }
                        removeTapCallback();
                        removeLongPressCallback();
                        mInContextButtonPress = false;
                        mHasPerformedLongPress = false;
                        mIgnoreNextUpEvent = false;
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        break;
    
                    case MotionEvent.ACTION_MOVE:
                        if (clickable) {
                            drawableHotspotChanged(x, y);
                        }
    
                        // Be lenient about moving outside of buttons
                        if (!pointInView(x, y, mTouchSlop)) {
                            // Outside button
                            // Remove any future long press/tap checks
                            removeTapCallback();
                            removeLongPressCallback();
                            if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                                setPressed(false);
                            }
                            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        }
                        break;
                }
                // 若该控件可以点击,则返回true
                return true;
            }
            // 如果不可点击,则返回false
            return false;
        }
    

    到这里就算是完成可一大半,如果其他方法都是super.xx的话,
    1)最底层的view的onTouchEvent返回了true,表示可以点击,然后对事件进行了拦截,那么回到文章开头getWindow().superDispatchTouchEvent(ev)则返回了true,也就是不会再调用后面的onTouchEvent了;
    2)最底层的view的onTouchEvent返回了false,表示不可点击,事件没有进行消费,则调用Activity的onTouchEvent()
    在ViewGroup中有这么一段代码,在我们分析完上面之后,需要再重新分析一遍

               if (!canceled && !intercepted) {
                           final View[] children = mChildren;
                           for (int i = childrenCount - 1; i >= 0; i--) {
                               if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                  
                                   } else {
                                       mLastTouchDownIndex = childIndex;
                                   }
                                   mLastTouchDownX = ev.getX();
                                   mLastTouchDownY = ev.getY();
                                   newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                   alreadyDispatchedToNewTouchTarget = true;
                               }
    
                           }
                       }
                   }
               }
    
               if (mFirstTouchTarget == null) {
                   handled = dispatchTransformedTouchEvent(ev, canceled, null,
                           TouchTarget.ALL_POINTER_IDS);
               } else {
                   // Dispatch to touch targets, excluding the new touch target if we already
                   // dispatched to it.  Cancel touch targets if necessary.
                   TouchTarget predecessor = null;
                   TouchTarget target = mFirstTouchTarget;
                   while (target != null) {
                       final TouchTarget next = target.next;
                       if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                           handled = true;
                       } else {
    
                          }
               }
    
    // ViewGourp#addTouchTarget
       private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
           final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
           target.next = mFirstTouchTarget;
           mFirstTouchTarget = target;
           return target;
       }
    

    第一部分是当前view没有拦截事件的前提下,开始遍历子view,然后通过dispatchTransformedTouchEvent 调用子view的dispatchTouchEvent,后面的流程我们大家都知道了,会最终调用最底层view的onTouchEvent,如果有子view的onTouchEvent返回true,那么dispatchTransformedTouchEvent也会返回true,然后将此view通过addTouchTarget添加进事件链中。如果所有的子view(比如说就只有一个子view)返回了false,那么我们事件链就是空的,会进入最后的mFirstTouchTarget == null判断,只不过此时的child == null,然后就会调用View.dispatchTouchEvent,然后在调用ViewGroup的onTouchEvent,因为ViewGroup本身并没有实现onTouchEvent(其他ViewGroup的派生类可能实现了),然后就调用了onTouchEvent(可能是ViewGroup实现的,也有可能是View的),如果ViewGroup的onTouchEvent也返回了false,就会调用文章开头说的Activity的onTouchEvent。
    事件的调用链我们总结一下(返回值以默认为主):

    Activity#dispatchTouchEvent -> ViewGroup#dispatchTouchEvent...
    (ViewGroup可能嵌套ViewGoup这里就用...代替了) -> ViewGroup#onInterceptTouchEvent -> 
    View#dispatchTouchEvent  -> View#onTouchEvent -> 
    ViewGroup#onTouchEvent -> Activity#onTouchEvent
    

    注意点

    1. 整个调用链中都是以boolean作为返回值的,true代表事件被消费或拦截,false代表事件可以向下传递
    2. 事件的分发是在dispatchTouchEvent 中进行的,准确的说是在ViewGoup或者View中进行的,所以如果额外实现了dispatchTouchEvent并且想要进行事件的向下传递必须要调用这两个super.xx(),直接返回true或者false是不会继续向下传递的,并且不会调用当前view的onTouchEvent,只会调用父view的onTouchEvent

    相关文章

      网友评论

        本文标题:事件分发机制

        本文链接:https://www.haomeiwen.com/subject/kirrqqtx.html