Detailed Process of Android Event Distribution Mechanism (II)

Keywords: Android Windows Mobile

Foreword: In the last article, we started with the process of event distribution execution. We should understand and analyze the process of event distribution. We should have a general understanding of event distribution from the analysis, and I believe we should also be able to analyze how events will be executed by ourselves. In fact, it's not difficult to understand something. But today we still need to look at activit. Y, viewgroup, view related source code to learn how they work, let's start!

First of all, let's take a look at the engineering situation in our last article.

Again, after a click event occurs, the event first passes to Activity, then to ViewGroup, and finally to View. Then we analyze the source code from Activity's distribution mechanism for click events, ViewGroup's distribution mechanism for click events, and View's distribution mechanism for click events.

I. Activity's Distribution Mechanism for Click Events

From our analysis of the previous day, we can see that when a new click event occurs, it will first enter the dispatchTouchEvent() method of Activity. Let's first look at the source code of dispatchTouchEvent() of Activity:

public boolean dispatchTouchEvent(MotionEvent ev) {
    //Since ACTION_DOWN must be the first event of each click, it must be true here.
    if (ev.getAction() == MotionEvent.ACTION_DOWN) {
        onUserInteraction();
    }
    if (getWindow().superDispatchTouchEvent(ev)) {
        //If getWindow (). superDispatch TouchEvent (ev) is true
        //Then dispatchTouchEvent() returns true, the event is consumed, and the delivery ends.
        return true;
    }
    //Otherwise, the onTouchEvent method will be executed
    return onTouchEvent(ev);
}
  • The onUserInteraction method of activity:
/**
 * Called whenever a key, touch, or trackball event is dispatched to the
 * activity.  Implement this method if you wish to know that the user has
 * interacted with the device in some way while your activity is running.
 * This callback and {@link #onUserLeaveHint} are intended to help
 * activities manage status bar notifications intelligently; specifically,
 * for helping activities determine the proper time to cancel a notfication.
 *
 * <p>All calls to your activity's {@link #onUserLeaveHint} callback will
 * be accompanied by calls to {@link #onUserInteraction}.  This
 * ensures that your activity will be told of relevant user activity such
 * as pulling down the notification pane and touching an item there.
 *
 * <p>Note that this callback will be invoked for the touch down action
 * that begins a touch gesture, but may not be invoked for the touch-moved
 * and touch-up actions that follow.
 *
 * @see #onUserLeaveHint()
 */
public void onUserInteraction() {
}

Here we return an empty method. When we don't know what this method is for, the official annotation is the best answer. Maybe your English is not good, but we have an online translation, right? This method is mainly to provide users with information and mobile phone interaction, if you can use it to achieve it and then rewrite the code, here we do not need, do not understand.

  • GetWindow (). superDispatch TouchEvent (ev)

When you click in, you see that this is an abstract class of Window s.

/**
 * Used by custom windows, such as Dialog, to pass the touch screen event
 * further down the view hierarchy. Application developers should
 * not need to implement or call this.
 *
 */
public abstract boolean superDispatchTouchEvent(MotionEvent event);

We can only look at the implementation classes of Window s.

As you can see, the only implementation class of Windows is PhoneWindow, so let's go into PhoneWindow and find the superDispatch TouchEvent method. The code is as follows:

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    return mDecor.superDispatchTouchEvent(event);
}

Then click on mDecor to see, and you'll find that it's in DecorView:

public boolean superDispatchTouchEvent(MotionEvent event) {
  return super.dispatchTouchEvent(event);
}

Continue to look down and find that it's in ViewGroup:

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
    ...Specific to ViewGroup Explain in Chinese
}

Now let's look at this picture again.

If the dispatch TouchEvent of ViewGroup returns false, then we will execute the onTouchEvent method here, which is handled by our own implementation onTouchEvent method. If the dispatch TouchEvent of ViewGroup returns true, then we will return the true event. It will be executed down, in line with the logic of our last first picture. So let's go down and look at the onTouchEvent method.

  • activity's onTouchEvent() method (default if the process is not rewritten)

Look at the source code:

/**
 * Called when a touch screen event was not handled by any of the views
 * under it.  This is most useful to process touch events that happen
 * outside of your window bounds, where there is no view to receive it.
 *
 * @param event The touch screen event being processed.
 *
 * @return Return true if you have consumed the event, false if you haven't.
 * The default implementation always returns false.
 */
public boolean onTouchEvent(MotionEvent event) {
    if (mWindow.shouldCloseOnTouch(this, event)) {
        finish();
        return true;
    }

    return false;
}

This code indicates that the onTouchEvent method is called when the touch screen event is not processed by any view, that is, when there is no view group, View consumption event, the onTouchEvent method of Activity will execute. If you have consumed this event, return true, and if you do not, return false, the default implementation always returns false. That is, true is returned only when the click event is outside the Window boundary, and false is returned in general.

Let's look at the mWindow.shouldCloseOnTouch(this, event) method.

  • shouldCloseOnTouch() Method of Window s
public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
   if (mCloseOnTouchOutside && event.getAction() == MotionEvent.ACTION_DOWN
            && isOutOfBounds(context, event) && peekDecorView() != null) {
        return true;
    }
    return false;
}

This method is mainly used to determine whether the coordinates of click event + DOWN event + event are in the boundary. If it is outside the boundaries and DOWN events, then it means returning true, then it is consumption events, if not, it returns false, no consumption events, and the event distribution of Activity has been completed here.

Because we have rewritten the onTouchEvent method in MainActivity, when getWindow (). superDispatch TouchEvent (ev) returns to false, the event is handled by the onTouchEvent method in our MainActivity, so look at our last test 3.

And the results of operation are consistent with our analysis above.

II. ViewGroup's Distribution Mechanism for Click Events

Because my api is newer, the code here is Android 7.0, and because the dispatch TouchEvent method in ViewGroup is longer, we only select some key code to analyze how it works.

// Check for interception.
//Define whether a variable is intercepted
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
        || mFirstTouchTarget != null) {
    //If it is a down event and the incoming object is not empty, enter
    final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
    if (!disallowIntercept) {
        //If interception is not prohibited, it is up to your onInterceptTouchEvent method to decide whether to intercept.
        intercepted = onInterceptTouchEvent(ev);
        ev.setAction(action); // restore action in case it was changed
    } else {
        //If interception is prohibited, it means no interception and events are passed down.
        intercepted = false;
    }
} else {
    // There are no touch targets and this action is not an initial down
    // so this view group continues to intercept touches.
    intercepted = true;
}
  • onInterceptTouchEvent Method of ViewGroup
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;
}

Implementing this method intercepts all touchscreen motion events, which allows us to watch events because they are assigned to their children and have ownership of the current gestures at any time. Using this function requires some attention, because it is quite complex, and you will receive a down event here, which will be handled by the child node of the viewgroup, or by your own onTouchEvent() method. This means that you should implement onTouchEvent() to return true. Similarly, by returning true from onTouchEvent(), you will not receive any subsequent events in onInterceptTouchEvent(), and all touch processing must occur in onTouchEvent(), just like normal. As long as you return false from this function, each subsequent event (including the final up) is passed here first, and then to the onTouchEvent() of the target.
The current target will receive ACTION_CANCEL events, and no more messages will be delivered here.

Stealing one of the pictures of a blog on the Internet combined with the conclusion of the analysis

Here I believe you have your own understanding of how ViewGroup distributes events. Next, let's look at how ViewGroup handles events.

3. View's Distribution Mechanism for Click Events

In view of the complexity of Android 7.0's code about View, let's look at Android 2.0's code. Although the code is simple, the workflow must be the same, but later optimized, let's understand the principle.

public boolean dispatchTouchEvent(MotionEvent event) {
   if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED &&
            mOnTouchListener.onTouch(this, event)) {
        return true;
    }
    return onTouchEvent(event);
 }
  • mOnTouchListener

On the assignment of onTouchListener in this method:

public void setOnTouchListener(OnTouchListener l) {
   mOnTouchListener = l;
}

As you can see, as long as we set up setOnTouchListener for view, mOnTouchListener must be valuable.

  • (mViewFlags & ENABLED_MASK) == ENABLED

This condition is to determine whether the control currently clicked is enable d, because since we want to set the click event for this view, then we will definitely give it setOnClickListener, so this condition should be valid.

  • mOnTouchListener.onTouch(this, event)

This event requires us to register onTouchListener and call back the result. For example, we also registered in our last article.

view.setOnTouchListener(new View.OnTouchListener() {
   @Override
    public boolean onTouch(View v, MotionEvent event) {
        System.out.println("Implemented onTouch(), Action is:" + event.getAction());
        return false;
    }
});

We have analysed:
return false; if the above if condition is not valid, the onTouchEvent method will be executed, as we did in our previous validation.

return true; the above conditions are valid, the event is passed on and consumed directly, as is the result of our verification.

Next, let's look at the onTouchEvent method.

  • View.onTouchEvent() method
public boolean onTouchEvent(MotionEvent event) {
    final int viewFlags = mViewFlags;

    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        // A disabled view that is clickable still consumes the touch
        // events, it just doesn't respond to them.
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }

    if (mTouchDelegate != null) {
        if (mTouchDelegate.onTouchEvent(event)) {
            return true;
        }
    }

    if (((viewFlags & CLICKABLE) == CLICKABLE ||
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
        //If view is clickable, including long clicks, enter the switch condition
        switch (event.getAction()) {
            case MotionEvent.ACTION_UP:
                if ((mPrivateFlags & PRESSED) != 0) {
                    // 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 (!mHasPerformedLongPress) {
                        // This is a tap, so remove the longpress check
                       if (mPendingCheckForLongPress != null) {
                           removeCallbacks(mPendingCheckForLongPress);
                        }

                        // Only perform take click actions if we were in the pressed state
                        if (!focusTaken) {
                            //Execution of this method is described in detail.
                            performClick();
                        }
                    }

                    if (mUnsetPressedState == null) {
                        mUnsetPressedState = new UnsetPressedState();
                    }

                    if (!post(mUnsetPressedState)) {
                        // If the post failed, unpress right now
                        mUnsetPressedState.run();
                    }
                }
                break;

            case MotionEvent.ACTION_DOWN:
                mPrivateFlags |= PRESSED;
                refreshDrawableState();
                if ((mViewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) {
                    postCheckForLongClick();
                }
                break;

            case MotionEvent.ACTION_CANCEL:
                mPrivateFlags &= ~PRESSED;
                refreshDrawableState();
                break;

            case MotionEvent.ACTION_MOVE:
                final int x = (int) event.getX();
                final int y = (int) event.getY();

                // Be lenient about moving outside of buttons
                int slop = ViewConfiguration.get(mContext).getScaledTouchSlop();
                if ((x < 0 - slop) || (x >= getWidth() + slop) ||
                        (y < 0 - slop) || (y >= getHeight() + slop)) {
                    // Outside button
                    if ((mPrivateFlags & PRESSED) != 0) {
                        // Remove any future long press checks
                        if (mPendingCheckForLongPress != null) {
                            removeCallbacks(mPendingCheckForLongPress);
                        }

                        // Need to switch from pressed to not pressed
                        mPrivateFlags &= ~PRESSED;
                        refreshDrawableState();
                    }
                } else {
                    // Inside button
                    if ((mPrivateFlags & PRESSED) == 0) {
                        // Need to switch from not pressed to pressed
                        mPrivateFlags |= PRESSED;
                        refreshDrawableState();
                    }
                }
                break;
       }
       //If the control is clickable, it must be return true.
        return true;
    }
    //If the control is not clickable, return false.
    return false;
}
  • Detailed performance Click ()
public boolean performClick() {
   sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

    if (mOnClickListener != null) {
        playSoundEffect(SoundEffectConstants.CLICK);
        mOnClickListener.onClick(this);
        return true;
    }

    return false;
}

If we give view.setOnClickListener(new View.OnClickListener() {}); then mOnClickListener must not be null here, so return true; if click listener is not set, return false; then the event is passed or consumed according to the result of return.

Conclusion:

Through the implementation process of the previous article and the source code analysis of this article, we should be able to clearly understand what the Android event distribution is, the best way to learn is to write a demo, and then go in to see the source code from some unknown places, not all of which need to be understood, find out the key points we care about, and can roughly analyze how to work, wish you well. Everyone enjoys learning.~

Posted by waqasahmed996 on Sun, 19 May 2019 03:52:05 -0700