[Statement] This article is from the study notes of the first chapter of “Gu Haoxin -Android Advanced”. The purpose of this article is to share knowledge. If it involves the content of the article, copyright and other issues, please contact me, I will delete the content in the first time!

Preface: In Android development, there are often touch event conflicts, such as the conflict between the scrolling view of ViewPager and the swipe event of Fragment, or the conflict between the scrolling view and the pull-down event, and the event processing of custom view. This article will introduce the touch event transmission mechanism of Activity, View and ViewGroup in detail. The transmission includes three stages: distribution, interception and consumption.Copy the code

This article will introduce the touch event transmission mechanism of Activity, View and ViewGroup in detail. The transmission includes three stages: distribution, interception and consumption.

Type of touch event

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

  1. ACTION_DOWN: Indicates the start of a touch event.
  2. ACTION_MOVE: Move when pressed. Any slight movement is passed to the event.
  3. ACTION_UP: Indicates a touch event when the user’s finger leaves the screen

Note: If the user only clicks, only ACTION_DOWN and ACTION_UP events will be executed, but ACTION_MOVE events will not be executed. So ACTION_DOWN and ACTION_UP are required events.

2. The transmission stage of touch events

1. Dispatch

All touch events in the Android system are distributed by the dispatchTouchEvent method. This method determines whether the event is consumed (return true) or continues to be distributed to subviews (return super.dispatchTouchEvent). If the current view is a ViewGroup or a subclass of it, OnInterceptTouchEvent is called to check whether it is intercepted.

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        return super.dispatchTouchEvent(event);
    }
Copy the code

2. Intercept

An InterceptTouchEvent exists only in a ViewGroup and its subclasses, not in an activity or View. The method to judge events is cut block (return true) and to their own OnToucEvent methods for consumption, continued to child views (return. Super InterceptTouchEvent or return false).

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        return super.onInterceptTouchEvent(ev);
    }
Copy the code

3. Consume

Event consumption is determined by the OnTouchEvent method, whether it is consumed (return true) or not processed (return false) and passed to the superview’s OnTouchEvent method for processing.

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        return super.onTouchEvent(event);
    }
Copy the code

All classes with event-passing capabilities:

Activity: Has dispatchTouchEvent and OnTouchEvent

ViewGroup: has dispatchTouchEvent, OnInterceptTouchEvent, OnTouchEvent

View: has dispatchTouchEvent and OnTouchEvent

The event passing mechanism of View

3.1 dome

Although ViewGroup is a subclass of View, but it is said that View control subclass except ViewGroup, first define a MyTextView inherit TextView, print the trigger of each event to change the process of event transmission.

MyTextView class

public class MyTextView extends TextView {

    private String tag = "MyTextView";

    public MyTextView(Context context) {
        super(context);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(event);
    }


    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        returnsuper.onTouchEvent(event); }}Copy the code

Define a MainActivity to display the MyTextView and set the onClick and onTouch listeners. MainActivity class

public class MainActivity extends AppCompatActivity implements View.OnClickListener,View.OnTouchListener{
    private MyTextView mMyTextView;
    private String tag = "MainActiviy";

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main); mMyTextView = findViewById(R.id.text_view); / / click to monitor mMyTextView setOnClickListener (this); / / touch monitor mMyTextView setOnTouchListener (this); Public void onClick(View View){switch (view.getid ()){case R.id.text_view:
                Log.i(tag, "MyTextView onClick");
                break; }} @override public Boolean onTouch(View View, MotionEvent motionEvent) { switch (motionEvent.getAction()){case MotionEvent.ACTION_UP:
                Log.i(tag, "MyTextView onTouch ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "MyTextView onTouch ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "MyTextView onTouch ACTION_DOWN");
                break;
        }
        return false; } // Activity  @override public Boolean dispatchTouchEvent(MotionEvent ev) {switch (ev.getAction()){case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        returnsuper.dispatchTouchEvent(ev); Override public Boolean onTouchEvent(MotionEvent){switch (event.getAction()){Override public Boolean onTouchEvent(MotionEvent){case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        returnsuper.onTouchEvent(event); }}Copy the code

3.2 Printing Logs

After running, click Text View to feedback the printed log

03-28 08:05:14. 824, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: DispatchTouchEvent ACTION_DOWN 03-28 08:05:14. 824, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: DispatchTouchEvent ACTION_DOWN 03-28 08:05:14. 824, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: MyTextView the onTouch ACTION_DOWN 03-28 08:05:14. 824, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: OnTouchEvent ACTION_DOWN 03-28 08:05:15. 034, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: DispatchTouchEvent ACTION_UP 03-28 08:05:15. 034, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: DispatchTouchEvent ACTION_UP 03-28 08:05:15. 034, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: MyTextView the onTouch ACTION_UP 03-28 08:05:15. 034, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: OnTouchEvent ACTION_UP 03-28 08:05:15. 044, 1219-1219 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: MyTextView onClickCopy the code

The return values of the dispatchTouchEvent and OnTouchEvent methods have three cases:

  1. Return true.
  2. Return false.
  3. Return the parent method of the same name, super.dispatchTouchEvent or super.OnTouchEvent.

Since there are different return values, the event delivery process is also different. After constantly modifying the return value test, the flow chart of the click event is finally obtained, and the transfer process of ACTION_DOWN and ACTION_UP events is the same.

3.3 Event transmission flow chart

  1. The touch event starts with dispatchTouchEvent, returns the super method of the same name as the parent class by default, and the event will be passed from the outside in according to the nested hierarchy (MainActivity to MyTextView). When it reaches the innermost View, Will be handled by the View’s OnTouchEvent method, which will be consumed when it returns true and will not be passed out when it returns false, and will be handled by the outer OnTouchEvent.
  2. If human interference returns true consumption during pass from outer layer to inner layer, it will not continue like inner pass.
  3. The View’s event control sequence executes onTouch first and then onClick. If onTouch returns true consumption, the pass is not continued and the onClick method is not executed.

4. The event passing mechanism of ViewGroup

4.1 dome

ViewGroup is a View control container. It has dispatchTouchEvent, onInterceptTouchEvent and onTouchEvent methods. An onInterceptTouchEvent method is added to the View. For a better view, we need to customize MyRelativeLayout to inherit from RelativeLayout.

MyRelativeLayout class

public class MyRelativeLayout extends RelativeLayout {

    private final static String tag = "MyRelativeLayout";

    public MyRelativeLayout(Context context) {
        super(context);
    }

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

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "dispatchTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "dispatchTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "dispatchTouchEvent ACTION_DOWN");
                break;
        }
        return super.dispatchTouchEvent(ev);
    }

    @Override
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        switch (ev.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onInterceptTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onInterceptTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onInterceptTouchEvent ACTION_DOWN");
                break;
        }
        return super.onInterceptTouchEvent(ev);
    }

    @Override
    public boolean onTouchEvent(MotionEvent event) {
        switch (event.getAction()){
            case MotionEvent.ACTION_UP:
                Log.i(tag, "onTouchEvent ACTION_UP");
                break;
            case MotionEvent.ACTION_MOVE:
                Log.i(tag, "onTouchEvent ACTION_MOVE");
                break;
            case MotionEvent.ACTION_DOWN:
                Log.i(tag, "onTouchEvent ACTION_DOWN");
                break;
        }
        returnsuper.onTouchEvent(event); }}Copy the code

Main_activity. XML file

<? xml version="1.0" encoding="utf-8"? > <com.mvp.chenzhesheng.androidadvance.MyRelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    >

    <com.mvp.chenzhesheng.androidadvance.MyTextView
        android:id="@+id/text_view"
        android:clickable="true"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Hello World!"

</com.mvp.chenzhesheng.androidadvance.MyRelativeLayout>
Copy the code

4.2 Printing Logs

04-02 08:47:57. 980, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: DispatchTouchEvent ACTION_DOWN 04-02 08:47:58. 000, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyRelativeLayout: DispatchTouchEvent ACTION_DOWN 04-02 08:47:58. 000, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyRelativeLayout: OnInterceptTouchEvent ACTION_DOWN 04-02 08:47:58. 000, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: DispatchTouchEvent ACTION_DOWN 04-02 08:47:58. 010, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: MyTextView the onTouch ACTION_DOWN 04-02 08:47:58. 010, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: OnTouchEvent ACTION_DOWN 04-02 08:47:58. 200, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: DispatchTouchEvent ACTION_UP 04-02 08:47:58. 200, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyRelativeLayout: DispatchTouchEvent ACTION_UP 04-02 08:47:58. 200, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyRelativeLayout: OnInterceptTouchEvent ACTION_UP 04-02 08:47:58. 200, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: DispatchTouchEvent ACTION_UP 04-02 08:47:58. 210, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: MyTextView the onTouch ACTION_UP 04-02 08:47:58. 210, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MyTextView: OnTouchEvent ACTION_UP 04-02 08:47:58. 260, 1030-1030 / com. MVP. Chenzhesheng. Androidadvance I/MainActiviy: MyTextView onClickCopy the code

You can see that a layer of MyRelativeLayout has been added to the event passing handling of MainActivity and MyTextView. A set of flow charts is obtained by testing different return values.

4.3 the flow chart

  1. Touch event passing is passed from the Activity to the ViewGroup and then to the View. If there is no ViewGroup in between, pass directly from the Activity to the View.
  2. ViewGroup through onInterceptTouchEvent method to cut block of events, if return false or super onInterceptTouchEvent, the event will continue to pass to the View.
  3. After an event is consumed in a child View, the ViewGroup will not receive any events.

In live.

  1. Event distribution is done from the outside in, from the Activity to the concrete child View;
  2. Event processing is consumed from the inside out, from the child View to the outermost Activity;
  3. Event interception exists only in viewGroups;
  4. Master the event transfer mechanism can better handle the event, whether it is to customize the View or read the Framework layer source code need to learn the event transfer, in order to more refined development applications.