站在初学者的角度看图理解Android事件分发机制流程
关于Android事件分发机制的文章已经烂大街了,但很多都并不是站在初学者角度分析,要想快速了解Android事件分发机制,我们应该从以下这几方面先开始了解。
1.什么是事件分发?
这里谈下我个人的通俗理解: 就是我们的Activity、ViewGroup、View 都重写了 onTouchEvent(MotionEvent event) 这个方法后,用户触摸屏幕后它们的onTouchEvent是否能够接收到event事件的过程
2. 什么时候会用到事件分发?
当发生了点击事件或滑动事件冲突时,例如scrollView嵌套后子scrollView无法滑动这样的情况,此时要使用到事件分发机制。通过自定义控件继承发生冲突控件并重写其一系列事件分发的方法,来决定是否拦截事件和分发到子View处理。
3.事件分发都会用到哪些方法?
主要都是在View,ViewGroup 或 Activity中重写以下这三个方法,通过return值 决定事件分发的方向。
boolean onTouchEvent(MotionEvent event) //Activity ViewGroup View都可以重写此方法
boolean dispatchTouchEvent(MotionEvent event) //Activity ViewGroup View都可以重写此方法
boolean onInterceptTouchEvent (MotionEvent event) //仅有 ViewGroup 可以重写此方法
事件源 :事件源就是MotionEvent ,一个 MotionEvent 包含手指 按下 (down) , 移动(move), 抬起(up) ,以及对应事件的坐标值。
一个事件源都是先通过Avtivity的dispatchTouchEvent开始,依次往 子View 的 dispatchTouchEvent 传,最后一个子View的dispatchTouchEvent 把事件交给其 onTouchEvent 进行消费处理,一个事件就结束。 若子View onTouchEvent 不消费这个事件则该事件又会一直往父View onTouchEvent 回传,直到回传到 Avtivity 的onTouchEvent 消费事件。
- boolean dispatchTouchEvent(MotionEvent event) 代表是否继续往下分发 ,返回值意义:
return true:事件不再往内部子View分发,并自己消费了这个事件。
return false: 事件不再往内部子View分发,直接交给外部父View onTouchEvent 处理。 若当前已是最外层Activity则事件结束。
return super.dispatchTouchEvent(event) : 事件继续往下分发。若当前为ViewGroup,则交给 onInterceptTouchEvent 决定是否处理; 否则事件交给子View的 dispatchTouchEvent方法继续决定,若已经是最底层的子View则将事件交给自己的 onTouchEvent 处理。
- boolean onTouchEvent(MotionEvent event) 处理事件方法并决定是否把事件继续交给父View处理,其返回值意义:
return true:自己触发了onTouchEvent , 并消费了这个事件 。
return false: 自己触发了onTouchEvent , 不消费了这个事件, 事件继续往上回传触发父View 的 onTouchEvent 。
return super.onTouchEvent(event) : 同 return false。
- boolean onInterceptTouchEvent (MotionEvent event) 是否拦截事件自己处理不再往下传递,其返回值意义:
注意:子view 设置了getParent().requestDisallowInterceptTouchEvent(true)时,则不会被父View的onInterceptTouchEvent 拦截触发
return true:拦截此次事件不再往下传 , 并交给自己的onTouchEvent触发 。
return false: 不拦截此次事件继续再往下传给子View的dispatchTouchEvent 。
return super.onInterceptTouchEvent (event) : 同 return false。
4. 通过代码模拟事件分发练习,来理解事件分发流程图
下面是我自己画的一个事件流程图,这里加上了requestDisallowInterceptTouchEvent 这个决定父View是否允许拦截事件这个重要的方法。先对这个图有点印象,边敲代码边学会看流程图:
5. 决定需要模拟的效果,开始代码练习
模拟假设我们有以下的这样一个场景,一个Activity , 一个ViewGroup, 一个View ,他们都是互相包含关系:
为了观察FrameLayout 和 TextView 的事件分发机制,我们需要自己写个继承它们2个的自定义View并重写其事件分发方法。
//自定义View
class MyTextView @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : AppCompatTextView(context, attrs, defStyleAttr) {
//重写View的事件分发
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
Log.d("TAG","[View] dispatchTouchEvent....${event.action.getActionDesc()}")
return super.dispatchTouchEvent(event)
}
//重写View的事件处理
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG","[View] onTouchEvent....${event.action.getActionDesc()}")
return super.onTouchEvent(event)
}
}
//自定义ViewGroup
class MyFrameLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyleAttr: Int = 0
) : FrameLayout(context, attrs, defStyleAttr) {
//重写ViewGroup的事件分发
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG","[ViewGroup] dispatchTouchEvent....${event.action.getActionDesc()}")
return super.dispatchTouchEvent(event)
}
//重写ViewGroup的事件处理
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG","[ViewGroup] onTouchEvent....${event.action.getActionDesc()}")
return super.onTouchEvent(event)
}
//ViewGroup特有的事件拦截方法
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG","[ViewGroup] onInterceptTouchEvent....${event.action.getActionDesc()}")
return super.onInterceptTouchEvent(event)
}
}
以及MainActivity的事件处理方法
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
// 重写Activity事件分发方法
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG","[Activity] dispatchTouchEvent.... ${event.action.getActionDesc()}")
return super.dispatchTouchEvent(event)
}
// 重写Activity事件处理方法
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG","[Activity] onTouchEvent....${event.action.getActionDesc()}")
return super.onTouchEvent(event)
}
}
上面的getActionDesc方法是自己定义的一个的扩展方法,因为event.action返回的是一个Int,所以定义一个方法根据Int值来方便显示当前是down up move描述。
fun Int.getActionDesc() = when (this) {
0 -> "手指down事件"
1 -> "手指up事件"
2 -> "手指move事件"
else -> ""
}
MainActivity的布局使用了我们自定义MyFrameLayout和MyTextView, 简单如下:
首先我们的 Activity, ViewGroup,View 默认的所有事件分发方法返回值都是 super , 这里我们在TextView区域滑动一下, 可以看到以下Log日志:
[Activity] dispatchTouchEvent.... 手指down事件
[ViewGroup] dispatchTouchEvent....手指down事件
[ViewGroup] onInterceptTouchEvent....手指down事件
[View] dispatchTouchEvent....手指down事件
[View] onTouchEvent....手指down事件
[ViewGroup] onTouchEvent....手指down事件
[Activity] onTouchEvent....手指down事件
[Activity] dispatchTouchEvent.... 手指move事件
[Activity] onTouchEvent....手指move事件
[Activity] dispatchTouchEvent.... 手指move事件
[Activity] onTouchEvent....手指move事件
[Activity] dispatchTouchEvent.... 手指move事件
[Activity] onTouchEvent....手指move事件
[Activity] dispatchTouchEvent.... 手指move事件
[Activity] onTouchEvent....手指move事件
[Activity] dispatchTouchEvent.... 手指up事件
[Activity] onTouchEvent....手指up事件
可以看到,我们默认所有都返回super的时候,所有的down事件符合我们事件分发的流程图结果。
而move和up事件默认被Activity自己处理了不往下分发。
再看当我们的MyTextView onTouchEvent返回 true 时,无论down,move, up 事件都是按照流程图分发
[Activity] dispatchTouchEvent.... 手指down事件
[ViewGroup] dispatchTouchEvent....手指down事件
[ViewGroup] onInterceptTouchEvent....手指down事件
[View] dispatchTouchEvent....手指down事件
[View] onTouchEvent....手指down事件
[Activity] dispatchTouchEvent.... 手指move事件
[ViewGroup] dispatchTouchEvent....手指move事件
[ViewGroup] onInterceptTouchEvent....手指move事件
[View] dispatchTouchEvent....手指move事件
[View] onTouchEvent....手指move事件
[Activity] dispatchTouchEvent.... 手指move事件
[ViewGroup] dispatchTouchEvent....手指move事件
[ViewGroup] onInterceptTouchEvent....手指move事件
[View] dispatchTouchEvent....手指move事件......
[View] onTouchEvent....手指move事件[Activity] dispatchTouchEvent.... 手指up事件
[ViewGroup] dispatchTouchEvent....手指up事件
[ViewGroup] onInterceptTouchEvent....手指up事件
[View] dispatchTouchEvent....手指up事件
[View] onTouchEvent....手指up事件
综上所述,我们可以对照着流程图,一个个试着从Activity,ViewGroup,View逐个尝试返回不同的值观察不同的事件传递结果。
结论:
事件分发的 down事件总会按照事件分发的流程图走,而之后的up , move事件的话则会根据第一次的down事件分发结果决定。
若down事件最后由 Activity onTouchEvent进行处理,则之后的up,move事件则不会往下分发直接由Activity的dispatchTouchEvent交给自己的onTouchEvent进行处理;否则down,up,move事件都会按照事件流程图走。
6. 最后,了解View的onTouchEvent 、setOnTouchListener、setOnClickListener之间的影响
onTouchEvent:View自身的一个方法,通过重写它来处理传递来的事件。
setOnTouchListener:功能和onTouchEvent一样,暴露给外部Activity对View的处理事件的方法,优先级比 onTouchEvent高。
当在Activity里对View设置了该方法并返回false时,先触发setOnTouchListener再触发onTouchEvent;返回true时View内部的onTouchEvent方法不会触发。
textView.setOnTouchListener { view, event ->
Log.d("TAG","[view.setTouchListener] onTouchListener....${event.action.getActionDesc()} ")
true
}
点击TextView后Log如下:
[Activity] dispatchTouchEvent.... 手指down事件
[ViewGroup] dispatchTouchEvent....手指down事件
[ViewGroup] onInterceptTouchEvent....手指down事件
[View] dispatchTouchEvent....手指down事件
[view.setTouchListener] onTouchListener....手指down事件
[Activity] dispatchTouchEvent.... 手指up事件
[ViewGroup] dispatchTouchEvent....手指up事件
[ViewGroup] onInterceptTouchEvent....手指up事件
[View] dispatchTouchEvent....手指up事件
[view.setTouchListener] onTouchListener....手指up事件
setOnClickListener:我们常用的点击事件,当自身onTouchEvent down和up事件再短时间内完成则会触发。若onTouchEvent 或 setOnTouchListener 返回 true 消费了事件时,则点击事件不会触发。
若想若onTouchEvent 或 setOnTouchListener 返回 true 并继续触发其点击事件,则需调用自身的 performClick()来继续触发点击事件
tv.setOnTouchListener { view, event ->
Log.d("TAG","[view.setTouchListener] onTouchListener....${event.action.getActionDesc()} ")
view.performClick()
true
}
本文地址:https://blog.csdn.net/zhong_zihao/article/details/107505027
上一篇: 品牌推广的渠道有哪些?