First, the type of touch events

ACTION_DOWN: User finger press action. A press action marks the start of a touch event
ACTION_UP: Lifting the user’s finger indicates the end of an event
ACTION_MOVE: Before the finger is pressed and lifted, if the moving distance exceeds a certain threshold, the ACTION_MOVE will be triggered

For a touch event, ACTION_DOWN and ACTION_UP must exist, and ACTION_MOVE depends.

Two, the three stages of event transmission

Distribute dispatchTouchEvent (dispatch)
  • public boolean dispatchTouchEvent(MotionEvent event)
  • Depending on the implementation logic of the current view, the decision is made to consume the event directly or to distribute the event to subviews for processing
  • True indicates that the event is consumed by the current view and is no longer distributed
  • Super. dispatchEvent means that the event will continue to be distributed. If the current view is a viewGroup or its subclasses, the onInterceptTouchEvent method will be called to determine whether to intercept the event
Intercept onInterceptTouchEvent (intercept)
  • Event intercepting corresponds to the onInterceptTouchEvent method, which exists only in viewGroup and its subclasses, not in activities and views
  • public boolean onInterceptTouchEvent(MotionEvent event)
  • True intercepts the event, does not continue distribution to the subview, and calls its own onTouchEvent for consumption
  • False or super.onInterceptEvent Indicates that the event is not intercepted and needs to be passed to the child view
Consumption onTouchEvent (consume)
  • public boolean onTouchEvent(MotionEvent event)
  • True indicates that the current view processes the corresponding event and the event will not be passed up to the parent view
  • False indicates that the current view does not process the corresponding event and the event will be passed up to the parent view’s onTouchEvent for processing
There are three classes that have event passing in Android: Activity View and viewGroup
  • Activity: Has the dispatchTouchEvent and onTouchEvent methods
  • View: Has the dispatchTouchEvent and onTouchEvent methods
  • ViewGroup: always dispatchTouchEvent, onInterceptEvent and onTouchEvent methods

3. View event transfer

Although a subclass of viewGroup is the view, the view here refers to the removal of viewGroup view controls, such as the textView, button, imageView controls such as writing a simple demo, analysis of the view of events

3.1. Customize a View inherited from textView and rewrite onTouchEvent and dispatchTouchEvent methods

**

class MyTextView : androidx.appcompat.widget.AppCompatTextView { constructor(context: Context):super(context){ } constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet){ } constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int): super(context, attributeSet, defStyleAttr){ } override fun dispatchTouchEvent(event: MotionEvent?) : Boolean { when (event? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MyTextView","dispatchTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MyTextView","dispatchTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MyTextView","dispatchTouchEvent ACTION_MOVE") } } return super.dispatchTouchEvent(event) } override fun onTouchEvent(event: MotionEvent?) : Boolean { when (event? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MyTextView","onTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MyTextView","onTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MyTextView","onTouchEvent ACTION_MOVE") } } return super.onTouchEvent(event) } }Copy the code
3.2. Add MyTextView to the activity XML and set setOnTouchListener and setOnClickListener to the MyTextView. Rewrite the activity’s onTouchEvent and dispatchTouchEvent methods

**

class MainActivity : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) setContentView(R.layout.activity_main) var mTextView = findViewById<MyTextView>(R.id.mTextView) mTextView.setOnClickListener { Log.e("ysl","mTextView Click") } mTextView.setOnTouchListener { v, event -> when (event.action) { MotionEvent.ACTION_DOWN -> { Log.e("mTextView","OnTouch ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("mTextView","OnTouch ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("mTextView","OnTouch ACTION_MOVE") } } return@setOnTouchListener super.onTouchEvent(event) } } override fun dispatchTouchEvent(ev: MotionEvent?) : Boolean { when (ev? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MainActivity","dispatchTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MainActivity","dispatchTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MainActivity","dispatchTouchEvent ACTION_MOVE") } } return super.dispatchTouchEvent(ev) } override fun onTouchEvent(event: MotionEvent?) : Boolean { when (event? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MainActivity","onTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MainActivity","onTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MainActivity","onTouchEvent ACTION_MOVE") } } return super.onTouchEvent(event) } }Copy the code
3.3. Log printing results

**

The 2021-03-30 18:07:14. 880, 23744-23744 / com. Ysl. Dispatchstudy E/MainActivity: DispatchTouchEvent ACTION_DOWN 18:07:14. 2021-03-30, 880, 23744-23744 / com. Ysl. Dispatchstudy E/MyTextView: DispatchTouchEvent ACTION_DOWN 18:07:14. 2021-03-30, 880, 23744-23744 / com. Ysl. Dispatchstudy E/mTextView: The OnTouch ACTION_DOWN 18:07:14. 2021-03-30, 880, 23744-23744 / com. Ysl. Dispatchstudy E/MyTextView: OnTouchEvent ACTION_DOWN 18:07:14. 2021-03-30, 960, 23744-23744 / com. Ysl. Dispatchstudy E/MainActivity: DispatchTouchEvent ACTION_UP 18:07:14. 2021-03-30, 960, 23744-23744 / com. Ysl. Dispatchstudy E/MyTextView: DispatchTouchEvent ACTION_UP 18:07:14. 2021-03-30, 960, 23744-23744 / com. Ysl. Dispatchstudy E/mTextView: The OnTouch ACTION_UP 18:07:14. 2021-03-30, 960, 23744-23744 / com. Ysl. Dispatchstudy E/MyTextView: OnTouchEvent ACTION_UP 18:07:14. 2021-03-30, 961, 23744-23744 / com. Ysl. Dispatchstudy E/ysl: mTextView ClickCopy the code
3.4 analysis of VIEW event distribution

1. Touch events are sent from dispatchTouchEvent. If there is no human intervention (that is, the function with the same name that returns the parent class by default), then the event will be sent from outside to inside in the nested hierarchy. If it can be processed, return true; if it cannot be processed, return false. In this case, the event will be relayed to the outer layer and processed by the outer layer onTouchEvent, and so on. If the event handler returns true, the event will be consumed ahead of time, and the inner view will not receive the event. The onClick method is not called, and if false is returned, the event continues

4. ViewGroup event distribution

ViewGroup as view controls container exists, the Android system default provides a series of viewGroup, LinearLayout, for example, FrameLayout, RelativeLayout, ListView, etc

4.1. Define a simple MyRelativeLayout extension from RelativeLayout, overriding the dispatchTouchEvent, onInterceptTouchEvent, and onTouchEvent methods

**

class MyRelativeLayout :RelativeLayout{ constructor(context: Context):super(context){ } constructor(context: Context, attributeSet: AttributeSet): super(context, attributeSet){ } constructor(context: Context, attributeSet: AttributeSet, defStyleAttr: Int): super(context, attributeSet, defStyleAttr){ } override fun dispatchTouchEvent(ev: MotionEvent?) : Boolean { when (ev? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MyRelativeLayout","dispatchTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MyRelativeLayout","dispatchTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MyRelativeLayout","dispatchTouchEvent ACTION_MOVE") } } return super.dispatchTouchEvent(ev) } override fun onInterceptTouchEvent(ev: MotionEvent?) : Boolean { when (ev? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MyRelativeLayout","onInterceptTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MyRelativeLayout","onInterceptTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MyRelativeLayout","onInterceptTouchEvent ACTION_MOVE") } } return super.onInterceptTouchEvent(ev) } override fun onTouchEvent(event: MotionEvent?) : Boolean { when (event? .action) { MotionEvent.ACTION_DOWN -> { Log.e("MyRelativeLayout","onTouchEvent ACTION_DOWN") } MotionEvent.ACTION_UP -> { Log.e("MyRelativeLayout","onTouchEvent ACTION_UP") } MotionEvent.ACTION_MOVE -> { Log.e("MyRelativeLayout","onTouchEvent ACTION_MOVE") } } return super.onTouchEvent(event) } }Copy the code
In the Activity XML, a Layer of MyRelativeLayout is nested around MyTextView
4.3 Log printing results

**

The 2021-03-30 18:17:56. 680, 24022-24022 / com. Ysl. Dispatchstudy E/MainActivity: DispatchTouchEvent ACTION_DOWN 18:17:56. 2021-03-30, 680, 24022-24022 / com. Ysl. Dispatchstudy E/MyRelativeLayout: DispatchTouchEvent ACTION_DOWN 18:17:56. 2021-03-30, 680, 24022-24022 / com. Ysl. Dispatchstudy E/MyRelativeLayout: OnInterceptTouchEvent ACTION_DOWN 18:17:56. 2021-03-30, 680, 24022-24022 / com. Ysl. Dispatchstudy E/MyTextView: DispatchTouchEvent ACTION_DOWN 18:17:56. 2021-03-30, 680, 24022-24022 / com. Ysl. Dispatchstudy E/mTextView: The OnTouch ACTION_DOWN 18:17:56. 2021-03-30, 680, 24022-24022 / com. Ysl. Dispatchstudy E/MyTextView: OnTouchEvent ACTION_DOWN 18:17:56. 2021-03-30, 760, 24022-24022 / com. Ysl. Dispatchstudy E/MainActivity: DispatchTouchEvent ACTION_UP 18:17:56. 2021-03-30, 760, 24022-24022 / com. Ysl. Dispatchstudy E/MyRelativeLayout: DispatchTouchEvent ACTION_UP 18:17:56. 2021-03-30, 760, 24022-24022 / com. Ysl. Dispatchstudy E/MyRelativeLayout: OnInterceptTouchEvent ACTION_UP 18:17:56. 2021-03-30, 760, 24022-24022 / com. Ysl. Dispatchstudy E/MyTextView: DispatchTouchEvent ACTION_UP 18:17:56. 2021-03-30, 760, 24022-24022 / com. Ysl. Dispatchstudy E/mTextView: The OnTouch ACTION_UP 18:17:56. 2021-03-30, 760, 24022-24022 / com. Ysl. Dispatchstudy E/MyTextView: OnTouchEvent ACTION_UP 18:17:56. 2021-03-30, 761, 24022-24022 / com. Ysl. Dispatchstudy E/ysl: mTextView ClickCopy the code
4.4, *viewGroup event flow

ViewGroup uses onInterceptTouchEvent to intercept the event. If the event is true, it will not be transmitted to the child view False goods super onInterceptTouchEvent, events will continue to view 3, in the view of events for the consumer, viewGroup will not accept any event \

Five, sliding conflict

5.1 Causes of sliding conflicts

When we slide on both sides of the View, we get slide conflicts.

5.2. Ending method of sliding conflict
1. External interception
Override the onInterceptTouchEvent of the parent viewGroup and intercept it logically in MotionEvent.ACTION_MOVE

**

Override fun onInterceptTouchEvent(ev: MotionEvent?) : Boolean { var intercepted = false when (ev? .getAction()) {motionEvent.action_down -> {intercepted = false} motionEvent.action_move -> {intercepted = Meet the interception requirements of the parent container  } MotionEvent.ACTION_UP -> { intercepted = false } else -> { } } return intercepted }Copy the code
Pay attention to

A, according to the business logic needs, in the ACTION_MOVE method to judge, if the parent View processing is required to return true, otherwise return false, the event is distributed to the child View to process false, do not intercept it, otherwise according to the View event distribution mechanism, C. In principle, ACTION_UP also needs to return false. If true is returned and the sliding event is handed to the child View for processing, then the child View will not receive the ACTION_UP event. The onClick event of the child View also failed to fire. The parent View is different. If the parent View starts intercepting events in ACTION_MOVE, the subsequent ACTION_UP will default to the parent View

2. Internal interception
The child view overrides dispatchTouchEvent and intercepts it in MotionEvent.ACTION_MOVE according to logic. The parent view overrides onInterceptTouchEvent

**

Override fun dispatchTouchEvent(ev: MotionEvent?) : Boolean { when (ev? .action) { MotionEvent.ACTION_DOWN -> { parent.requestDisallowInterceptTouchEvent(true) } MotionEvent.ACTION_MOVE -> { If (the parent container need such click events) {parent. RequestDisallowInterceptTouchEvent (false)}} MotionEvent. ACTION_UP - > {} else - > {}} Return super.dispatchTouchEvent(ev)} override fun onInterceptTouchEvent(ev: MotionEvent?) : Boolean { val action: Int = ev!! .action return action ! = MotionEvent.ACTION_DOWN }Copy the code
Pay attention to

A. Internal interception requires that the parent View cannot intercept ACTION_DOWN events. ACTION_DOWN is not controlled by FLAG_DISALLOW_INTERCEPT. Once the parent intercepts ACTION_DOWN, no events are passed to the child View B, and the logic for the sliding policy is placed in the ACTION_MOVE of the child View’s dispatchTouchEvent method, If the parent container need access to click event. Call the parent requestDisallowInterceptTouchEvent (false) method, let the parent container to intercept events