Android event distribution mechanism decryption one (ViewGroup distribution)

Once upon a time, when I was still an Android little rookie, I was asked about the event distribution mechanism. At that time, I was a blog from Baidu. The answer was very simple. At that time, I remembered it for a while and then forgot it. Now write a blog to record it, and I won’t be afraid of being asked next time ( Dese.gif ). Under normal circumstances, if you understand the following issues, you can basically grasp the overall appearance of event distribution:

1. Why should we understand event distribution?
Answer: 1. In order to solve the event conflict (the event conflict means: there is only one event, and the control that multiple controls want to process and handle is not the control we want to give, and there is a conflict)
2. In order not to be asked by the interviewer.懵Force (懵for warning ⚠️)
Insert picture description here

2. What is the event?
To put it simply: MotionEvent is generated when a finger touches the mobile phone screen. The specific generation process will have a chance to analyze later. This article mainly analyzes the distribution process of the event (MotionEvent). Remember that the following table, especially ACTION_MOVE, will be triggered multiple times . Keep this in mind, and you will often forget this when you analyze the source code later.
Insert picture description here
3. What are the main methods and functions of the event distribution process?
Answer: dispatchTouchEvent(), onInterceptTouchEvent() and onTouchEvent()
Insert picture description here

4. What is the distribution process in event distribution? (The train is off, sit firmly)
Insert picture description here
So if we do not rewrite or change the return value of the method in the control, but directly use super to call the default implementation of the parent class, then the entire event flow should be from Activity---->ViewGroup— >View calls the dispatchTouchEvent method from top to bottom, until the leaf node (View), and then calls the onTouchEvent method from the bottom to the top by View—>ViewGroup—>Activity. So overall it should be a U-shaped recursive call .

Take a look at the source code and restore the U-shaped call. It is best to read the source code by yourself to deepen the impression (I analyzed the source code of Android 10, and each version may be a bit different, and the basic principles are similar)

4.1 Activity->ViewGroup event delivery mechanism

//Activity中
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
    // 一般事件列开始都是DOWN事件 = 按下事件,故此处基本是true
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
    
    
        onUserInteraction();//这个是个空方法,子类可以重写实现屏保功能
    }
    //这里调用的是Window的superDispatchTouchEvent()方法
    if (getWindow().superDispatchTouchEvent(ev)) {
    
    
        return true;
        // 若getWindow().superDispatchTouchEvent(ev)的返回true
        // 则Activity.dispatchTouchEvent()就返回true,则方法结束。即 :该点击事件停止往下传递 & 事件传递过程结束(返回true是被消费了,false是没有被消费)
        // 如果没有被消费,则调用Activity.onTouchEvent
    }
    return onTouchEvent(ev);
}

//Window 是抽象类其唯一的子类是PhoneWindow
public abstract boolean superDispatchTouchEvent(MotionEvent event);

//PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
   //这里调用的是DecorView的superDispatchTouchEvent()方法
   return mDecor.superDispatchTouchEvent(event);
}
// DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
    
    
    //DecorView的父类是FrameLayout,FrameLayout的父类是ViewGroup,由于FrameLayout没有实现dispatchTouchEvent()方法,所以这里调用的是ViewGroup的dispatchTouchEvent()方法
    return super.dispatchTouchEvent(event);
}

4.2 ViewGroup event distribution mechanism

From the above analysis, we can see that the ViewGroup event distribution mechanism starts from dispatchTouchEvent()

//ViewGroup的dispatchTouchEvent()方法代码比较多,这里说一些重点的片段
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    
    
   ...//仅贴出关键代码
   
   //这里是方法的核心,每个事件都会走到这个if里面
   if (onFilterTouchEventForSecurity(ev)) {
    
    
        //重点分析1:ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
         final boolean intercepted;
            //判断条件1:拦截对象针对于ACTION_DOWN 
            //判断条件2:mFirstTouchTarget赋值在addTouchTarget()方法中,先给出结论:当事件被ViewGroup的子View消费了mFirstTouchTarget的值才不为null,具体分析见文末链接
            //也就是如果是down事件或者之前事件被子View消费了后续事件都可以进入if条件代码块内
         if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
    
    
             //判断条件3:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
             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;
            }
       //判断是否被取消或者拦截
       if (!canceled && !intercepted) {
    
    
            // 重点分析2
            // 如果当前ViewGroup有子View,则通过for循环,遍历了当前ViewGroup下的所有子View
       	    for (int i = childrenCount - 1; i >= 0; i--) {
    
    
                // 判断条件1:子View是否显示
                // 判断条件2:当前遍历的View是不是正在点击的View,从而找到当前被点击的View
                // 如果不是则跳过
                if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {
    
    
                      ev.setTargetAccessibilityFocus(false);
                      continue;
                }
                // dispatchTransformedTouchEvent的内部调用了该View的dispatchTouchEvent()
                // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下文的View事件分发机制)
                if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
    
    
                     //这里对mFirstTouchTarget进行了赋值
                     newTouchTarget = addTouchTarget(child, idBitsToAssign);
                     alreadyDispatchedToNewTouchTarget = true;
                     //如果事件被消费了则跳出循环
                     break;
                }
            }
       }
       //重点分析3 点击事件没有任何子View消费则仍然调用dispatchTransformedTouchEvent()方法,但是chrild传入的是null,会调用View的dispatchTouchEvent()将当前ViewGroup本身作为View来进行处理
      if (mFirstTouchTarget == null) {
    
        
         handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS);
      } else {
    
    
          //...    
      }
   }
}
/**
 * 分析1:ViewGroup.onInterceptTouchEvent()
 * 作用:是否拦截事件
 * 说明:
 *     a. 返回true = 拦截,即事件停止往下传递(需手动设置,即复写onInterceptTouchEvent(),从而让其返回true)
 *     b. 返回false = 不拦截(默认)
  */
  public boolean onInterceptTouchEvent(MotionEvent ev) {
    
      
  
    return false} 
/**
 * 分析2:ViewGroup.dispatchTransformedTouchEvent()
 * 作用:都是调用View的dispatchTouchEvent()方法
 * 说明:
 *      a.如果当前ViewGroup有子View可以处理,则调用当前child的dispatchTouchEvent()
 *      b.如果当前ViewGroup没有子View则把当前ViewGroup当做View处理也调用View的dispatchTouchEvent()
  */  
 private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    
    
   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);
    }
     // Done.
     transformedEvent.recycle();
     return handled;
  }
  /**
   * 分析3:ViewGroup.requestDisallowInterceptTouchEvent()
   * 作用:子View可以改变父ViewGroup的拦截,前提是父ViewGroup必须把down事件下发到处理事件的子View
   * 说明:
   *      a.disallowIntercept==true 则禁止ViewGroup拦截事件
   *      b.disallowIntercept==false 则允许ViewGroup拦截事件
   */
   public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
    
    
       //约束当前ViewGroup的拦截
       if (disallowIntercept) {
    
    
            mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
        } else {
    
    
            mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
        }
        //约束父ViewGroup的拦截(如果有父容器)
        if (mParent != null) {
    
    
            mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
        }
   }
 

V. Summary

  • Conclusion: Android event distribution is always passed to ViewGroup first, and then to View
  • When a certain control is clicked down-to
    determine who the event is given to
    1. First see if the ViewGroup is intercepted and then processed by itself (that is, not distributed)
    2. Distribute: Sort and traverse the View that distributes the event to process the event
    3. No child View to receive it, Then look at whether you handle the event

    move-handle the event 1. First
    see if the ViewGroup intercepts it and then handle it by itself (that is, not distribute it) (the child View can request not to intercept the Move event)
    2. Distribute it: the view processing directly determined by the down event

    Insert picture description here

View event handling will be explained in the next article. If you find this article useful, please like and add to favorites✨.

Related reference article links:
Aunt Guo’s event distribution mechanism completely analyzes the
Android event distribution mechanism. Detailed explanation: the most comprehensive and easy-to-understand Android event distribution mFirstTouchTarget in history

Guess you like

Origin blog.csdn.net/qq_32865887/article/details/114539968