Take a look at Android's touch event distribution

When our fingers touch various views on the screen, what exactly goes through to the end of this click event? Let's briefly analyze. The reason for this simple analysis is that the hal layer is not involved at all.

event type

There are three types of touch events:

int action = MotionEventCompat.getActionMasked(event);
    switch(action) {
        case MotionEvent.ACTION_DOWN:
            break;
        case MotionEvent.ACTION_MOVE:
            break;
        case MotionEvent.ACTION_UP:
            break;
    }

In addition to these three types, there is also ACTION_CANCEL, which indicates that the current gesture is canceled. It can be understood that a child View receives the ACTION_DOWN event sent to it by the parent View. When the parent View intercepts the event and no longer forwards the event to the child VIew, it will give the child View an ACTION_CANCEL event.

Activity's event distribution mechanism

When we touch the screen, the click event will first be passed to the Activity, which is completed by PhoneWindow in the Activity. PhoneWindow will then pass the event to DecorView, the root of the entire control tree, and then DecorView will hand over the event processing to ViewGroup.

### Activity.dispatchTouchEvent
  public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
  // 由Window进行分发,当getWindow().superDispatchTouchEvent(ev)返回true
  //则 dispatchTouchEvent返回true,
  //方法结束,不会执行onTouchEvent方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev); 1
    }
### PhoneWindow
//将事件传递到ViewGroup进行处理
   @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

When the event is not handled, it will return to the Activity's onTouchEvent method:

### Activity.onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
  //只有在点击事件在Window边界外才返回true,一般都返回false
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
			
        return false;
    }

### Window.shouldCloseOnTouch
  public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
  //是不是Down事件,event坐标是否在边界内等待
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
          //返回true:说明事件在边界外,即 消费事件
            return true;
        }
        return false;
    }

ViewGroup event distribution mechanism

As you can see from the above distribution mechanism, ViewGroup's event distribution mechanism starts from dispatchTouchEvent().

	### ViewGroup.dispatchTouchEvent
    public boolean dispatchTouchEvent(MotionEvent ev) {
      if (mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
            }
       ......

        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) {             
                cancelAndClearTouchTargets(ev);
              //重置所有的触摸状态以这准备新的循环
                resetTouchState();
            }

            // 事件被处理, mFirstTouchTarget为第一个触摸目标
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
              //子View通过requestDisllowInterceptTouchEvet设置FLAG_DISALLOW_INTERCEPT标志,表示父View不再拦截事件
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                  //返回false默认
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // 恢复
                } 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) {
                        ......
                      
                      //遍历ViiewGroup的子元素,如果子元素能够接收到点击事件,则交给子元素处理
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                           ......
                             
                             //子 View的范围内或者子View是否在播放动画
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // 在触摸范围内
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                          
                          //条件判断的内部调用了该View的dispatchTouchEvent()
                          //实现了点击事件从ViewGroup到子View的传递
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                               .....
                    }
                    ......
                }
            }
            ......
        return handled;
    }
  
  
  //拦截
  //返回false:不拦截(默认)
  // 返回true:拦截,即事件停止往下传递(需手动复写onInterceptTouchEvent()其返回true)
     public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }

Seeing that the dispatchTransformedTouchEvent method was called above, how does the ViewGroup distribute the event to the sub-View:

### 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);
          //有子View,则调用子View的dispatchTouchEvent(event)方法,如果没有子View,
        // 则调用super.dispatchTouchEvent(event)方法。
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }
       ......
        // Done.
        transformedEvent.recycle();
        return handled;
    }  

It can be known that event distribution is always passed to the ViewGroup first, and then traverses the sub-View to find the clicked sub-View, and then passes it to the sub-View.

TouchTarget: It is a one-way linked list that records which pointer (finger) pressed each sub-View.

The three key important method relationships can be expressed in pseudo code:

public boolean dispatchTouchEvent(MotionEvent ev){
  
  boolean consume = false;
  
  if(onInterceptTouchEvent(ev)){
    	consume = onTouchEvent(ev);
  } else {
    	consume = childDispatchTouchEvent(ev);
  }
  return consume;
}

View's event distribution mechanism

The event is passed to the dispatchTouchEvent method of View.

### View.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent event) {
        ......
        boolean result = false;
        ......

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //onTouchListener方法的优先级要高于onTouchEvent方法
            ListenerInfo li = mListenerInfo;
          //有三个条件符合才会返回true
           //mOnTouchListener != null      mOnTouchListener变量在View.setOnTouchListener()里赋值
          //(mViewFlags & ENABLED_MASK) == ENABLED   判断当前点击的控件是否是enabled
          // mOnTouchListener.onTouch(this, event)   回调控件注册Touch事件时的onTouch()
            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;
    }

If any of the three conditions is not met, the event will be passed to the View's onTouchEvent(). The onTouchEvent method is analyzed in detail below.

### 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;

   		//如果被禁用了  把事件吃掉
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
          //抬起,仍然设置setPressed(false),表示点击过了
            if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
 
            return clickable;
        }
   
   	//触摸代理  增加点击区域
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }

   		//TOOLTIP   tooptipText是个解释工具
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                    mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    if ((viewFlags & TOOLTIP) == 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) {
                       
                        boolean focusTaken = false;
                      //可以获取焦点
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }

                      //如果是预按下状态
                        if (prepressed) {
                            setPressed(true, x, y);
                        }

                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            // 取消长按回调
                            removeLongPressCallback();

                            // Only perform take click actions if we were in the pressed state
                            if (!focusTaken) {
                              
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                  //触发点击
                                    performClickInternal();
                                }
                            }
                        }

                       ......

                        removeTapCallback();
                    }
                    mIgnoreNextUpEvent = false;
                    break;

                case MotionEvent.ACTION_DOWN:
                //你是不是摸到屏幕了
                    if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
                        mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
                    }
                    mHasPerformedLongPress = false;

                //不可点击 检查长按, 设置一个长按的监听器
                    if (!clickable) {
                        checkForLongClick(0, x, y);
                        break;
                    }

                //判断鼠标右键点击
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }

                    // 在滑动控件中
                    boolean isInScrollingContainer = isInScrollingContainer();

                //对于滚动容器内的视图,如果这是滚动,请将按下的反馈延迟一小段时间。
                //滑动控件中不知道你是操作父View还是子View
                //不是滑动的 可以重写shouleDelayChildPressedState为false,不用延迟子View按下时间
                    if (isInScrollingContainer) {
                      //设置状态设为 预按下
                        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);
                    }

                    // 如果手出界了
                    if (!pointInView(x, y, mTouchSlop)) {
                        // 全部取消掉
                        removeTapCallback();
                        removeLongPressCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            setPressed(false);
                        }
                        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                    }
                    break;
            }

            return true;
        }

        return false;
    }

Finally the performClick() method is called:

### View.performClick 
public boolean performClick() {
     
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
   // 如果View设置了点击事件,onClick方法就会执行。
   //onTouch()的执行 先于 onClick()
        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;
    }

Event delivery summary

  • The same event sequence refers to the time from the finger touching the screen to the finger leaving. The entire event sequence starts with a down event, contains a varying number of move events in the middle, and finally ends with an up event.
  • Under normal circumstances, an event sequence can only be intercepted and consumed by one View.
  • Once a View is determined to intercept, this event sequence can only be processed by it, and its onInterceptToucheEvent will not be called again.
  • Once a View starts processing events, if it does not consume the ACTION_DOWN event, then other events in the same event sequence will not be handed over to it for processing, and the event will be handed over to its parent element for processing, that is, the parent element's onTouchEvent will be called.
  • If the View does not consume other events other than ACTION_DOWN, then the click event will disappear. At this time, the onTouchEvent of the parent element will not be called, and the current View can continue to receive subsequent events. Eventually, these disappeared click events will be passed to Activity processing.
  • ViewGroup does not intercept any events by default.
  • View's onTouchEvent will consume events (return true) by default, unless it is not clickable (clickable and longClickable are false at the same time). The longClickable attribute of View defaults to false, and the clickable attribute depends on the situation. For example, the clickable attribute of Button defaults to true, and the clickable attribute of TextView defaults to false.
  • View's enable attribute does not affect the default return value of onTouchEvent. Even if a View is in the disabled state, as long as one of its clickable or longClickable is true, its onTouchEvent will return true.
  • The event delivery process is from the outside in, that is, the event is always delivered to the parent element first, and then distributed by the parent element to the child View. The requestDisallowInterceptTouchEvent method can be used to intervene in the event distribution process of the parent element in the child element, except for the ACTION_DOWN event. .

Complete diagram:

Guess you like

Origin blog.csdn.net/Jason_Lee155/article/details/132833955