Android touch event delivery mechanism, as before

Posted by rocket on Thu, 09 Jan 2020 06:51:30 +0100

Preface

To be a great Android developer, you need a complete Knowledge System Here, let's grow up as we want.

1. Understanding the composition of Activity

An Activity contains a Window object, which is implemented by PhoneWindow.PhoneWindow uses DecorView as the root View of the entire application window, and this DecorView divides the screen into two areas: TitleView and ContentView. What we usually write is shown in ContentView. The following diagram shows the composition of the Activity.

2. Types of Touch Events

Touch events correspond to the MotionEvent class, and there are three main types of events:

  • ACTION_DOWN
  • ACTION_MOVE (Moving distances beyond a certain threshold are determined as ACTION_MOVE operations)
  • ACTION_UP

3. Three Stages of Event Delivery

  • Distribution (dispatchTouchEvent): A method return value of true indicates that the event was consumed by the current view; a return of super.dispatchTouchEvent indicates that the event continues to be distributed.

  • Intercept TouchEvent: A method return value of true means intercepting the event and leaving it to its own onTouchEvent method for consumption; a false means no interception and needs to be passed on to the subview. If return super.onInterceptTouchEvent(ev), there are two scenarios for event interception:

    1. If the View(ViewGroup) has a subView and clicks on it, continue distribution without intercepting it Processing for the child View, at this point, is equivalent to return false. 2. If the View(ViewGroup) has no sub-Views or sub-Views but does not click on the sub-View (ViewGroup at this time) Equivalent to a normal View), then the onTouchEvent response from that View is handed over, which in this case is equivalent to return true. Note: ViewGroup s such as LinearLayout, RelativeLayout, FrameLayout are not blocked by default, and ViewGroup s such as ScrollView, ListView, and so on may be intercepted, depending on the situation.

  • Consumption (onTouchEvent): A method return value of true indicates that the current view can handle the corresponding event; a return value of false indicates that the current view does not handle this event, and it will be handled by the onTouchEvent method passed to the parent view.If return super.onTouchEvent(ev), event processing is divided into two cases:

    1. If the View is clickable or long clickable, it returns true, indicating consumption The event is returned as true; 2. If the View is not clickable or longclickable, it returns false, indicating no Consuming the event will pass it up, just like returning false.

Note: On Android systems, there are three classes with event delivery and handling capabilities.

  • Activity: Has two methods of distribution and consumption.
  • ViewGroup: Has three methods of distribution, interception, and consumption.
  • View: There are two ways to distribute and consume.

4. Distribution Process of Activity to Click Events

When we operate on the touch screen, Linux receives the appropriate hardware interrupt, which is then processed into the original input event and written to the appropriate device node.What our Android Input System does, in summary, is monitor these device nodes, read out and translate data when a device node has data to read, then find the appropriate event recipient in all windows and distribute it. When the click event is generated, the event is passed to the current Activity and completed by PhoneWindow in the Activity. PhoneWindow handles the event processing to DecorView, and then DecorView handles the event processing to ViewGroup.The source flow is as follows:

1.Activity#dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    // Distributed by Window affiliated to Activity, returns true, the event loop ends
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    // Returning false means that the event is not handled, and all View s have onTouchEvent s
    // If false is returned, the Activity's onTouchEvent will be called
    return onTouchEvent(ev);
}
2. Abstract Class Window#superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);
3. The only implementation class PhoneWindow#superDispatchTouchEvent
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

V. Event Distribution Mechanism for View

Events are distributed to the dispatchTouchEvent method of ViewGroup. If its onInterceptTouchEvent returns true, it is handled by itself. If its mOnTouchListener is set, onTouch will be called, otherwise onTouchEvent will be called.In onTouchEvent, if mOnCLickListener is set, onClick is called.If its onInterceptTouchEvent returns false, it is handled by the sub-View on the click event chain so that the distribution is completed in a loop.The key source code for ViewGroup#dispatchTouchEvent is as follows:

1.ViewGroup will reset the state when the ACTION_DOWN event arrives
// Handle an initial down.
if (actionMasked === MotionEvent.ACTION_DOWN) {
    // Throw away all previous state when starting a new touch gesture.
    // The framework may have dropped the up or cancel event for the
    // previous gesture due to an app switch, ANR, or some other stae change.
    cancelAndClearTouchTarget(ev);
    // FLAG_DISALLOW_INTERCEPT will be reset in this method
    resetTouchState();
}
2. Handle whether the current View intercepts click events
final boolean interception;
// mFirstTouchTarget is assigned when the event is successfully handled by a child element of ViewGorup
// And point to the child elements, otherwise, when intercepted by ViewGroup, mFirstTouchTarget is null.
if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    // Set in subview by requestDisallowInterceptTouchEvent method
    // FLAG_DISALLOW_INTERCEPT, at which point ViewGroup will not be able to intercept events other than ACTION_DOWN 
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowintercept) {
        intercepted = onInterceptTouchEvent(ev);
        //re store action in case it was changed
        ev.setAction(action);
    } else {
        intercepted = false;
    } else {
        // There are no touch targets and this action is not an initial down so this 
        // view group continues to intercept touches(ACTION_MOVE,ACTION_UP.eg).
        intercepted = true;
    }
}
3. Part of the source code remaining from the dispatchTouchEvent () method
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...
    final View[] children = mChildren;
    // Traverse through the child elements of ViewGroup, and if the child element can accept a click event, it is handled by the child element.
    for (int i = childrenCount - 1;i >= 0;i--) {
        final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i;
        final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex);
        if (childWithAccessibilityFocus != null) {
            if (childWithAccessibilityFocus != child) {
                continue;
            }
            childWithAccessibilityFocus = null;
            i = childrenCount - 1;
            }
            // There is one item that determines whether the position of the touch point is within the scope of the child View or whether the child View is playing an animation.
            // Nonconformance begins traversing the next subview.
            if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {
                ev.setTargetAccessibilityFocus(false);
                continue;
            }
            newTouchTarget == getTouchTarget(child);
            if (newTouchTarget != null) {
                newTouchTarget.pointerIdBits |= idBitsToAssign;
                break;
            }
            resetCancelNextUpFlag(child);
            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                mLastTouchDownTime = ev.getDownTime();
                if (preorderedList != null) {
                    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
            }
            ev.setTargetAccessibilityFocus(false);
        }
    ...
}
4. Execute true distribution logic in the dispathcTransformedTouchEvent method
private boolean dispatchTransformedTouchEvent(MotionEvent event,boolean cancel,View child,int desiredPointerIdBits) {
    final int oldAction = event.getAction();
    if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
        event.setAction(MotionEvent.ACTION_CANCEL);
        // If there is a child View, the dispatchTouchEvent(event) method of the child View is called, if there is no child View,
        // The super.dispatchTouchEvent(event) method is called.
        if (child == null) {
            handled == super..dispatchTouchEvent(event);
        } else {
            handled = child.dispatchTouchEvent(event);
        }
        event.setAction(oldAction);
        return handled;
    }
    ...
}
5. dispatchTouchEvent() the event is passed to View
public boolean dispatchTouchEvent(MotionEvent event) {
    ...
    boolean result = false;
    if (onFilterTouchEventForSecurity(event)) {
        ListenerInfo li = mListenerInfo;
        // The onTouch method takes precedence over the onTouchEvent(event) method
        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;
}
6. onTouchEvent() the event is passed to View
public boolean onTouchEvent(MotionEvent event) {
    ...
    final int action = event.getAction();
    // As long as the CLICKABLE and LONG_CLICKABLE of View have one as true, onTouchEvent() will
    // Returns the true consumption event.
    if ((viewFlags & CLICKABLE) == CLICKABLE || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
        switch(action) {
            case MotionEvent.ACTION_UP:
                boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                if ((mPrivatFlags & PFLAG_PRESSED) != 0 || prepressed) {
                    boolean focusTaken = false;
                    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                        removeLongPressCallback();
                        if (!focusTaken) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                }
                ...
            }
            return true;
    }
    return true;
}
7. The performCLick() method is called in the ACTION_UP event
public boolean performClick() {
    final boolean result;
    final Listenerinfo li = mListenerInfo;
    // If the View sets a click event, the onClick method executes.
    if (li != null && li.mOnClickListener !== null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        li.mOnClickListener.onClick(this);
        result = true;
    } else {
        result = false;
    }
    sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    return result;
}

From the source analysis above, it can be concluded that View's complete click event delivery process is shown in the following figure.

6. Summary: Delivery rules for click event distribution

Source analysis of event distribution reveals the relationships among the three important methods of click event distribution, represented by pseudocode:

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

Some important conclusions:

1. Event delivery priority: onTouchListener.onTouch > onTouchEvent > onClickListener.onClick.

2. Normally, a time series can only be intercepted and consumed by one View.Because once an element intercepts this event, all events within the same event sequence are handled directly to it (that is, instead of calling the View's intercept method to ask if it is intercepted, the remaining ACTION_MOVE, ACTION_DOWN, and so on, are handled directly to it).Special case: Events can be forcibly handed over to other Views by returning the onTouchEvent that overrides the View to false.

3. If the View does not consume events other than ACTION_DOWN, then the click event will disappear, the parent element's onTouchEvent will not be called, and the current View can continue to receive subsequent events, which will eventually be passed to the Activity for processing.

4.ViewGroup does not intercept any events by default (returns false).

5.View's onTouchEvent consumes events by default (returns true), unless it is clickable (both clickable and longClickable are false).View's long Clickable property defaults to false, clickable property is case-sensitive, such as Button's clickable property defaults to true, TextView's clickable defaults to false.

6.The enable property of View does not affect the default return value of onTouchEvent.

7. The request DisallowInterceptTouchEvent method allows you to intervene in the event distribution process of the parent element in child elements, with the exception of ACTION_DOWN events.

The final complete event distribution flowchart is as follows:

Reference link:

1. Exploration of Android Development Art

2. Android Advanced Light

3. Android Advanced

4,Gityuan Android Event Distribution Mechanism

5,Understanding Android's Event Distribution Mechanisms

6,Detailed mechanisms for distributing Android events: the most comprehensive and understandable in history

7,Long Distance in Android Development VI - Illustrating the Android Event Distribution Mechanism (Deep Source)

appreciate

If this library is of great help to you, you are willing to support the further development of this project and the ongoing maintenance of this project.You can scan the QR code below and let me have a cup of coffee or beer.Thank you very much for your donation.Thank you!

<div align="center"> <img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc32595031fa?w=1080&h=1457&f=jpeg&s=93345" width=20%><img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc3259518ecd?w=990&h=1540&f=jpeg&s=110691" width=20%> </div>

Contanct Me

# WeChat:

Welcome to my WeChat: bcce5360

# WeChat Group:

If you can't sweep code to join the WeChat group, please invite your friends who want to join the WeChat group, and add me to WeChat to pull you into the group.

<div align="center"> <img src="https://user-gold-cdn.xitu.io/2020/1/7/16f7dc352011e1fe?w=1013&h=1920&f=jpeg&s=86819" width=35%> </div>

The QQ group:

2,000 people QQ group, Awesome-Android learning and communication group, QQ group number: 959936182, welcome to join ~

About me

Thank you for reading this article. I hope you can share it with your friends or technical groups. It means a lot to me.

Hope we can be friends at Github,Nuggets Share your knowledge last time.

Topics: Mobile Android github Linux Windows