• Android:事件分发机制(二)


    这篇主要是第一篇回顾之后,补充一些上一篇没写到的两个点。
    第一个的切入点是这个。【处理层叠的view,想要执行下一层的view的点击事件】其背后的原理。

    处理层叠的view,要执行下一层的view的点击事件

    我们知道,方法是将上一层的view设置setOnTouchListener的onTouch() return false;

    iv_right.setOnTouchListener { _, _ ->
        false
    }
    
    • 1
    • 2
    • 3

    那么,原理是啥?其实看源码就可以了解。
    首先,viewGroup的 dispatchTouchEvent 在 onInterceptTouchEvent不拦截的情况下, 传递Event到 view的 dispatchTouchEvent,然后在
    其方法体中,实现原理如一下源代码:

    
       if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
    
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        //...
         return result;
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    而onTouchEvent默认是false。因此,最后的result就是返回了false。这个时候,会回到ViewGroup层。再回过来看其 dispatchTouchEvent的这段代码:

    //...(此处省略部分源码)
    final int childrenCount = mChildrenCount;
      if (newTouchTarget == null && childrenCount != 0) {
          final float x =
                  isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
          final float y =
                  isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
          // Find a child that can receive the event.
          // Scan children from front to back.
          final ArrayList<View> preorderedList = buildTouchDispatchChildList();
          final boolean customOrder = preorderedList == null
                  && isChildrenDrawingOrderEnabled();
       final View[] children = mChildren;
       for (int i = childrenCount - 1; i >= 0; i--) {
           final int childIndex = getAndVerifyPreorderedIndex(
                   childrenCount, i, customOrder);
           final View child = getAndVerifyPreorderedView(
                   preorderedList, children, childIndex);
           if (!child.canReceivePointerEvents()
                   || !isTransformedTouchPointInView(x, y, child, null)) {
               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);
           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();
               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);
       }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 53
    • 54
    • 55
    • 56
    • 57

    由此,可知viewgroup会在这个方法中,遍历对应区域下的所有view。如果所有view都没消费掉这个Event的时候,dispatchTouchEvent会继续执行接下来的代码,

     // Dispatch to touch targets.
                if (mFirstTouchTarget == null) {
                    // No touch targets so treat this as an ordinary view.
                    handled = dispatchTransformedTouchEvent(ev, canceled, null,
                            TouchTarget.ALL_POINTER_IDS);
    
    • 1
    • 2
    • 3
    • 4
    • 5

    这这段代码中,可以看到,进入了if语句,语句中执行了dispatchTransformedTouchEvent()方法。可以看到,它的源码中,它会在这个场景下,回调super.dispatchTouchEvent(event);最终执行了viewGroup自身的onTouchEvent()方法。

     private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
                View child, int desiredPointerIdBits) {
            final boolean handled;
    
            // Canceling motions is a special case.  We don't need to perform any transformations
            // or filtering.  The important part is the action, not the contents.
            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;
            }
    	//...(此处省略部分源码)
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19

    这样,最终形成闭环。也就是上一篇文章所画的流程了。第一篇在这:Android:事件分发机制

    click在整体流程中的哪个节点上

    这个其实在第一篇的4.3节有提到过。但是没说得很多,在这里补充一下。
    这个问题的切入点,我们从最常见的设置点击事件开始说起。

    mTv.setOnClickListener(v -> {});
    
    • 1

    这里,设置给了View的mOnClickListener。然后,这个回调会在performClick中被调用。

     public boolean performClick() {
            // We still need to call this method to handle the cases where performClick() was called
            // externally, instead of through performClickInternal()
            notifyAutofillManagerOnClick();
    
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
    
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
            notifyEnterOrExitForAutoFillIfNeeded(true);
    
            return result;
        }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    然后,它会在onTouchEvent中被调用。具体是在MotionEvent.ACTION_UP中执行了performClickInternal()。
    看部分源码如下:

    //...(此处省略部分源码)
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                        if ((viewFlags & TOOLTIP) == TOOLTIP) {
                            handleTooltipUp();
                        }
                        if (!clickable) {
                            removeTapCallback();
                            removeLongPressCallback();
                            mInContextButtonPress = false;
                            mHasPerformedLongPress = false;
                            mIgnoreNextUpEvent = false;
                            break;
                        }
                        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)) {
                                        performClickInternal();
                                    }
                                }
                            }
                            //...(此处省略部分源码)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
  • 相关阅读:
    Mysql事务+redo日志+锁分类+隔离级别+mvcc
    @Autowired @Resource @Qualifier的区别
    NestJS 中的 gRPC 微服务通信
    深度学习笔记之优化算法(八)Adam算法的简单认识
    C++的explicit是什么?
    高效擦除/移除(Erase–remove idiom)std::vector元素
    NPM包本地开发
    4009. Problem D:走台阶问题
    什么是项目管理,如何做好项目管理?
    Linux操作-4之stat, find, xargs命令
  • 原文地址:https://blog.csdn.net/wzj_what_why_how/article/details/133805926