• 欢迎访问搞代码网站,推荐使用最新版火狐浏览器和Chrome浏览器访问本网站!
  • 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏搞代码吧

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

android 搞代码 4年前 (2022-03-01) 24次浏览 已收录 0个评论

一、触摸事件的类型
ACTION_DOWN:用户手指按下操作,一个按下操作标记着一次触摸事件的开始
ACTION_UP:用户手指抬起操作,一次抬起标记着一次事件的完结
ACTION_MOVE:手指按下抬起前,如果挪动的间隔超过肯定的阈值,就会触发ACTION_MOVE
一次触摸事件,ACTION_DOWN和ACTION_UP是必须存在的,ACTION_MOVE视状况而定。
二、事件传递的三个阶段
散发(dispatch) dispatchTouchEvent

public boolean dispatchTouchEvent(MotionEvent event)
依据以后视图的具体实现逻辑,来决定是间接生产这个事件还是将这个事件持续分发给子视图进行解决
true 示意事件被以后视图生产掉,不在持续散发事件
super.dispatchEvent示意持续散发改事件,如果以后视图是viewGroup及其子类,则会调用onInterceptTouchEvent办法判断是否拦挡该事件

拦挡(intercept) onInterceptTouchEvent

事件的拦挡对应着onInterceptTouchEvent办法,这个办法只在viewGroup及其子类中存在,不在activity和view中存成
public boolean onInterceptTouchEvent(MotionEvent event)
true 示意拦挡这个事件,不持续分发给子视图,并调用本身的onTouchEvent进行生产
false或者super.onInterceptEvent示意不对事件进行拦挡,须要持续传递给子视图

生产(consume) onTouchEvent

public boolean onTouchEvent(MotionEvent event)
true 示意以后视图解决对应的事件,事件将不会向上传递给父视图
false 示意以后视图不解决对应的事件,事件将会向上传递给父视图的onTouchEvent进行解决

在Android中领有事件传递的类有三种 activity view 和viewGroup

activity:领有dispatchTouchEvent和onTouchEvent办法
view:领有dispatchTouchEvent和onTouchEvent办法
viewGroup:永远dispatchTouchEvent、onInterceptEvent和onTouchEvent办法

三、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)办法,让父容器去拦挡事件.


搞代码网(gaodaima.com)提供的所有资源部分来自互联网,如果有侵犯您的版权或其他权益,请说明详细缘由并提供版权或权益证明然后发送到邮箱[email protected],我们会在看到邮件的第一时间内为您处理,或直接联系QQ:872152909。本网站采用BY-NC-SA协议进行授权
转载请注明原文链接:View的事件分发及滑动冲突的解决

喜欢 (0)
[搞代码]
分享 (0)
发表我的评论
取消评论

表情 贴图 加粗 删除线 居中 斜体 签到

Hi,您需要填写昵称和邮箱!

  • 昵称 (必填)
  • 邮箱 (必填)
  • 网址