Android event Distribution

Source: Internet
Author: User
Tags gety

Android event Distribution

Methods related to Touch events in Android include dispatchTouchEvent (MotionEvent ev), onInterceptTouchEvent (MotionEvent ev), and onTouchEvent (MotionEvent ev)

From this table, we can see that ViewGroup and its subclass can respond to the three methods related to Touch events, while Activity does not respond to onInterceptTouchEvent (MotionEvent ev), that is, event interception. In addition, you must note that the View responds to dispatchTouchEvent (MotionEvent ev) and onInterceptTouchEvent (MotionEvent ev) only if you can add a subview to the View, if the current View is already a smallest unit View (such as TextView), you cannot add a subview to the smallest View, and you cannot distribute or intercept events to the subview, therefore, it does not have dispatchTouchEvent (MotionEvent ev) and onInterceptTouchEvent (MotionEvent ev), and only onTouchEvent (MotionEvent ev ).

Touch Event Analysis
Event distribution: public boolean dispatchTouchEvent (MotionEvent ev)
DispatchTouchEvent (MotionEvent ev) of the Activity when the Touch event occurs) the method will be in the tunnel mode (pass down from the root element until the inmost layer sub-element or stop passing because of a certain condition in the middle of a mona1 element) the event is passed to the dispatchTouchEvent (MotionEvent ev) method of the outermost View, and the dispatchTouchEvent (MotionEvent ev) method of the View distributes the event. The event distribution logic of dispatchTouchEvent is as follows:

If return true, the event is distributed to the current View and consumed by the dispatchTouchEvent method. At the same time, the event stops being passed down;
If return false, event distribution is divided into two situations:
If the event retrieved by the current View is directly from the Activity, the event is returned to the onTouchEvent of the Activity for consumption;
If the event retrieved by the current View comes from the outer parent control, the event is returned to the onTouchEvent of the parent View for consumption.
If the system returns the default super. dispatchTouchEvent (ev), the event is automatically distributed to the onInterceptTouchEvent method of the current View.

Event interception: public boolean onInterceptTouchEvent (MotionEvent ev)
When the dispatchTouchEvent (MotionEvent ev) method of the outer View returns the system's default super. dispatchTouchEvent (ev), the event is automatically distributed to the onInterceptTouchEvent method of the current View. The event interception logic of onInterceptTouchEvent is as follows:

If onInterceptTouchEvent returns true, it intercepts the event and submits the intercepted event to the onTouchEvent of the current View for processing;
If onInterceptTouchEvent returns false, the event is released. The event on the current View is passed to the subview, And the dispatchTouchEvent of the subview is used to distribute the event;
If onInterceptTouchEvent returns super. onInterceptTouchEvent (ev), the event is intercepted by default, and the intercepted event is handed over to the onTouchEvent of the current View for processing.

Event Response: public boolean onTouchEvent (MotionEvent ev)
When dispatchTouchEvent returns super. dispatchTouchEvent (ev) and onInterceptTouchEvent returns true or super. onInterceptTouchEvent (ev), onTouchEvent is called. The Event Response logic of onTouchEvent is as follows:

If the event is passed to the onTouchEvent method of the current View, and the method returns false, the event will be passed up from the current View and received by the onTouchEvent of the upper View, if the onTouchEvent passed to the above method also returns false, the event will "disappear" and the next event will not be received.
If true is returned, the event is received and consumed.
If super. onTouchEvent (ev) is returned, the default event processing logic is the same as when false is returned.

Next, we will analyze event distribution from the source code perspective:
For example, you have a very simple project with only one Activity and only one button in the Activity. You may already know that if you want to register a click event and a Touch event for this button:

button.setOnClickListener(new OnClickListener() {      @Override      public void onClick(View v) {          Log.d("TAG", "onClick execute");      }  });
button.setOnTouchListener(new OnTouchListener() {      @Override      public boolean onTouch(View v, MotionEvent event) {          Log.d("TAG", "onTouch execute, action " + event.getAction());          return false;      }  });

Run the program and click the button to print the result as follows:

As you can see, onTouch takes precedence over onClick, and onTouch executes twice, one is ACTION_DOWN, and the other is ACTION_UP (You may also execute ACTION_MOVE multiple times, if you shake your hand ). Therefore, the order of event transfer is first passed through onTouch, and then passed to onClick.
Careful friends should be able to notice that the onTouch method has a return value. Here we return false. If we try to change the return value in the onTouch method to true, run it again and the result is as follows:

We found that the onClick method is no longer executed! Why? You can first understand that the onTouch method returns true and the event is consumed by onTouch, so it will not continue to be passed down.
First, you need to know that as long as you touch any control, the dispatchTouchEvent method of the control will be called. When we click the Button, we will call the dispatchTouchEvent method in the Button class, but you will find that this method is not used in the Button class, then you can find it in its parent class TextView, and you will find that there is no such method in TextView, so there is no way, I had to find it in the parent Class View of TextView. At this time, you finally found this method in the View, as shown below:

Next, let's take a look at the source code of the dispatchTouchEvent method in the View:

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

This method is very concise, with only a few lines of code! We can see that in this method, we first make a judgment, if mOnTouchListener! = Null, (mViewFlags & ENABLED_MASK) = ENABLED and mOnTouchListener. if all three conditions onTouch (this, event) are true, true is returned. Otherwise, the onTouchEvent (event) method is executed and the result is returned.
Let's take a look at the first condition. Where is the mOnTouchListener variable assigned a value? We found the following method in View after searching:

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

The second condition (mViewFlags & ENABLED_MASK) = ENABLED is used to determine whether the currently clicked control is enable. By default, all buttons are enable, so this condition is always true.

The third condition is critical. mOnTouchListener. onTouch (this, event) is actually the onTouch method when the callback control registers the touch event. That is to say, if we return true in the onTouch method, all three conditions will be set up, so that the entire method returns true directly. If we return false in the onTouch method, the onTouchEvent (event) method will be executed again.

Now we can analyze it with the previous example. First, the onTouch method is executed first in dispatchTouchEvent. Therefore, onTouch must take precedence over onClick, it also confirms the printed result. If true is returned in the onTouch method, the dispatchTouchEvent method will directly return true without further execution. The printed results also confirm that if onTouch returns true, onClick will not be executed again.

Based on the above source code analysis, the running results of the previous example are explained in principle. The above analysis also reveals an important piece of information, that is, the onClick call must be in the onTouchEvent method! Now let's take a look at the onTouchEvent source code, as shown below:

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)) {          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();                              }                              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;          }          return true;      }      return false;  }  

Compared with the dispatchTouchEvent method, the onTouchEvent method is much more complicated, but it doesn't matter. We just need to focus on it.
First, we can see in row 14th that if the control is clickable, it will enter the switch judgment in row 16th, and if the current event is to raise your finger, will enter the MotionEvent. ACTION_UP in this case. After various judgments, we will execute the merge mclick () method of row 38th. Let's go into this method and take a look:

public boolean performClick() {      sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);      if (mOnClickListener != null) {          playSoundEffect(SoundEffectConstants.CLICK);          mOnClickListener.onClick(this);          return true;      }      return false;  } 

As you can see, as long as mOnClickListener is not null, it will call its onClick method.
Everything is so clear! When we call the setOnClickListener method to register a click event for the control, mOnClickListener is assigned a value. Then, when a control is clicked, The onClick method of the clicked control is called back in the receivmclick () method.

In this way, we can figure out the entire event distribution process of the View! Don't be happy too early. It's not over yet. Another important point to note is the hierarchical transmission of touch events. We all know that if a touch event is registered for a control, a series of ACTION_DOWN, ACTION_MOVE, ACTION_UP and other events will be triggered each time you click it. Note that if you return false when executing ACTION_DOWN, the subsequent actions will no longer be executed. Simply put, when dispatchTouchEvent is used for event distribution, only the previous action returns true to trigger the next action.
At this point, many of my friends must have great questions. Isn't that self-contradictory? In the previous example, when the onTouch event returns false, does ACTION_DOWN and ACTION_UP get executed? In fact, you are only confused by the illusion. Let's take a closer look at what we actually returned in the previous example.
Refer to the source code we analyzed earlier. First, if false is returned in the onTouch event, it will definitely enter the onTouchEvent method. Then let's take a look at the details of the onTouchEvent method. As we click the button, it will enter the internal judgment of the if statement in row 14th, and then you will find that no matter what the current action is, it will eventually go to row 89th, returns true.
Is there a feeling of being cheated? If false is returned in the onTouch event, the system returns true in the onTouchEvent method. For this reason, the ACTION_UP in the preceding example can be executed.
Then we can change the control, replace the button with ImageView, register a touch event for it, and return false.

Run the program and click ImageView. The result is as follows:

After ACTION_DOWN is executed, all subsequent actions are not executed. Why? Because ImageView is different from the button, it cannot be clicked by default. Therefore, when the onTouchEvent row 14th is judged, it cannot enter the if internal state. if it is jumped directly to row 91st, false is returned, as a result, other subsequent actions cannot be executed.
Now, all the things I want to talk about View event distribution are here. Now let's review the three questions mentioned at the beginning. I believe everyone will have a deeper understanding.

Now we can use a Demo to demonstrate the VewGroup event distribution process in Android.
First, we define a layout named MyLayout, inherited from LinearLayout, as shown below: <喎?http: www.bkjia.com kf ware vc " target="_blank" class="keylink"> VcD4KCgoKPHByZSBjbGFzcz0 = "brush: java;"> public class MyLayout extends LinearLayout { public MyLayout(Context context, AttributeSet attrs) { super(context, attrs); } }

We added two buttons in MyLayout, and then registered the listening events for both the buttons and MyLayout in MainActivity:

myLayout.setOnTouchListener(new View.OnTouchListener() {            @Override            public boolean onTouch(View v, MotionEvent event) {                Log.i("========","myLayout onTouch");                return false;            }        });        bt1.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.i("========", "Button1 onClick");            }        });        bt2.setOnClickListener(new View.OnClickListener() {            @Override            public void onClick(View v) {                Log.i("========","Button2 onClick");            }        });

Click "Button1", "Button2", and "blank area" respectively. The output is as follows:

You will find that when you click the button, the onTouch method registered by MyLayout will not be executed, and this method will be executed only when you click the blank area. You can first understand that The onClick method of the Button consumes the event, so the event will not continue to be passed down.

You can see that ViewGroup has an onInterceptTouchEvent method. Let's take a look at the source code of this method:

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

If you do not look at the source code, you may be scared by this comment, so long English comments are too big. But the source code is so simple! Only one line of code returns a false value!
Well, since it is a Boolean return, there are only two possibilities. We will re-write this method in MyLayout and return a true value. The Code is as follows:

public class MyLayout extends LinearLayout {      public MyLayout(Context context, AttributeSet attrs) {          super(context, attrs);      }      @Override      public boolean onInterceptTouchEvent(MotionEvent ev) {          return true;      }  } 

Run the project again, and then run Button1, Button2, and the blank area respectively. The output is as follows:

You will find that, no matter where you click, the touch event of MyLayout will always be triggered, and the button clicking event will be completely blocked! Why? If the touch event in Android is first transmitted to the View and then to the ViewGroup, how can MyLayout block the Button click event?
It seems that only by reading the source code and figuring out the ViewGroup event distribution mechanism in Android can we solve our concerns, but here I 'd like to tell you first, in Android, the touch event is first transmitted to the ViewGroup and then to the View. If you touch any control, the dispatchTouchEvent method of the control will be called. This statement is true, but not complete. The actual situation is that when you click a control, the dispatchTouchEvent method of the layout where the control is located will be called first, and then the corresponding control to be clicked will be found in the dispatchTouchEvent method of the layout, then call the dispatchTouchEvent method of the control. If we click the button in MyLayout, The dispatchTouchEvent method of MyLayout will be called first, but you will find that this method is not used in MyLayout. Then find it in its parent class LinearLayout, and find that there is no such method. So we have to continue looking for the parent ViewGroup of LinearLayout. You finally saw this method in ViewGroup. The dispatchTouchEvent method of the button is called here.

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;      boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;      if (action == MotionEvent.ACTION_DOWN) {          if (mMotionTarget != null) {              mMotionTarget = null;          }          if (disallowIntercept || !onInterceptTouchEvent(ev)) {              ev.setAction(MotionEvent.ACTION_DOWN);              final int scrolledXInt = (int) scrolledXFloat;              final int scrolledYInt = (int) scrolledYFloat;              final View[] children = mChildren;              final int count = mChildrenCount;              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)) {                          final float xc = scrolledXFloat - child.mLeft;                          final float yc = scrolledYFloat - child.mTop;                          ev.setLocation(xc, yc);                          child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;                          if (child.dispatchTouchEvent(ev))  {                              mMotionTarget = child;                              return true;                          }                      }                  }              }          }      }      boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||              (action == MotionEvent.ACTION_CANCEL);      if (isUpOrCancel) {          mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;      }      final View target = mMotionTarget;      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);      }      if (!disallowIntercept && onInterceptTouchEvent(ev)) {          final float xc = scrolledXFloat - (float) target.mLeft;          final float yc = scrolledYFloat - (float) target.mTop;          mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;          ev.setAction(MotionEvent.ACTION_CANCEL);          ev.setLocation(xc, yc);          if (!target.dispatchTouchEvent(ev)) {          }          mMotionTarget = null;          return true;      }      if (isUpOrCancel) {          mMotionTarget = null;      }      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);  }

This method has a long code, so let's focus on it. First, you can see a condition judgment in row 13th. If disallowIntercept and! If either onInterceptTouchEvent (ev) is true, the condition is determined. DisallowIntercept indicates whether to disable event interception. The default value is false. You can also call the requestDisallowInterceptTouchEvent method to modify this value. When the first value is false, it depends entirely on the second value to determine whether it can enter the condition judgment. What is the second value? It turns out to be an inverse of the returned value of the onInterceptTouchEvent method! That is to say, if we return false in the onInterceptTouchEvent method, the second value will be set to true to enter the condition judgment. If we return true in the onInterceptTouchEvent method, the second value is set to false, thus jumping out of this condition.
Now you can think about it. Because we have just rewritten the onInterceptTouchEvent method in MyLayout, so that this method returns true, all Button clicking events are blocked, then we have a reason to believe that the processing of button-click events is performed within the 13th row condition judgment!
Let's focus on how the internal conditions are implemented. In row 19th, A for loop is used to traverse all the sub-views in the current ViewGroup, and then the row 24th is used to determine whether the View being traversed is the View being clicked, if yes, it will enter the internal judgment of the condition, and then the dispatchTouchEvent of the View is called in row 29th. Therefore, we confirmed that, the button clicking event is indeed handled here.
Note that the returned value is returned after the dispatchTouchEvent of the sub-View is called. As we know, if a control can be clicked, the return value of dispatchTouchEvent must be true when the control is clicked. Therefore, the condition judgment of the 29th rows is true. Therefore, the dispatchTouchEvent method of the ViewGroup is directly returned to the 31st rows. In this way, the subsequent Code cannot be executed, and the result printed by the Demo above is also confirmed. If the Click Event of the button is executed, the touch event of MyLayout will be intercepted.
What if we click a blank area instead of a button? In this case, the system will not return true In line 3, but will continue to execute the subsequent code. Then let's continue to look back. In row 44th, if the target is null, it will enter the condition to judge the interior. Generally, the target will be null here, therefore, super. dispatchTouchEvent (ev ). Where can this code be called? Of course it is the dispatchTouchEvent method in View, because the parent class of ViewGroup is View. The subsequent processing logic is the same as previously mentioned, so the onTouch method registered in MyLayout will be executed. The subsequent code is generally inaccessible, so we will not continue to analyze it.

Now the analysis of the event distribution process of the entire ViewGroup is over. Let's take a look at it.
1. Android event distribution is first transmitted to ViewGroup and then to View by ViewGroup.
2. in ViewGroup, The onInterceptTouchEvent method can be used to intercept event transmission. The onInterceptTouchEvent method returns true, indicating that the event is not allowed to continue to be passed to the subview. If false is returned, the event is not blocked. The default value is false.
3. If the subview consumes the passed events, the ViewGroup will not be able to receive any events.

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.