Event distribution and sliding conflict resolution of View

1, Type of touch event

ACTION_DOWN: user's finger press operation. A press operation marks the beginning of a touch event
ACTION_UP: user's finger lifting operation. One lifting marks the end of an event
ACTION_MOVE: before the finger is pressed and lifted, if the moving distance exceeds a certain threshold, action will be triggered_ MOVE

One touch event, ACTION_DOWN and ACTION_UP must exist, ACTION_MOVE depends.

2, Three stages of event delivery

Distribute dispatchtouchevent
  • public boolean dispatchTouchEvent(MotionEvent event)
  • According to the specific implementation logic of the current view, decide whether to consume this event directly or continue to distribute this event to child views for processing
  • true indicates that the event is consumed by the current view and will not be distributed
  • super.dispatchEvent means to continue to distribute the modified event. If the current view is viewGroup and its subclasses, the onInterceptTouchEvent method will be called to determine whether to intercept the event
Intercept onintercepttouchevent
  • The interception of events corresponds to the onInterceptTouchEvent method, which exists only in viewGroup and its subclasses, not in activity and view
  • public boolean onInterceptTouchEvent(MotionEvent event)
  • true means to intercept this event, stop distributing it to child views, and call 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
Consume ontouchevent
  • public boolean onTouchEvent(MotionEvent event)
  • true indicates that the current view handles the corresponding event, and the event will not be passed up to the parent view
  • false means that the current view does not process the corresponding event, and the event will be passed up to the onTouchEvent of the parent view for processing
In Android, there are three classes with event delivery: activity view and viewGroup
  • activity: has dispatchTouchEvent and onTouchEvent methods
  • view: has dispatchTouchEvent and onTouchEvent methods
  • viewGroup: always dispatchTouchEvent, onInterceptEvent, and onTouchEvent methods

3, Event delivery of view

Although viewGroup is a subclass of view, view here refers to the view control excluding viewGroup, such as textView,button,imageView and other controls
Write a simple demo to analyze the event delivery of view

3.1. Customize a view, inherit textView, and override 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)
    }


}
3.2. Add MyTextView to the xml of the activity, set setOnTouchListener and setOnClickListener listening for MyTextView, and rewrite the onTouchEvent and dispatchTouchEvent methods of the activity

**

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)
    }
}
3.3 log printing results

**

2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_DOWN
2021-03-30 18:07:14.880 23744-23744/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_DOWN
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_UP
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_UP
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_UP
2021-03-30 18:07:14.960 23744-23744/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_UP
2021-03-30 18:07:14.961 23744-23744/com.ysl.dispatchstudy E/ysl: mTextView Click

3.4 analysis of view event distribution

The event delivery of view is displayed according to the results
1. The delivery process of touch events starts from dispatchTouchEvent. If there is no human intervention (that is, the function with the same name as the parent class is returned by default), the events will be delivered outward and inward according to the nesting level. When they reach the innermost view, they will be processed by the innermost onTouchEvent. If they can be processed, they will return true. If they cannot be processed, they will return false, At this time, the event will be re passed to the outer layer and processed by the onTouchEvent of the outer layer, and so on
2. If the event is interfered by human during its transmission to the inner layer, the event handling function returns true, the event will be consumed in advance, and the inner layer view will not receive this event
3. The event trigger of view is to execute the onTouch method first and the onClick method last. If onTouch returns true, the event will not continue to pass, and finally the onClick method will not be called. If false is returned, the event will continue to pass

4, Event distribution for viewGroup

viewGroup exists as a container for view control. Android system provides a series of viewgroups by default, such as LinearLayout,FrameLayout,RelativeLayout,ListView, etc

4.1. Customize a simple MyRelativeLayout, inherit RelativeLayout, and override 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)
    }

}

4.2. In the xml of activity, a layer of MyRelativeLayout is nested outside MyTextView
4.3 log printing results

**

2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: onInterceptTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_DOWN
2021-03-30 18:17:56.680 24022-24022/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_DOWN
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MainActivity: dispatchTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: dispatchTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyRelativeLayout: onInterceptTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyTextView: dispatchTouchEvent ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/mTextView: OnTouch ACTION_UP
2021-03-30 18:17:56.760 24022-24022/com.ysl.dispatchstudy E/MyTextView: onTouchEvent ACTION_UP
2021-03-30 18:17:56.761 24022-24022/com.ysl.dispatchstudy E/ysl: mTextView Click

4.4 event flow of * viewGroup

It can be seen from the log printing results
1. The transfer order of touch events is activity - > ViewGroup - > view
2. The viewGroup intercepts events through the onInterceptTouchEvent method
true, the event will not be passed to the child view
false is super.onInterceptTouchEvent, and the event will continue to be passed to the child view
3. If the event is consumed in the sub view, the viewGroup will not accept any event\

5, Sliding conflict

5.1 causes of sliding conflict

When both the inner and outer layers of View can slide, a sliding conflict will occur.

5.2. Outcome method of sliding conflict
1. External interception method
Override the onInterceptTouchEvent of the parent viewGroup and set it in motionevent.action according to logic_ Intercept in move

**

//Pseudo code
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
    }

be careful

a. According to the needs of business logic, in action_ Judge in the move method. If the parent View needs to be processed, it returns true; otherwise, it returns false. The event is distributed to the child View for processing
b,ACTION_DOWN must return false. Do not intercept it. Otherwise, follow-up actions will be executed according to the View event distribution mechanism_ Move and action_ All up events will be handled by the parent View by default
c. In principle, ACTION_UP also needs to return false. If it returns true and the sliding event is handed over to the child View for processing, the child View will not receive the ACTION_UP event and onClick event of child View cannot be triggered. The parent View is different. If the parent View is in action_ If an event is intercepted in move, the subsequent ACTION_UP will also default to the parent View for processing

2. Internal interception method
The child view rewrites the dispatchTouchEvent and displays it in motionevent.action according to logic_ For interception in move, the parent view needs to override onInterceptTouchEvent

**

//Pseudo code
//Child view overrides dispatchTouchEvent
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {

        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                if (This click event is required by the parent container) {
                    parent.requestDisallowInterceptTouchEvent(false)
                }
            }
            MotionEvent.ACTION_UP -> {
            }
            else -> {
            }
        }

        return super.dispatchTouchEvent(ev)
    }
//Parent view overrides onInterceptTouchEvent
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        val action: Int = ev!!.action
        return action != MotionEvent.ACTION_DOWN
    }

be careful

a. The internal interception method requires that the parent View cannot intercept the ACTION_DOWN event due to ACTION_DOWN is not affected by flag_ DISALLOW_ The interrupt flag bit controls once the parent container intercepts the ACTION_DOWN, then all events will not be passed to the child View
b. The logic of the sliding policy is placed in the action of the dispatchTouchEvent method of the child View_ In move, if the parent container needs to get the click event, call the parent.requestDisallowInterceptTouchEvent(false) method to let the parent container intercept the event.



Posted on Tue, 30 Nov 2021 01:11:12 -0500 by mallard