This is the second day of my participation in Gwen Challenge

First, who are the people to whom events are distributed?

Events.

Touch events are generated when the user touches the screen (a control derived from a View or ViewGroup)

Touch event-related details, such as Touch location, time, gesture, and so on, are encapsulated as MotionEvent objects.

Touch events are mainly as follows:

The event Introduction to the
ACTION_DOWN fingerYour first exposure to a screenWhen triggered.
ACTION_MOVE fingerSwipe across the screenTrigger, will trigger multiple times.
ACTION_UP fingerLeave the screenWhen triggered.
ACTION_CANCEL The eventIntercepted by upper levelsWhen triggered.

Event column: The process from finger touching the screen to finger leaving the screen generates a series of times. Any time starts with a Down event and ends with an UP event, with numerous Move events in between.

That is, when a MotionEvent is generated, the system needs to pass that event to a specific View to process.


What is event distribution?

To understand what event distribution is, it is the process of distributing the MotionEvent event, that is, when a finger is pressed, the system needs to pass the event to a specific View, and this process is the distribution process.

The distribution order is Activity(Window)-> ViewGroup -> View.

Three methods: – >(Getting started)

When it comes to the distribution process, there are three methods:

  1. dispatchTouchEvent,
  2. onInterceptTouchEvent
  3. onTouchEvent

Here’s what these three methods do:

dispatchTouchEvent

If the event can be passed to the current View, then this method must be called. The return result is affected by the current View’s onTouchEvent and the subordinate View’s dispatchTouchEvent method, indicating whether the current event is consumed.

onInterceptTouchEvent

Called within dispatchTouchEvent to determine whether to intercept an event. If the current View intercepted an event, this method is not called again within the same sequence of events and returns the result indicating whether to intercept the current event.

onTouchEvent

Called in the ‘dispatchTouchEvent ‘method to handle the click event and returns a result indicating whether the current event is consumed. If not, the current View cannot receive the event again in the same sequence of events.

Let me illustrate it with a graph

√ indicates that the method exists.

X means there is no such method.

type Relevant methods ViewGroup View
Dispatching events dispatchTouchEvent Square root Square root
Events to intercept onInterceptTouchEvent Square root X
The event consumer onTouchEvent Square root Square root

I don’t know if you’re wondering why the View has a dispatchTouchEvent method.

A View can register many listeners, such as click, long press, and touch events, and the View itself also has onTouchEvent methods, so the question is, who should manage so many event-related methods, so the View also has this method.

The relationship between them can be seen through the following pseudocode:

// The dispatchTouchEvent () method is called directly after the click event occurs
public boolean dispatchTouchEvent(MotionEvent ev) {

    // indicates whether events are consumed
    boolean consume = false;


    if (onInterceptTouchEvent(ev)) {
    // If onInterceptTouchEvent() returns true, the View intercepts the click event
    // The click event is handed to the current View for processing
    // Call onTouchEvent () to handle the click event
      consume = onTouchEvent (ev) ;

    } else {
      // If onInterceptTouchEvent() returns false, the View does not intercept the click event
      // The click event is passed on to its children
      // The dispatchTouchEvent () of the child element is called, repeating the process
      // Until the click event is finally processed
      consume = child.dispatchTouchEvent (ev) ;
    }

    return consume;
   }
Copy the code

As you can see from the pseudocode above, for a root ViewGroup, the click event is first delivered to it, and its dispatchTouEvent is called, If the onInterceptTouchEvent method of the ViewGroup returns true, it intercepts the current event. The event is then handed over to the ViewGroup, and its onTouchEvent method is called. If the onInterceptTouchEvent method of the ViewGroup returns false, it does not intercept the current event. The current event is passed on to its children, and the child dispatchTouEvent method is called. This is repeated until the event is finally processed.

To illustrate this, use a moving event distribution flow chart:

When a View needs to handle an event, if it has OnTouchListener set, the onTouch method in OnTouchListener is called back. If false, the current View’s onTouchEvent method will be called. If true is returned, the onTouchEvent method will not be called. As you can see, setting onTouchListener to the View has a higher priority than onTouchEvent. In the onTouchEvent method, if onClickListener is currently set, its onClick method will be called. As you can see, onClickListener, which we usually use, has the lowest priority, that is, at the end of event delivery.

Through the following source code can be seen:

public boolean dispatchTouchEvent(MotionEvent event) {... ListenerInfo li = mListenerInfo;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;
}
Copy the code

The following conclusions can be drawn about the event delivery mechanism, from which we can better understand the overall delivery mechanism :(excerpt from Android development art exploration)

  1. The same event sequence refers to a series of time generated in this process from the moment when the finger touches the screen to the moment when the finger leaves the screen. This event sequence starts with down event, contains an indefinite number of move events in the middle, and finally ends with up event.
  2. Normally, a sequence of events can only be intercepted and consumed by one View. See (3) for this reason, because once an element intercepts an event, all events in the same event sequence are directly assigned to it for processing. Therefore, events in the same event sequence cannot be processed by two views at the same time, but it can be done by special means. For example, a View forces an event to be handled by another View via onTouchEvent.
  3. Once a View decides to intercept, it can only handle a sequence of events (if the sequence can be passed to it), and its onInterceptTouchEvent will not be called again. When a View intercepts an event, all other methods in the same event sequence are handled directly by the View. Therefore, the View’s onInterceptTouchEvent is no longer called to ask if it should intercept.
  4. Once a View has started processing events, if it does not consume ACTION_DOWN events (onTOuchEvent returns false), then no other events in the same sequence of events will be assigned to it and the event will be reassigned to its parent. The onTouchEvent for the parent element will be called. This means that once an event is assigned to a View, it must be consumed, or the rest of the sequence of events in the same sequence will not be assigned to it. This is like a superior giving a task to a program ape. If the task is not handled well, the superior will not dare to give the task to the program ape in the short term. It’s the same thing.
  5. If the View consumes no events other than ACTION_DOWN, the click event disappears, the parent element’s onTouchEvent is not called, and the current View continues to receive subsequent events. Eventually these missing click events are passed to the Activity processing.
  6. ViewGroup does not intercept any events by default. The onInterceptTouchEvent method of the ViewGroup returns false by default.
  7. The View does not have an onInterceptTouchEvent method. Its onTouchEvent method is called whenever a hit event is passed to it.
  8. The View’s onTouchEvent will consume the event by default (returning true), Unless it is unclickable (both clickable and longClickable are false). The longClickable property of the View defaults to false, depending on the case. For example, Button clickable defaults to true and TextView clickable defaults to false.
  9. The enable property of the View is not affected by the default return value of onTouchEvent, even if a View is disabled, As long as either its Clickable or longClickable is true, its onTouchEvent returns true.
  10. OnClick can only happen if the current View is clickable and it receives down and Up events.
  11. Events are from the outside to the delivery process, namely the event always passed to the parent element, and then distributed to the View, by the parent element through requestDisallowInterceptTouchEvent method can intervene in the parent element in a child element distribution of events, Except for ACTION_DOWN events.

Example: -> (practice)

With the above conclusion, let’s use an example to demonstrate

So let’s start with a picture like this

This is a simple layout, the Activity inside a LinearLayout, used to replace the ViewGroup, inside is a Button, nothing to say.

We should explore these conclusions from the following aspects.

  1. Event distribution by default
  2. Distribution at the time of interception
  3. What if the viewagroup does not intercept the Down event, i.e. the Button handles it, but it intercepts the next move event (i.e. the intercept)?
  4. If no event is consumed at all, who ultimately arranges the event.
  5. If you return true or false in onTouch, what does that do to onTouchEvent?

Code:

First inherited from LinearLayout, in order to rewrite the corresponding three methods.

/ * * *@authorPetterp on 2019/7/2 * Summary: Rewrite the LinearLayout corresponding method * mailbox: [email protected] */
public class LinearLayoutView extends LinearLayout {
    public LinearLayoutView(Context context) {
        super(context);
    }

    public LinearLayoutView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    /** * event distribution **@param ev
     * @returnWhether to consume the current event * true-> consume the event, subsequent events continue to be distributed to the view * false-> */
    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        return super.dispatchTouchEvent(ev);
    }

    /** * event interception, that is, handle the event yourself **@param ev
     * @returnWhether to intercept the current event * true-> the event stops passing and executes its own onTouchEvent, which will not be called later * false -> The event continues passing and calls the child view.dispatchTouchEvent(), which will still be called later */
    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        Log.e("demo"."viewgroup-onInterceptTouchEvent: "+ ViewActivity.mode);
        return ViewActivity.mode;
    }

    /** * handle the click event **@param event
     * @returnWhether to consume the current event * true-> the event stops passing and subsequent events are handled by it * false-> Do not process the event and the event is handled by the parent onTouchEvent(). This view no longer accepts other events in this event column */
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event); }}Copy the code

Then the Activity:

public class ViewActivity extends AppCompatActivity {
    / / mode flag bit
    public static boolean mode=false;


    @SuppressLint("ClickableViewAccessibility")
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_view);
        Button btn_1=findViewById(R.id.btn_t1);
        LinearLayoutView linearLayout=findViewById(R.id.ln_group);

        / / LinearLayout viewGroup
        linearLayout.setOnClickListener(v -> Log.e("demo"."viewgroup-onClick"));
        linearLayout.setOnTouchListener((v, event) -> {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    Log.e("demo"."Viewgroup - Finger press");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("demo"."Viewgroup - Finger release");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e("demo"."Viewgroup - Finger move");
                    break;
            }
            return false;
        });



        / / button that is the view
        btn_1.setOnClickListener(v -> Log.e("view-demo"."onClick"));
        btn_1.setOnTouchListener((v, event) -> {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    Log.e("demo"."View - Finger down");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("demo"."View - Let go of your finger");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e("demo"."View - Finger move");
                    break;
                case MotionEvent.ACTION_CANCEL:
                    Log.e("demo"."View - truncated by parent view");
                    break;
            }
            return false;
        });
    }

    /** * Rewrite the Activity onTouchEvent to simulate the processing of a child view that does not consume events@param event 
     * @return* /
    @Override
    public boolean onTouchEvent(MotionEvent event) {
        Log.e("demo"."Activity- Digest yourself");
        return super.onTouchEvent(event); }}Copy the code

The corresponding notes are concise and needless to say.

xml


      
<com.petterp.studybook.View.LinearLayoutView xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:custom="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:id="@+id/ln_group"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    tools:context=".View.ViewActivity">

    <Button
        android:id="@+id/btn_t1"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Button"
        tools:ignore="HardcodedText" />
</com.petterp.studybook.View.LinearLayoutView>
Copy the code

Let’s first look at the event distribution process by default:

Finger -> Button, then click the blank (Viewgroup). Observe log printing:

Conclusion: By default, the ViewGroup interceptor returns false, the event is passed to the child’s dispatchTouchEvent and continues to be distributed until it is consumed by the child’s onTouchEvent, at which point the onClick method is called, So onclick is the lowest priority.


What about event distribution in case of interception, such as onInterceptTouchEvent of ViewGroup returning true?

Change the code

/ / mode flag bit
public static boolean mode=true;
Copy the code

The default is false, of course this is a flag bit I wrote myself, which is definitely not true. Just for simulation. Now change it to true, so the onInterceptTouchEvent on the LinearLayout will return true, that is, the ViewGroup consumed the event.

Finger -> Button, then click blank (ViewGroup), watch log print:

You can see that the viewGroup has already consumed the event, and no matter where you click, the event will not be passed to the child view.

Conclusion: When dispatchTouchEvent intercepts this event, no subsequent sequence of events is passed down. It’s all handled from the view.


If we don’t intercept first, what happens when we intercept the child view event after the click?

Modify code:

/ / mode flag bit
public static boolean mode=false; . btn_1.setOnTouchListener((v, event) -> {switch (event.getAction()){
          case MotionEvent.ACTION_DOWN:
              Log.e("demo"."View - Finger down");
              mode=true;
              break; . }return false;
  });
Copy the code

Finger -> Button, then move slightly to release:

Did you find that the event sequence passed an ACTION_CANCEL to the child view when it was intercepted, and no subsequent events were passed down?

Conclusion: When an event is intercepted by onInterceptTouchEvent returning true, ACTION_CANCEL is passed to the View’s onTouchEvent method. Subsequent events, such as moves, are passed directly to the interceptor’s onTouchEvent() method. Further events are not passed to its onInterceptTouchEvent method, which returns true once and is never called again.


What would it be like to consume this event without dealing with it?

Modify code:

For demonstration purposes, since the default onInterceptTouchEvent returns true, we don’t bother modifying the Button, so we try intercepting the event with the ViewGroup and not consuming it, i.e. onTOuchEvent returns false.

/ / mode flag bit
public static boolean mode=false; LinearLayoutView -> Change the codepublic boolean onTouchEvent(MotionEvent event) {
        Log.e("demo"."Viewgroup - I don't consume");
        return false;
    }
Copy the code

Finger -> button Press and move gently:

Conclusion: This is what we call the chain of responsibility mode, passing events one layer at a time, deciding to send a dispatchTouchEvent, which is finally received by onTouchEvent, and if the child doesn’t consume, it continues up until the Activity consumes itself. The Activity actually consumes the event regardless of whether it returns true or false.


Does returning true or false in onTouch affect onTouchEvent?

Restore the code as it was, then change it

Activity: 
linearLayout.setOnTouchListener((v, event) -> {
            switch (event.getAction()){
                case MotionEvent.ACTION_DOWN:
                    Log.e("demo"."Viewgroup - Finger press");
                    break;
                case MotionEvent.ACTION_UP:
                    Log.e("demo"."Viewgroup - Finger release");
                    break;
                case MotionEvent.ACTION_MOVE:
                    Log.e("demo"."Viewgroup - Finger move");
                    break;
            }
            return true;
        });

LinearLayout:
  @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        boolean mode=super.onInterceptTouchEvent(ev);
        Log.e("demo"."viewgroup-onInterceptTouchEvent: "+ mode);
        return false;
    }


 @Override
    public boolean onTouchEvent(MotionEvent event) {
        boolean mode=super.onTouchEvent(event);
        Log.e("demo"."viewgroup-onTouchEvent");
        return mode;
    }
Copy the code

Finger -> Press the blank area and release:

And then I’m going to modify the code

Activity: 
 linearLayout.setOnTouchListener((v, event) -> {
         	...
            return false;
        });
Copy the code

Finger -> Press the blank area and release:

Conclusion: You can see that the onTouch method takes precedence over the onTouchEvent execution. The reason for this can be found in my next article on Android event distribution.

Thank you

  • GcsSloop
  • Android development art exploration

Learn more about Android development at CloudBook.