事件分发是Android View体系里非常重要的知识点,下面开始具体的实验和分析。 创建了2个自定义ViewGroup:GrandFatherLayout继承自LinearLayout,FatherLayout继承自RelativeLayout。创建了一个自定义View(SonButton)继承自Button。3个View中只在它们的dispatchTouchEvent,onIntercepptTouchEvent,onTouchEvent中增加了打印的代码,其他未做修改,将SonButton放入FatherLayout,将FatherLayout放在GrandFatherLayout中,布局如下:
<?xml version="1.0" encoding="utf-8"?>
<dispatchview.GrandFatherLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<dispatchview.FatherLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center">
<dispatchview.SonButton
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="我爱安卓" />
</dispatchview.FatherLayout>
</dispatchview.GrandFatherLayout>
下面贴一下几个类的代码:
class DispatchViewActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.dispatchview)
}
override fun dispatchTouchEvent(ev: MotionEvent?): Boolean {
when (ev?.action) {
MotionEvent.ACTION_DOWN -> Log.e(FatherLayout.TAG, "DispatchViewActivity-dispatchTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(FatherLayout.TAG, "DispatchViewActivity-dispatchTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(FatherLayout.TAG, "DispatchViewActivity-dispatchTouchEvent-UP")
else -> {}
}
return super.dispatchTouchEvent(ev)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(FatherLayout.TAG, "DispatchViewActivity-onTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(FatherLayout.TAG, "DispatchViewActivity-onTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(FatherLayout.TAG, "DispatchViewActivity-onTouchEvent-UP")
else -> {}
}
return super.onTouchEvent(event)
}
}
GrandFatherLayout类:
class GrandFatherLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : LinearLayout(context, attrs, defStyle, defStyleRes) {
companion object {
const val TAG = "tanyonglin"
}
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "GrandFatherLayout-dispatchTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "GrandFatherLayout-dispatchTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "GrandFatherLayout-dispatchTouchEvent-UP")
else -> {}
}
return super.dispatchTouchEvent(event)
}
override fun onInterceptTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "GrandFatherLayout-onInterceptTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "GrandFatherLayout-onInterceptTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "GrandFatherLayout-onInterceptTouchEvent-UP")
else -> {}
}
return super.onInterceptTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "GrandFatherLayout-onTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "GrandFatherLayout-onTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "GrandFatherLayout-onTouchEvent-UP")
else -> {}
}
return super.onTouchEvent(event)
}
}
FatherLayout类:
class FatherLayout @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
defStyle: Int = 0,
defStyleRes: Int = 0
) : RelativeLayout(context, attrs, defStyle, defStyleRes) {
companion object {
const val TAG = "tanyonglin"
}
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "FatherLayout-dispatchTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "FatherLayout-dispatchTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "FatherLayout-dispatchTouchEvent-UP")
else -> {}
}
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> {
Log.e(TAG, "FatherLayout-onTouchEvent-DOWN")
// return false
}
MotionEvent.ACTION_MOVE -> {
Log.e(TAG, "FatherLayout-onTouchEvent-MOVE")
// return true
}
MotionEvent.ACTION_UP -> {
Log.e(TAG, "FatherLayout-onTouchEvent-UP")
// return true
}
else -> {}
}
return super.onTouchEvent(event)
//return true
}
override fun onInterceptTouchEvent(ev: MotionEvent?): Boolean {
val action: Int? = ev?.action
when (action) {
MotionEvent.ACTION_DOWN -> {
Log.e(TAG, "FatherLayout-onInterceptTouchEvent-DOWN")
// return false
}
MotionEvent.ACTION_MOVE -> {
Log.e(TAG, "FatherLayout-onInterceptTouchEvent-MOVE")
// return true
}
MotionEvent.ACTION_UP -> Log.e(TAG, "FatherLayout-onInterceptTouchEvent-UP")
else -> {}
}
return super.onInterceptTouchEvent(ev)
//return true
}
}
SonButton类:
@SuppressLint("AppCompatCustomView")
class SonButton @JvmOverloads constructor(
context: Context,
attrs: AttributeSet? = null,
) : Button(context, attrs) {
companion object {
const val TAG = "tanyonglin"
}
override fun dispatchTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "SonButton-dispatchTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "SonButton-dispatchTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "SonButton-dispatchTouchEvent-UP")
MotionEvent.ACTION_CANCEL -> Log.e(TAG, "SonButton-dispatchTouchEvent-CANCEL")
else -> {}
}
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent?): Boolean {
val action: Int? = event?.action
when (action) {
MotionEvent.ACTION_DOWN -> Log.e(TAG, "SonButton-onTouchEvent-DOWN")
MotionEvent.ACTION_MOVE -> Log.e(TAG, "SonButton-onTouchEvent-MOVE")
MotionEvent.ACTION_UP -> Log.e(TAG, "SonButton-onTouchEvent-UP")
else -> {}
}
return super.onTouchEvent(event)
}
}
1 前置条件:未修改任何方法返回值,点击按钮,打印日志如下:
从以上结果可以总结出:
事件是以ACTION_DOWN开始,中间有若干个ACTION_MOVE,最后以ACTION_UP结束。
事件是从Activity开始分发,从视图树由上往下传递,调用顺序为dispatchTouchEvent->onInterceptTouchEvent->onTouchEvent。
2 前置条件:如果在FatherLayout的onInterceptTouchEvent()中返回true,点击按钮,打印日志如下:
从以上结果可以总结出:
当FatherLayout拦截了事件,那么就不会分发到SonButton,并且FatherLayout的onTouchEvent方法返回false,因此事件会传递给GrandFatherLayout的onTouchEvent,然后传递给DispatchViewActivity的onTouchEvent。而且后面的MOVE和UP事件都只交给Activity处理了,不会向下分发了。
3 前置条件:如果在FatherLayout的onInterceptTouchEvent()中返回true,同时onTouchEvent返回true,点击按钮,打印日志如下:
从以上结果可以总结出;当FatherLayout拦截了事件,并且消耗了事件,那么此次事件到此结束分发,待下次事件分发。
4 前置条件:如果在FatherLayout的onInterceptTouchEvent()的DOWN事件返回false,MOVE事件返回true,也就是不拦截按下事件,但是拦截移动事件,同时onTouchEvent也返回true。点击按钮,打印日志如下:
从上述结果可以看出:
DOWN事件分发到SonButton就消耗掉了,Button的onTouchEvent的默认返回值是为ture的,而当分发MOVE事件时,FatherLayout的子View也就是SonButton收到了CANCEL事件,这一次是没有触发的FatherLayout的onTouchEvent的,下次以及后续的MOVE和UP都交给FatherLayout处理,并返回ture分发到此结束。
换句话说:父级View拦截事件流时,如果同一事件流中,拦截之前已经有事件到达了子View中,那么子View会收到CANCEL事件,用于结束子View事件流(按钮的状态恢复)
5 前置条件:如果在FatherLayout的onInterceptTouchEvent()的DOWN事件返回false,MOVE事件返回true,也就是不拦截按下事件,但是拦截移动事件,同时onTouchEvent也返回false。点击按钮,打印日志如下:
从上述日志可以看出:DOWN事件分发到SonButton结束,第一次的MOVE事件分发到SonButton的dispatchTouchEvent的CANCEL结束,而后续的MOVE以及UP事件,分发到FatherLayout的onTouchEvent然后跳过父级GrandFatherLayout直接抛给了Activity的onTouchEvent了。 结论View不消耗MOVE和UP事件时(消耗了DOWN事件),事件不经过父级,而是直接抛给Activity。(如果这里的View是ViewGroup,它的子View消耗了也算他消耗了???)
6 前置条件:如果在FatherLayout的onInterceptTouchEvent()的返回true, 同时让 onTouchEvent的ACTION_DOWN事件时返回false,ACTION_MOVE和ACTION_UP事件返回true。点击按钮,打印日志如下:
从上述日志可以看出:如果没有消耗DOWN事件,那么后续的事件就不会再交给它处理了,而是上个事件处理的人继续处理。正常情况下,一个事件序列只能被一个角色消耗。