其他分享
首页 > 其他分享> > View的事件分发及滑动冲突的解决

View的事件分发及滑动冲突的解决

作者:互联网

一、触摸事件的类型

ACTION_DOWN:用户手指按下操作,一个按下操作标志着一次触摸事件的开始
ACTION_UP:用户手指抬起操作,一次抬起标志着一次事件的结束
ACTION_MOVE:手指按下抬起前,如果移动的距离超过一定的阈值,就会触发ACTION_MOVE

一次触摸事件,ACTION_DOWN和ACTION_UP是必须存在的,ACTION_MOVE视情况而定。

二、事件传递的三个阶段

分发(dispatch) dispatchTouchEvent
拦截(intercept) onInterceptTouchEvent
消费(consume) onTouchEvent
在Android中拥有事件传递的类有三种 activity view 和viewGroup

三、view的事件传递

虽然viewGroup是view的子类,这里的view指除去viewGroup的view控件,例如textView,button,imageView等控件
写个简单的demo,分析view的事件传递

3.1、自定义一个view继承textView,并重写onTouchEvent和dispatchTouchEvent方法

**

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、在activity的xml中添加MyTextView,给MyTextView设置setOnTouchListener和setOnClickListener监听,并重写activity的onTouchEvent和dispatchTouchEvent方法

**

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、日志打印结果

**

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、view事件分发的分析

view的事件传递 根据结果显示
1、触摸事件的传递流程是从dispatchTouchEvent开始的,如果没有人为干预(也就是默认返回父类的同名函数),则事件将会按照嵌套层次有外向内传递,到达最内层的view时,就由最内层的onTouchEvent进行处理,如果能处理就返回true消费掉,如果不能处理就返回false,这时事件会重新向外层传递,并由外层的onTouchEvent进行处理,依次类推
2、如果事件在向内层传递过程中被人为干预,事件处理函数返回true,事件将会被提前消费掉,内层view将不会收到这个事件
3、view的事件触发是先执行onTouch方法,在最后执行onClick方法,如果onTouch返回true,事件将不会继续传递,最后也不会调用onClick方法,如果返回false,事件继续传递

四、viewGroup的事件分发

viewGroup作为view控件的容器存在,Android系统默认提供了一系列viewGroup,例如LinearLayout,FrameLayout,RelativeLayout,ListView等

4.1、自定义一个简单的MyRelativeLayout继承RelativeLayout,重写dispatchTouchEvent、onInterceptTouchEvent和onTouchEvent方法

**

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、在activity的xml中,MyTextView外面嵌套一层MyRelativeLayout
4.3、日志打印结果

**

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、 *viewGroup的事件流程

根据日志打印结果可以看出
1、触摸事件的传递顺序是activity->viewGroup->view
2、viewGroup通过onInterceptTouchEvent方法对事件进行拦截
true 则事件不会传递给子view
false货super.onInterceptTouchEvent,事件会继续传递给子view
3、在子view中对事件进行了消费,viewGroup将接受不到任何事件\

五、滑动冲突

5.1、滑动冲突产生的原因

当我们内外两层View都可以滑动时候,就会产生滑动冲突。

5.2、滑动冲突的结局方法
1、外部拦截法
重写父viewGroup的onInterceptTouchEvent,根据逻辑在MotionEvent.ACTION_MOVE中进行拦截

**

//伪代码
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
        var intercepted = false
        when (ev?.getAction()) {
            MotionEvent.ACTION_DOWN -> {
                intercepted = false
            }
            MotionEvent.ACTION_MOVE -> {
                intercepted = 满足父容器的拦截要求
            }
            MotionEvent.ACTION_UP -> {
                intercepted = false
            }
            else -> {
            }
        }
        return intercepted
    }

注意

a、根据业务逻辑需要,在ACTION_MOVE方法中进行判断,如果需要父View处理则返回true,否则返回false,事件分发给子View去处理
b、ACTION_DOWN 一定返回false,不要拦截它,否则根据View事件分发机制,后续ACTION_MOVE 与 ACTION_UP事件都将默认交给父View去处理
c、原则上ACTION_UP也需要返回false,如果返回true,并且滑动事件交给子View处理,那么子View将接收不到ACTION_UP事件,子View的onClick事件也无法触发。而父View不一样,如果父View在ACTION_MOVE中开始拦截事件,那么后续ACTION_UP也将默认交给父View处理

2、内部拦截法
子view重写dispatchTouchEvent,根据逻辑在MotionEvent.ACTION_MOVE中进行拦截,父view需要重写onInterceptTouchEvent

**

//伪代码
//子view重写dispatchTouchEvent
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {

        when (ev?.action) {
            MotionEvent.ACTION_DOWN -> {
                parent.requestDisallowInterceptTouchEvent(true)
            }
            MotionEvent.ACTION_MOVE -> {
                if (父容器需要此类点击事件) {
                    parent.requestDisallowInterceptTouchEvent(false)
                }
            }
            MotionEvent.ACTION_UP -> {
            }
            else -> {
            }
        }

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

注意

a、内部拦截法要求父View不能拦截ACTION_DOWN事件,由于ACTION_DOWN不受FLAG_DISALLOW_INTERCEPT标志位控制,一旦父容器拦截ACTION_DOWN那么所有的事件都不会传递给子View
b、滑动策略的逻辑放在子View的dispatchTouchEvent方法的ACTION_MOVE中,如果父容器需要获取点击事件则调用 parent.requestDisallowInterceptTouchEvent(false)方法,让父容器去拦截事件。



标签:分发,MotionEvent,dispatchTouchEvent,onTouchEvent,UP,DOWN,ACTION,滑动,View
来源: https://www.cnblogs.com/0121ww/p/15623920.html