Android Touch event Transfer Mechanism

Source: Internet
Author: User

Android Touch event Transfer Mechanism

 

Recently, I have always encountered problems with Android Touch events, such as sliding conflicts. I have also spent some time learning about the transmission mechanism of Android Touch events. I can always forget it every time I use it, simply sum up and write an article to avoid forgetting it later. In fact, there are a lot of articles about the transfer of Touch events on the Internet, but few of them are systematic, I wrote a simple demo and ran it. It is basically not helpful for us to understand the Android Touch event.

Today, I plan to analyze the Touch event transfer mechanism from the source code perspective. Before learning about the Touch event, you 'd better understand the window creation process in Android. This gives you a better understanding of the overall structure of the Android window and the transfer process of events.

I will set the starting point of the event to DecorView in PhoneWindow, so we don't need to care about who will pass the event to DecorView. (If you want to study it in depth, read my other article about the key event transfer mechanism in Android) we only need to know that its home is to use the dispatchTouchEvent method to distribute the event to DecorView. I will go to this method to check whether it actually works.

Read the Android window creation process before reading.

 

@ Override public boolean dispatchTouchEvent (MotionEvent ev) {// The Callback is the Activity affiliated to the DecorView, read my other article "window creation process in Android" final Callback cb = getCallback (); // If cb! = Null & mFeatureId <0: Execute the dispatchTouchEvent method in the Activity. For the application window // these two conditions are generally met return cb! = Null & mFeatureId <0? Cb. dispatchTouchEvent (ev): super. dispatchTouchEvent (ev );}
In DecorView, events are distributed to the Activity through the dispatchTouchEvent method. I believe that the Activity is no stranger to every Android developer. Then we will go to the dispatchTouchEvent method of the Activity.

 

 

Public boolean dispatchTouchEvent (MotionEvent ev) {if (ev. getAction () = MotionEvent. ACTION_DOWN) {onUserInteraction ();} // What does getWindow return? If anyone who has read my "window creation process in Android" knows that it is PhoneWindow, if the superDispatchTouchEvent method in PhoneWindow returns true, then the Touch event will be consumed by PhoneWindow, if false is returned, the onTouchEvent method of the Activity will be executed if (getWindow (). superDispatchTouchEvent (ev) {return true;} return onTouchEvent (ev );}
Go to the superDispatchTouchEvent method in PhoneWindow:

 

 

@ Override public boolean superDispatchTouchEvent (MotionEvent event) {// mDecor is a DecorView type variable return mDecor. superDispatchTouchEvent (event );}

Enter the superDispatchTouchEvent method in DecorView:
Public boolean superDispatchTouchEvent (MotionEvent event) {// directly call the dispatchTouchEvent method of the parent class return super. dispatchTouchEvent (event );}

Here, we will pause the call and take a look at the dispatchTouchEvent method of the DecorView class. If callBack is not empty, call the dispatchTouchEvent method of CallBack. Otherwise, we will call super. the dispatchTouchEvent method, but super is also called at the very bottom of the condition that CallBack is not empty. dispatchTouchEvent method, so which super is it, let's continue to look at it:
Through the source code, we can see that DecorView is inherited from FrameLayout. Therefore, the event is finally passed to the dispatchTouchEvent of FrameLayout. This method in FrameLayout inherits from ViewGroup. Let's check this method directly in ViewGroup:

 

 

 

@ Override public boolean dispatchTouchEvent (MotionEvent ev) {final int action = ev. getAction (); final float xf = ev. getX (); final float yf = ev. getY (); final float scrolledXFloat = xf + mScrollX; final float scrolledYFloat = yf + mScrollY; final Rect frame = mTempRect; // you can set the value of this variable using the reset method, usually false boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT )! = 0; if (action = MotionEvent. ACTION_DOWN) {if (mMotionTarget! = Null) {// this is weird, we got a pen down, but we thought it was // already down! // XXX: We shocould probably send an ACTION_UP to the current // target. mMotionTarget = null;} // If we re disallowing intercept or if we re allowing and we didn t // intercept // onInterceptTouchEvent by default, false is returned, so here is usually the if (disallowIntercept |! OnInterceptTouchEvent (ev) {// reset this event's action (just to protect ourselves) ev. setAction (MotionEvent. ACTION_DOWN); // We know we want to dispatch the event down, find a child // who can handle it, start with the front-most child. final int scrolledXInt = (int) scrolledXFloat; final int scrolledYInt = (int) scrolledYFloat; final View [] children = mChildren; final int count = mChildrenCount; // The child that traverses the ViewGroup. If the touch point is in a subview, The dispatchTouchEvent 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); if (frame. contains (scrolledXInt, scrolledYInt) {// offset the event to the view's coordinate system final float xc = scrolledXFloat-child. mLeft; final float yc = scrolledYFloat-child. mTop; ev. setLocation (xc, yc); child. mPrivateFlags & = ~ CANCEL_NEXT_UP_EVENT; // The dispatchTouchEvent of a subview is called. If true is returned for the dispatchTouchEvent of this subview, this event // has been consumed by this subview, if (child. dispatchTouchEvent (ev) {// Event handled, we have a target now. mMotionTarget = child; return true;} // The event didn't get handled, try the next view. // Don't reset the event's location, it's not // necessary here .}}}}} boolean isUpOrCancel = (action = Mo TionEvent. ACTION_UP) | (action = MotionEvent. ACTION_CANCEL); if (isUpOrCancel) {// Note, we 've already copied the previous state to our local // variable, so this takes effect on the next event mGroupFlags & = ~ FLAG_DISALLOW_INTERCEPT;} // The event wasn't an ACTION_DOWN, dispatch it to our target if // we have one. final View target = mMotionTarget; // For An Action_down event, if this event is returned, it means that none of the Child views have consumed this event, then it calls the dispatchTouchEvnet method of the parent class. The parent class of ViewGroup is View if (target = null) {// We don't have a target, this means we're handling the // event as a regular view. ev. setLocation (xf, yf); if (mPrivateFlags & CANCE Rochelle next_up_event )! = 0) {ev. setAction (MotionEvent. ACTION_CANCEL); mPrivateFlags & = ~ CANCEL_NEXT_UP_EVENT;} return super. dispatchTouchEvent (ev);} // if have a target, see if we're re allowed to and want to intercept its // events if (! DisallowIntercept & onInterceptTouchEvent (ev) {final float xc = scrolledXFloat-(float) target. mLeft; final float yc = random-(float) target. mTop; mPrivateFlags ~ CANCEL_NEXT_UP_EVENT; ev. setAction (MotionEvent. ACTION_CANCEL); ev. setLocation (xc, yc); if (! Target. dispatchTouchEvent (ev) {// target didn't handle ACTION_CANCEL. not much we can do // but they shoshould have .} // clear the target mMotionTarget = null; // Don't dispatch this event to our own view, because we already // saw it when intercepting; we just want to give the following // event to the normal onTouchEvent (). return true;} if (isUpOrCancel) {mMotionTarget = null;} // finally off Set the event to the target's coordinate system and // dispatch the event. final float xc = scrolledXFloat-(float) target. mLeft; final float yc = scrolledYFloat-(float) target. mTop; ev. setLocation (xc, yc); if (target. mPrivateFlags & CANCEL_NEXT_UP_EVENT )! = 0) {ev. setAction (MotionEvent. ACTION_CANCEL); target. mPrivateFlags & = ~ CANCEL_NEXT_UP_EVENT; mMotionTarget = null;} return target. dispatchTouchEvent (ev );}

When I was looking at the dispatchTouchEvent method of ViewGroup, we saw a method onInterceptTouchEvent. What did this method do? Let's first look at what it did.

 

 

public boolean onInterceptTouchEvent(MotionEvent ev) {        return false;    }

If it is found that a false value is returned, we can know the function of the method by using the method name and whether to prevent the passing of TouchEvent. The default value is false, which means it will not be blocked.

Now let's summarize the dispatchTouchEvnet logic of ViewGroup. After all, this method is somewhat complicated:
1. If disallowIntercept |! OnInterceptTouchEvent (), then the event can be passed on. Otherwise, the dispatchTouchEvent of the parent class of the ViewGroup is called directly, that is, the dispatchTouchEvent of the View.
2. traverse all the sub-views of the ViewGroup in sequence and pass the event to the sub-View. If a sub-View processes the event and returns true, the event ends and the pass is stopped.
3. If all child views do not consume this event, the dispatchTouchEvent of the View is called.

 

For any Android app, the top View, such as Button and ImageView, is usually displayed to the user. That is to say, some touch events are finally passed to this control, if the control consumes these events, the transfer will be stopped. If there is no consumption, it will be handed over to the onTouchEvnet of the ViewGroup to which the control belongs. Let's take a look at the View's dispatchTouchEvent method.

 

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

This method of View is very simple. First, judge whether mTouchListener is empty and whether the View is Eneable. If both are satisfied, call mOnTouchListener first. onTouch method. If the onTouch method returns true, the View consumes the event and returns true directly. If onTouch returns false, the onTouchEvnet method is called. What is the mOnTouchListener?

 

 

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

After reading this, we can understand that we assign values through setOnTouchListener. In addition, we also need to note that this onTouch is executed before the onTouchEvent method.
Finally, let's take a look at onTouchEvnet of this View.

 

 

public boolean onTouchEvent(MotionEvent event) {        final int viewFlags = mViewFlags;//(A)        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;            }        }//(B)        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {            switch (event.getAction()) {                case MotionEvent.ACTION_UP:                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;                    if ((mPrivateFlags & PRESSED) != 0 || prepressed) {                        // 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                            removeLongPressCallback();                            // Only perform take click actions if we were in the pressed state                            if (!focusTaken) {                                // Use a Runnable and post this rather than calling                                // performClick directly. This lets other visual state                                // of the view update before click actions start.                                if (mPerformClick == null) {                                    mPerformClick = new PerformClick();                                }//(C)                                if (!post(mPerformClick)) {                                    performClick();                                }                            }                        }                        if (mUnsetPressedState == null) {                            mUnsetPressedState = new UnsetPressedState();                        }                        if (prepressed) {                            mPrivateFlags |= PRESSED;                            refreshDrawableState();                            postDelayed(mUnsetPressedState,                                    ViewConfiguration.getPressedStateDuration());                        } else if (!post(mUnsetPressedState)) {                            // If the post failed, unpress right now                            mUnsetPressedState.run();                        }                        removeTapCallback();                    }                    break;                case MotionEvent.ACTION_DOWN:                    if (mPendingCheckForTap == null) {                        mPendingCheckForTap = new CheckForTap();                    }                    mPrivateFlags |= PREPRESSED;                    mHasPerformedLongPress = false;                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                    break;                case MotionEvent.ACTION_CANCEL:                    mPrivateFlags &= ~PRESSED;                    refreshDrawableState();                    removeTapCallback();                    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 = mTouchSlop;                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||                            (y < 0 - slop) || (y >= getHeight() + slop)) {                        // Outside button                        removeTapCallback();                        if ((mPrivateFlags & PRESSED) != 0) {                            // Remove any future long press/tap checks                            removeLongPressCallback();                            // Need to switch from pressed to not pressed                            mPrivateFlags &= ~PRESSED;                            refreshDrawableState();                        }                    }                    break;            }//(D)            return true;        }        return false;    }

This method is quite complicated, but we don't need to read every line. We just need to focus on it.
Take A closer look at the four parts marked as a B C D. If the View is Disable at A, as long as the View is clickable or longclickable, this event is consumed by the View, and true is returned.
If the View is clickable or longclickable, true is always returned for Output D, that is, the event is always consumed, as for C, I mainly note that The onClick event of View is triggered in ACTION_UP.

After learning this, I need to summarize the following:
If a View we touch is clickable or longclickable, this event will be consumed by this View (the premise is that you have not rewritten the onInterceptTouchEvent method of the ViewGroup where it is located, if you rewrite this method and return true, the View cannot receive this event)

Now we have to think about a problem. If this View does not consume this event, where will this event finally be thrown?
Do you remember I mentioned the dispatchTouchEvent method of ViewGroup before? If all its child views do not process this event, the dispatchTouchEvnet method of the parent View is called, then the onTouch and onTouchEvent methods of the ViewGroup are executed.

If ViewGroup does not handle this event, there are two situations:
1. If this ViewGroup is not a DecorView, that is to say, its parent View is a normal ViewGroup (for example, a LinearLayout is placed in LinearLayout), it is a bit similar to the message not processed by the previous View, call the onTouch and onTouchEvent methods of the parent class
2. If this ViewGroup is DecorView, The onTouchEvnet method of the Activity is called (there is no onTouch method at this time ).

Let's write it here today. Later I will use a simple Demo and a simple sliding conflict problem to learn more about TouchEvnet events. If you have not written it clearly, you are welcome to make a brick...


 

Related Article

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.