Talk about the event distribution mechanism in Android

  • 2021-12-12 05:58:26
  • OfStack

Event Distribution Mechanism of Directory Activity Event Distribution Mechanism of ViewGroup Event Distribution Mechanism of View

The essence of View event distribution mechanism is the distribution process of MotionEvent events, that is, how MotionEvent is transmitted and processed between View after it is produced.

First of all, introduce what is MotionEvent. The so-called MotionEvent, that is, a series of touch events generated when the user's finger touches the mobile phone screen. Typical touch events are:

ACTION_DOWN: 1 moment after your finger touches the screen. ACTION_MOVE: The finger slides across the screen. ACTION_UP: 1 moment after the finger leaves the screen. ACTION_CANCLE: The current sequence of events terminates.

Event sequence 1 generally begins with DOWN event, ends with UP event, and interspersed with several MOVE events.

Event delivery order: Activity (Window) → ViewGroup → View, that is, events are delivered down from Activity.

There are three main methods involved in the distribution of events:

dispatchTouchEvent: Top-down event delivery. The return value is affected by the dispatchTouchEvent method of the child View and the onTouchEvent method of the current View. onInterceptTouchEvent: Intercept events. This method is unique to ViewGroup. 1 Once an event in an event sequence is intercepted, the remaining events in the sequence are handed over to the intercepted ViewGroup, and this method is not called again. onTouchEvent: To consume an event, that is, to process an event.

Next, the event distribution mechanism of Activity, ViewGroup and View will be explained respectively.

Event Distribution Mechanism of Activity

When a 1-click event occurs, the event is first passed to the dispatchTouchEvent () method of Activity for processing.
Activity calls the getWindow () in the dispatchTouchEvent () method. The superDispatchTouchEvent () method passes the event to mDecor (DecorView) of Window for processing, while mDecor passes the event to ViewGroup for processing by calling the superDispatchTouchEvent method.


/**
  *  Source code analysis: Activity.dispatchTouchEvent () 
  */ 
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }

            if (getWindow().superDispatchTouchEvent(ev)) {

                return true;
                //  If getWindow().superDispatchTouchEvent(ev) Return of true
                //  Then Activity.dispatchTouchEvent () returns true The method ends. I.e.   The click event stops passing down  &  End of event delivery process 
                //  Otherwise: Continue to call Activity.onTouchEvent

            }
            return onTouchEvent(ev);
        }

/**
  * getWindow().superDispatchTouchEvent(ev)
  *  Description: 
  *     a. getWindow() =  Get Window Objects of the class 
  *     b. Window Class is an abstract class, and its only 1 Implementation class  = PhoneWindow Class; That is, here Window Class object  = PhoneWindow Class object 
  *     c. Window Class superDispatchTouchEvent() = 1 Abstract methods, composed of subclasses PhoneWindow Class implementation 
  */
    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {

        return mDecor.superDispatchTouchEvent(event);
        // mDecor =  Top floor View ( DecorView ) Instance object of 
    }

/**
  * mDecor.superDispatchTouchEvent(event)
  *  Definition: Belonging to the top level View ( DecorView ) 
  *  Description: 
  *     a. DecorView Class is PhoneWindow Class 1 Inner classes 
  *     b. DecorView Inherit from FrameLayout Is the parent class of all interfaces 
  *     c. FrameLayout Yes ViewGroup Subclass of, so DecorView Indirect parent class of  = ViewGroup
  */
    public boolean superDispatchTouchEvent(MotionEvent event) {

        return super.dispatchTouchEvent(event);
        //  Call the method of the parent class  = ViewGroup Adj. dispatchTouchEvent()
        //  I.e.   Pass an event to the ViewGroup To deal with, please see the details ViewGroup Event distribution mechanism based on 

    }

/**
  * Activity.onTouchEvent () 
  *  Definition: Belonging to the top level View ( DecorView ) 
  *  Description: 
  *     a. DecorView Class is PhoneWindow Class 1 Inner classes 
  *     b. DecorView Inherit from FrameLayout Is the parent class of all interfaces 
  *     c. FrameLayout Yes ViewGroup Subclass of, so DecorView Indirect parent class of  = ViewGroup
  */
  public boolean onTouchEvent(MotionEvent event) {

        //  When 1 Click events have not been Activity Under any 1 A View Receive  /  When processing 
        //  Application scenario: Processing occurs in Window Touch events outside the boundary 
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        
        return false;
        //  That is, only when the click event is in Window Will return outside the border true , 1 All situations return false
    }

Event Distribution Mechanism of ViewGroup

When an event is passed from Activity to dispatchTouchEvent () of ViewGroup, ViewGroup will first call onInterceptTouchEvent () method to judge whether to intercept the event (default does not intercept, if intercepted, it needs to be rewritten by the user). If it does not intercept the event, ViewGroup will loop through for to traverse all its children View, find the View where the current event occurs, and then call dispatchTouchEvent () method of the child View to distribute the event to the child View for processing.
If the event is intercepted by ViewGroup or the View where the event occurred is not found (the event occurred in the blank space), ViewGroup calls its onTouchEvent () method to handle the event.


/**
  * 源码分析:ViewGroup.dispatchTouchEvent()
  */ 
    public boolean dispatchTouchEvent(MotionEvent ev) { 

    ... // 仅贴出关键代码

        // ViewGroup每次事件分发时,都需调用onInterceptTouchEvent()询问是否拦截事件
            if (disallowIntercept || !onInterceptTouchEvent(ev)) {  

            // 判断值1:disallowIntercept = 是否禁用事件拦截的功能(默认是false),可通过调用requestDisallowInterceptTouchEvent()修改
            // 判断值2: !onInterceptTouchEvent(ev) = 对onInterceptTouchEvent()返回值取反
                    // a. 若在onInterceptTouchEvent()中返回false(即不拦截事件),就会让第2个值为true,从而进入到条件判断的内部
                    // b. 若在onInterceptTouchEvent()中返回true(即拦截事件),就会让第2个值为false,从而跳出了这个条件判断
                    // c. 关于onInterceptTouchEvent() ->>分析1

                ev.setAction(MotionEvent.ACTION_DOWN);  
                final int scrolledXInt = (int) scrolledXFloat;  
                final int scrolledYInt = (int) scrolledYFloat;  
                final View[] children = mChildren;  
                final int count = mChildrenCount;  

            // 通过for循环,遍历了当前ViewGroup下的所有子View
            for (int i = count - 1; i >= 0; i--) {  
                final View child = children[i];  
                if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE  
                        || child.getAnimation() != null) {  
                    child.getHitRect(frame);  

                    // 判断当前遍历的View是不是正在点击的View,从而找到当前被点击的View
                    // 若是,则进入条件判断内部
                    if (frame.contains(scrolledXInt, scrolledYInt)) {  
                        final float xc = scrolledXFloat - child.mLeft;  
                        final float yc = scrolledYFloat - child.mTop;  
                        ev.setLocation(xc, yc);  
                        child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  

                        // 条件判断的内部调用了该View的dispatchTouchEvent()
                        // 即 实现了点击事件从ViewGroup到子View的传递(具体请看下面的View事件分发机制)
                        if (child.dispatchTouchEvent(ev))  { 

                        mMotionTarget = child;  
                        return true; 
                        // 调用子View的dispatchTouchEvent后是有返回值的
                        // 若该控件可点击,那么点击时,dispatchTouchEvent的返回值必定是true,因此会导致条件判断成立
                        // 于是给ViewGroup的dispatchTouchEvent()直接返回了true,即直接跳出
                        // 即把ViewGroup的点击事件拦截掉

                                }  
                            }  
                        }  
                    }  
                }  
            }  
            boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||  
                    (action == MotionEvent.ACTION_CANCEL);  
            if (isUpOrCancel) {  
                mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;  
            }  
            final View target = mMotionTarget;  

      
        // 若点击的是空白处(即无任何View接收事件) / 拦截事件(手动复写onInterceptTouchEvent(),从而让其返回true)
        if (target == null) {  
            ev.setLocation(xf, yf);  
            if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {  
                ev.setAction(MotionEvent.ACTION_CANCEL);  
                mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;  
            }  
            
            return super.dispatchTouchEvent(ev);
            // 调用ViewGroup父类的dispatchTouchEvent(),即View.dispatchTouchEvent()
            // 因此会执行ViewGroup的onTouch() ->> onTouchEvent() ->> performClick() ->> onClick(),即自己处理该事件,事件不会往下传递(具体请参考View事件的分发机制中的View.dispatchTouchEvent())
            // 此处需与上面区别:子View的dispatchTouchEvent()
        } 

        ... 

}
/**
  * ViewGroup.onInterceptTouchEvent()
  * 作用:是否拦截事件
  * 说明:
  *     a. 返回true = 拦截,即事件停止往下传递(需手动设置,即复写onInterceptTouchEvent(),从而让其返回true)
  *     b. 返回false = 不拦截(默认)
  */
  public boolean onInterceptTouchEvent(MotionEvent ev) {  
    
    return false;

  } 

Event Distribution Mechanism of View

When an event is passed from ViewGroup to dispatchTouchEvent () of View, the onTouch () method of View is executed first. The onTouch () method is defined in the OnTouchListener interface of View and is triggered when the user touches the screen if the user registers listening for View. This method returns false by default and needs to be overridden by the user.
The onTouchEvent () method of View is executed only if the onTouch () method returns false. The performClick () method is then called as appropriate, and the performClick () method then calls the onClick () method.


/**
  *  Source code analysis: View.dispatchTouchEvent () 
  */
  public boolean dispatchTouchEvent(MotionEvent event) {  

        if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&  
                mOnTouchListener.onTouch(this, event)) {  
            return true;  
        } 
        return onTouchEvent(event);  
  }
  //  Note: Only the following 3 All the conditions are true, dispatchTouchEvent() Before returning true ; Otherwise execute onTouchEvent()
  //     1. mOnTouchListener != null
  //     2. (mViewFlags & ENABLED_MASK) == ENABLED
  //     3. mOnTouchListener.onTouch(this, event)
  //  The following face this 3 Analyze each condition one by one 


/**
  *  Condition 1 : mOnTouchListener != null
  *  Description: mOnTouchListener Variable in View.setOnTouchListener Assignment in () method 
  */
  public void setOnTouchListener(OnTouchListener l) { 

    mOnTouchListener = l;  
    //  That is, as long as we register the control Touch Events, mOnTouchListener Just 1 Definitely assigned (not empty) 
        
} 

/**
  *  Condition 2 : (mViewFlags & ENABLED_MASK) == ENABLED
  *  Description: 
  *     a.  The condition is to determine whether the currently clicked control enable
  *     b.  Because of many View Default enable Therefore, the condition is constant as true
  */

/**
  *  Condition 3 : mOnTouchListener.onTouch(this, event)
  *  Description: Namely   Back control registration Touch Event at the time of onTouch (); You need to manually override the settings, as follows (with buttons Button For example) 
  */
    button.setOnTouchListener(new OnTouchListener() {  
        @Override  
        public boolean onTouch(View v, MotionEvent event) {  
     
            return false;  
        }  
    });
    //  If in onTouch () Returns true , will make the above 3 All the conditions hold, so that View.dispatchTouchEvent () Returns directly true Event distribution ends 
    //  If in onTouch () Returns false , will make the above 3 All the conditions are not true, which makes View.dispatchTouchEvent Jump out of () If , execution onTouchEvent(event)

If onTouchEvent () of View returns true, that is, the event is consumed, then the distribution of the event ends here. If false is returned, the onTouchEvent () method of ViewGroup and Activity is called from bottom to top to handle the event. It is worth mentioning that the onTouchEvent () method of Activity must handle the event.
At this point, the distribution of events is completed.

The above is to talk about Android event distribution mechanism in detail, more information about Android event distribution mechanism please pay attention to other related articles on this site!


Related articles: