前言
前面一篇博客我们简单分析了View的onTouchListener、onTouchEvent、onClickListener执行时机,并总结了一些结论,这一篇我们分析下ViewGroup的事件分发流程。Android View的onTouchListener、onTouchEvent、onClickListener执行时机
示例演示
布局显示
这里我们定义了一个TouchViewGroup
继承自LinearLayout
和一个TouchView
继承自View
,其中TouchView
包裹在TouchViewGroup
中。
相关代码
- 布局文件
<androidx.constraintlayout.widget.ConstraintLayout 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"
tools:context=".MainActivity">
<com.crystal.view.TouchViewGroup
android:layout_width="match_parent"
android:layout_height="300dp"
android:background="@color/red"
android:gravity="center"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent">
<com.crystal.view.TouchView
android:id="@+id/touchView"
android:layout_width="100dp"
android:layout_height="100dp"
android:background="@color/green" />
</com.crystal.view.TouchViewGroup>
</androidx.constraintlayout.widget.ConstraintLayout>
- TouchView
class TouchView : View {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
Log.d(
"TAG",
"View --> dispatchTouchEvent:" + if (event.action == ACTION_DOWN) "ACTION_DOWN" else if (event.action == ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
)
return super.dispatchTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d("TAG", "View --> onTouchEvent:" + if (event.action == ACTION_DOWN) "ACTION_DOWN" else if (event.action == ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP")
return super.onTouchEvent(event)
}
}
- TouchViewGroup
class TouchViewGroup : LinearLayout {
constructor(context: Context?) : super(context)
constructor(context: Context?, attrs: AttributeSet?) : super(context, attrs)
constructor(context: Context?, attrs: AttributeSet?, defStyleAttr: Int) : super(
context,
attrs,
defStyleAttr
)
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
Log.d(
"TAG",
"ViewGroup --> dispatchTouchEvent:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
)
return super.dispatchTouchEvent(event)
}
override fun onInterceptTouchEvent(event: MotionEvent): Boolean {
Log.d(
"TAG",
"ViewGroup --> onInterceptTouchEvent:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
)
return super.onInterceptTouchEvent(event)
}
override fun onTouchEvent(event: MotionEvent): Boolean {
Log.d(
"TAG",
"ViewGroup --> onTouchEvent:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP"
)
return super.onTouchEvent(event)
}
}
- Activity中代码
val touchView = findViewById<TouchView>(R.id.touchView)
touchView.setOnTouchListener { v, event ->
Log.d("TAG", "View --> TouchListener:" + if (event.action == MotionEvent.ACTION_DOWN) "ACTION_DOWN" else if (event.action == MotionEvent.ACTION_MOVE) "ACTION_MOVE" else "ACTION_UP")
false
}
touchView.setOnClickListener {
Log.d("TAG", "View --> OnClickListener")
}
点击TouchView打印结果
ViewGroup --> dispatchTouchEvent:ACTION_DOWN
ViewGroup --> onInterceptTouchEvent:ACTION_DOWN
View --> dispatchTouchEvent:ACTION_DOWN
View --> TouchListener:ACTION_DOWN
View --> onTouchEvent:ACTION_DOWN
ViewGroup --> dispatchTouchEvent:ACTION_MOVE
ViewGroup --> onInterceptTouchEvent:ACTION_MOVE
View --> dispatchTouchEvent:ACTION_MOVE
View --> TouchListener:ACTION_MOVE
View --> onTouchEvent:ACTION_MOVE
ViewGroup --> dispatchTouchEvent:ACTION_UP
ViewGroup --> onInterceptTouchEvent:ACTION_UP
View --> dispatchTouchEvent:ACTION_UP
View --> TouchListener:ACTION_UP
View --> onTouchEvent:ACTION_UP
View --> OnClickListener
那为什么执行顺序是这样的呢?接下来我们就分析下ViewGroup的dispatchTouchEvent
方法。
源码分析
public boolean dispatchTouchEvent(MotionEvent ev) {
...
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 当点击事件为ACTION_DOWN时
if (actionMasked == MotionEvent.ACTION_DOWN) {
//这里简单理解成清除touchTarget,将mFirstTouchTarget=null
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 标志是否拦截事件
final boolean intercepted;
//当点击事件为ACTION_DOWN时,命中If(即使第一次mFirstTouchTarget为null)
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//这里也比较关键,FLAG_DISALLOW_INTERCEPT标志可以通过调用requestDisallowInterceptTouchEvent方法进行调整【表示子view不想让父控件通过调用onInterceptTouchEvent进行事件拦截】
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//这里默认disallowIntercept是false,则会命中If,调用ViewGroup的onInterceptTouchEvent(ev)
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
intercepted = true;
}
....
//这里由于点击事件没有canceled且intercepted = onInterceptTouchEvent(ev)默认为false,则命中If
if (!canceled && !intercepted) {
.....
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
.....
//newTouchTarget没有值并且childrenCount 子View不为空
if (newTouchTarget == null && childrenCount != 0) {
...
final View[] children = mChildren;
//注意这里为倒序遍历,例如当ViewGroup为RelativeLayout时,最上层的子View优先响应触摸事件
for (int i = childrenCount - 1; i >= 0; i--) {
...
newTouchTarget = getTouchTarget(child); //找到触摸的子child
....
// 当child不为null时,调用child.dispatchTouchEvent(event)进行事件分发【本文对应TouchView】
//当child为null时,调用super.dispatchTouchEvent(event)进行事件分发【ViewGroup同样继承自View,其实就是调用View的dispatchTouchEvent方法】
//返回值表示是否消费此事件
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
...
//当dispatchTransformedTouchEvent为true时,会调用addTouchTarget方法,这里会对mFirstTouchTarget进行赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
break;
}
....
}
....
}
...
}
}
//如果没有子view处理本次事件,则调用自己的【父类为View】dispatchTouchEvent(event)进行事件分发【注意这里的第三个参数为null】
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
// 如果已经有子view处理事件并且找到了,方法返回true
handled = true;
} else {
// 如果事件被拦截,会走这里
// 还没有找到相应的子view,就依次调用每个touchTarget的子view或viewGroup父类(View)的dispatchTouchEvent()
// 一旦有一个的dispatchTouchEvent()返回true,整体就返回true
// 如果事件被拦截,就销毁target链表
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
...
return handled;
}
总结
对于ViewGroup
而言:
-
事件分发总是从
dispatchTouchEvent
开始,如果onInterceptTouchEvent
返回false,即不进行事件拦截时,事件会传递到子view,由子view进行处理,子view不消费,则事件会流传回ViewGroup中进行处理; -
对于存在多个子View的情况,由于是倒序遍历,事件优先从最上层View向下进行传递;
-
如果子View的
dispatchTouchEvent(event)
方法返回true,则表示子View消费了本次事件【dispatchTransformedTouchEvent方法返回true,对mFirstTouchTarget进行了赋值操作】,则后续均交给此View进行处理; -
如果子View没有消费事件,则会交给ViewGroup自己进行处理,执行
View.dispatchTouchEvent(event)
方法; -
可以通过调用
requestDisallowInterceptTouchEvent方法
进行拦截,传递true,表示不允许ViewGroup进行事件拦截;