前提须知:</br>
1、DecorView是activity的根布局。</br>
2、activity的window是PhoneWindow实例。</br>
3、DecorView是一个FrameLayout。</br>
4、ViewGroup继承自View。没有重载View#onTouchEvent()方法。</br>
5、源码版本为android 9.0。</br>
一图胜千言(PhoneWindow、DecorView和Activity布局的关系)(ps:盗用下辉哥的图):
image
简单介绍:</br>
ViewGroup:</br>
1、ViewGroup#dispatchTouchEvent() 对ViewGroup的事件进行分发。</br>
2、ViewGroup#onInterceptTouchEvent() 对ViewGroup的事件进行拦截。</br>
3、ViewGroup#onTouchEvent() 对ViewGroup的事件进行处理(消费)。</br>
View:</br>
1、View#dispatchTouchEvent() 对View的事件进行分发。</br>
2、View#onTouchEvent() 对View的事件进行处理(消费)。</br>
MotionEvent:</br>
1、MotionEvent.ACTION_DOWN Down事件 用户手指按下时触发。</br>
2、MotionEvent.ACTION_MOVE Move事件 用户手指在屏幕上移动时触发</br>
3、MotionEvent.ACTION_UP Up事件 用户手指抬起,离开屏幕时触发</br>
4、MotionEvent.ACTION_CANCEL Cancel事件 </br>
完整的MotionEvent = MotionEvent.ACTION_DOWN + n*MotionEvent.ACTION_MOVE + MotionEvent.ACTION_UP
其他:</br>
1、onTouchEvent()返回true时,表示该View或者ViewGroup消费此事件;返回false时,表示该View或者ViewGroup没有消费事件。</br>
2、可对View或ViewGroup设置View.OnTouchListener进行触摸事件监听。OnTouchListener#onTouch()可以对事件进行监听并消费事件。
测试Demo:
image定义一个继承View的MyButton类,重载dispatchTouchEvent()和onTouchEvent(),并添加打印输出。</br>
定义一个继承View的MyView类,重载dispatchTouchEvent()和onTouchEvent(),并添加打印输出。</br>
定义一个继承FrameLayout的MyViewGroup类,重载dispatchTouchEvent()、onInterceptTouchEvent()和onTouchEvent(),并添加打印输出。(继承LinearLayout不需要重载onLayout()方法摆放子View)</br>
注意:</br>
不可继承自Button,否则会默认消费事件。
#MainActivity.kt
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
daqiBtn.setOnTouchListener(object :View.OnTouchListener{
override fun onTouch(v: View?, event: MotionEvent?): Boolean {
when(event?.action){
MotionEvent.ACTION_DOWN -> {
Log.d("daqia","View OnTouchListener ACTION_DOWN")
}
MotionEvent.ACTION_MOVE -> {
Log.d("daqia","View OnTouchListener ACTION_MOVE")
}
MotionEvent.ACTION_UP -> {
Log.d("daqia","View OnTouchListener ACTION_UP")
}
}
return false
}
})
}
}
#activity_main.xml
<com.daqi.daqitouchevent.MyViewGroup
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<com.daqi.daqitouchevent.MyButton
android:id="@+id/daqiBtn"
android:layout_width="120dp"
android:layout_height="50dp"
android:layout_marginLeft="10dp"
android:layout_marginTop="10dp"
android:background="@color/colorAccent"/>
</com.daqi.daqitouchevent.MyViewGroup>
Activity:
当手指触碰屏幕时,触摸事件最先达到Activity#dispatchTouchEvent()。所以我们先来看看Activity的dispatchTouchEvent()。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//空方法
onUserInteraction();
}
//其实就是调用PhoneWindow的superDispatchTouchEvent()
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
1、Down事件会执行onUserInteraction()方法。Activity#onUserInteraction()是一个空方法,而且翻阅了FragmentActivity和AppCompatActivity都没有发现重载onUserInteraction()方法。所以默认当初空方法处理。</br>
2、Activity中的window是PhoneWindow实例,所以getWindow().superDispatchTouchEvent(ev)其实是调用PhoneWindow的superDispatchTouchEvent()方法。</br>
跳转到PhoneWindow#superDispatchTouchEvent(),发现调用的是mDecor的superDispatchTouchEvent()方法,而mDecor就是DecorView的实例。
#PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
3、跳转到DecorView#superDispatchTouchEvent(),看到调用父类的方法super.dispatchTouchEvent(event)。</br>
DecorView继承自FrameLayout,但FrameLayout没有重载dispatchTouchEvent(event)方法,即super.dispatchTouchEvent(event)调用的是ViewGroup#dispatchTouchEvent(event)
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
所以,当DecorView#dispatchTouchEvent()返回false时,会调用Activity#onTouchEvent()处理(消费)该事件。否则表示布局中已有组件处理(消费)了事件。
View:
View作为最底层的"叶子",并不涉及太多的事件分发且没有拦截机制,所以先从View开始研究事件分发。</br>
View#dispatchTouchEvent
//只显示主要代码,其他代码用//....屏蔽掉
public boolean dispatchTouchEvent(MotionEvent event) {
//....
boolean result = false;
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
//1、down事件下来先停止嵌套滑动
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//....
//ListenerInfo中定义了一堆监听事件类变量,我们设置的view监听事件,都是存储到这里。
//2、比如OnClickListener、OnLongClickListener等
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
//3、如果没有定义onTouch监听事件,则result为false,并直接执行onTouchEvent(event)
//如果定义了onTouch监听事件,当mOnTouchListener.onTouch返回true时,result则为true,导致下面!result为false,onTouchEvent(event)无法被执行。
//(mViewFlags & ENABLED_MASK) == ENABLED 判断组件是否为启动状态
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
//....
//4、
return result;
}
1、先停止嵌套滑动</br>
2、ListenerInfo中定义了很多View的监听事件实例,可从ListenerInfo中获取OnTouchListener实例,回调onTouch()方法。调用setOnTouchListener其实是将传进来的View.OnTouchListener赋值到ListenerInfo的OnTouchListener变量中。
3、当li.mOnTouchListener != null时,会回调View.OnTouchListener#onTouch(),如果我们在onTouch()方法中返回true,表示消费此事件交由onTouch()处理(消费),不会调用View#onTouchEvent()来处理(消费)事件;反之,在onTouch()中返回false,表示不消费此事件,则事件交由onTouchEvent()处理(消费)。</br>
当没有设置View.OnTouchListener监听时,result默认为false,事件默认交由View#onTouchEvent()处理(消费)。
4、当View#onTouchEvent()或者OnTouchListener#onTouch()返回true时(两者只调用一个),View#dispatchTouchEvent()整体返回true。反之,View#dispatchTouchEvent()整体返回false。
View#onTouchEvent
public boolean onTouchEvent(MotionEvent event) {
//1、获取view是否可以点击(点击,长按等)
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//2、判断当前组件是否处于非启动(禁用)状态,即enabled是否为false
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn‘t respond to them.
// 如果为禁用状态且组件设置了点击事件,则会消费该事件,但不作响应。
return clickable;
}
//android有效触摸的范围是48dp,当组件太小,而又因为布局问题不能设置padding时。
//3、可对父组件设置TouchDelegate,以增大点击面积。
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
//4、当组件可点击时,响应点击事件。
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//..
break;
case MotionEvent.ACTION_DOWN:
//..
break;
case MotionEvent.ACTION_CANCEL:
//..
break;
case MotionEvent.ACTION_MOVE:
//..
break;
}
//只要组件是可点击的,必定消费事件
return true;
}
//5、组件不可点击的,则不会消费事件。
return false
}
1、依据组件的设置点击监听和长按监听的情况,获取表示组件是否可点击的布尔值clickable。</br>
2、当组件处于禁用(DISABLED)状态,返回clickable。如果组件可点击或长按,则会返回true消费事件,但不做处理。(该View处于非启动(禁用)状态,enabled为false)</br>
3、当组件设置了TouchDelegate时,则会直接消费事件。(子View自身体积太小不利于点击,但又限制于布局无法增大自身大小,可对父组件设置TouchDelegate,以增大点击面积。TouchDelegate不在讨论范围)</br>
4、如果View设置了点击监听或长按监听,则会依据事件进行不同的处理,但最后还是会return true消费事件。(switch语句结束后返回true)</br>
5、否则View没有设置点击监听或长按监听,则return false不消费事件。</br>
注意:</br>
1、onTouchLinstener在View#dispatchTouchEvent()中回调,如果重载dispatchTouchEvent,而不调用父类的dispatchToucEvent,则不会有onTouch()回调。</br>
2、onLongClickListener和onClickListener在View#onTouchEvent()中回调,同理,不调用父类的onTouchEvent(),则不会有onClick()和onLongClick()回调。
ViewGroup:
ViewGroup中涉及到更多事件分发和事件拦截机制,会比较难懂。先看一遍源码,再依据demo中的现象在源码中寻找答案,从而更好的学习和加深对源码的认识。
ViewGroup#dispatchTouchEvent()
#ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//....
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//1、在开始新的触摸手势时丢弃所有先前的状态,重置Touch状态标识
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//标记是否拦截事件
final boolean intercepted;
//2、down事件或已经有处理Touch事件的目标了
//当子控件没有消费事件或者被拦截mFirstTouchTarget为空
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//3、标记是否禁止拦截
//因为在其他地方可能调用了requestDisallowInterceptTouchEvent(boolean disallowIntercept)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//回调拦截方法
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//4、
intercepted = true;
}
//...
}
1、Down事件是整套MotionEvent事件的开始,先丢弃所有先前的状态,重置Touch状态标识。</br>
2、actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null 事件为down事件时,等式成立,进入if语句。</br>
3、获取一个布尔值disallowIntercept(字面意思:不允许拦截),该布尔值决定着调不调用ViewGroup#onInterceptTouchEvent(),而ViewGroup#onInterceptTouchEvent()正是用来拦截事件的。</br>
disallowIntercept赋值true还是false,由当前ViewGroup中的子View决定。</br>
当子View调用requestDisallowInterceptTouchEvent(boolean)方法请求父类不要拦截时,该disallowIntercept为true,标记是否拦截的布尔值intercepted写死为false,表示不拦截。</br>
当没有子View调用requestDisallowInterceptTouchEvent(boolean)方法时,该disallowIntercept为false,回调ViewGroup#onInterceptTouchEvent()。ViewGroup可以在onInterceptTouchEvent()中对事件进行拦截。</br>
4、当不是Down事件 和 mFirstTouchTarget为null时,intercepted = true。先记住,后面用到。(重点mFirstTouchTarget为null)</br>
#ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//...
//每次分发事件前,将newTouchTarget置空
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
//5、不是取消状态且没拦截的情况下
if (!canceled && !intercepted) {
//....
//6、判断(x,y)在该View的可视范围内。true则继续往下执行。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//....
final View[] children = mChildren;
//7、遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
//....
//通过dispatchTransformedTouchEvent()将事件分发下去
//dispatchTransformedTouchEvent返回true时,表示子view消费了事件
//当第三参数传child(View实例)时,调用该ViewGroup的子View的dispatchTouchEvent()
//当第三参数传null时,调用父类(即View)的dispatchTouchEvent()
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//8、对mFirstTouchTarget进行赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记已派发事件给新的触摸对象
alreadyDispatchedToNewTouchTarget = true;
break;
}
//...
}
//9、如果遍历完子View,newTouchTarget没有被赋值,即本次没有消费该事件的子View,但之前有子View的消费事件记录(mFirstTouchTarget 不为空)
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
//该事件没有找到触摸对象,但之前有触摸对象,则将新的触摸对象指向最近的触摸对象
newTouchTarget = mFirstTouchTarget;
//从链表表头触摸对象开始遍历,寻找下一个触摸对象,指定找到最后的(第一个)触摸对象为止,将其赋值给newTouchTarget变量
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
//....
}
5、上面对定义的是否拦截的布尔值intercepted,在判断语句 !canceled && !intercepted 用上了。if(!canceled && !intercepted)内会对事件进行事件分发。intercepted为true时,表明事件被该ViewGroup拦截了,不会将事件分发给该ViewGroup的子View。</br>
6、ViewGroup#isTransformedTouchPointInView()用于判断该事件的坐标(x,y)是否在子View可视范围内。符合条件才继续往下执行。
7、遍历自己的子View,通过dispatchTransformedTouchEvent()将事件分发出去。dispatchTransformedTouchEvent返回true时,表示子view消费了事件。(第三个参数传递了当前遍历的子View)</br>
子View的遍历是倒序来得,其实是先选择点击范围内的顶层View,再一层一层将事件分发下去。
#ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//....
// Perform any necessary transformations and dispatch.
//child为空 调用super.dispatchTouchEvent(),即调用View#dispatchTouchEvent(),而View#dispatchTouchEvent()中会调用View#onTouchEvent()
if (child == null) {
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//调用子View的dispatchTouchEvent(),将事件分发下去。
handled = child.dispatchTouchEvent(transformedEvent);
}
//....
}
当第三参数传 child(View实例) 时,调用该子View的dispatchTouchEvent()。</br>
当第三参数传 null 时,调用父类(即View)的dispatchTouchEvent()。而View#dispatchTouchEvent中,会调用View#onTouchEvent()。即调用自身的onTouchEvent(),因为ViewGroup没有重载onTouchEvent()。</br>
8、dispatchTransformedTouchEvent()的返回值作为if语句的判断条件。当if语句为true时,说明子View消费事件。然后调用addTouchTarget(),将子View封装成TouchTarget,并将其赋值到mFirstTouchTarget。break退出循环,不再分发Move事件。(也就是说只把事件分发给一个消费该事件的子View)</br>
#ViewGroup.java
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
//对mFirstTouchTarget进行赋值
mFirstTouchTarget = target;
return target;
}
TouchTarget是一个链表解构,可以看到将target最终会赋值到mFirstTouchTarget,而上一步是将target的下一个索引指向mFirstTouchTarget,也就是说target的下一个索引指向的是上一个消费事件的target,而本次消费事件的target作为链表的第一个元素。</br>
然后将alreadyDispatchedToNewTouchTarget设为true,标记已分发事件给新的TouchTarget</br>
9、遍历完子View,newTouchTarget仍然没有被赋值,即本次子View没有消费事件。但mFirstTouchTarget 不为空,表示之前有子View的消费事件的记录。则遍历链表,找出第一个消费事件的target,将其赋值给newTouchTarget对象。
#ViewGroup.java
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//....
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.(没有触摸目标,所以将其视为普通视图。)
//10、即ViewGroup调用父类(View)的dispatchTouchEvent()
// 如果View的dispatchTouchEvent中返回true,消费了此事件,则handled会为true,ViewGroup#dispatchTouchEvent()返回true。表示告诉该ViewGroup的父视图,该视图消费了事件。
// 否则,无子View消费事件,自身也不消费事件的情况下,则该ViewGroup#dispatchTouchEvent()返回false,表示不消费事件
// 因为模版设计模式,通过View的dispatchTouchEvent进行对ViewGroup的onTouch() 和 onTouchEvent()进行调用。
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//11、遍历TouchTargt树,分发事件
while (target != null) {
final TouchTarget next = target.next;
//如果新的触摸目标依据派发过事件 且当前触摸目标等于新的触摸目标。
//alreadyDispatchedToNewTouchTarget在上面子View消费会进行赋值为true
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//既然子View依据消费了事件,则ViewGroup#dispatchEvent()必定返回true
handled = true;
} else {
//标记是否让child取消处理事件
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//派发事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
//ViewGroup#dispatchEvent()返回true
handled = true;
}
//12、cancelChild为true时,将当前target从TouchTarget链表中移除
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
}
10、如果mFirstTouchTaget没有被赋值,则表示本次包括之前的,没有子View消费事件。调用ViewGroup#dispatchTransformedTouchEvent(),第三个参数传递null,调用自身的onTouch()和onTouchEvent()。自己尝试消费事件,如果自身不消费事件,则ViewGroup#dispatchTouchEvent()返回false。</br>
11、如果mFirstTouchTaget不为空,则遍历TouchTaget链表。</br>
alreadyDispatchedToNewTouchTarget为true只有在本次事件被子View消费的情况下不为空。即调用addTouchTaget()方法后设置为true。</br>
但newTouchTarget存在两种情况不为空:</br>
一种是本次事件被子View消费了,调用了addTouchTaget();</br>
一种是本次事件没有被消费,但之前有子View消费事件(可见ViewGroup#dispatchTouchTaget分析第9条)</br>
当本次事件被子View消费时,alreadyDispatchedToNewTouchTarget为true,且newTouchTarget等于mFirstTouchTarget。</br>
alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件成立,因为target初始赋值是mFirstTouchTarget。则直接将ViewGroup#dispatchTouchEvent()返回值设置为true。
alreadyDispatchedToNewTouchTarget && target == newTouchTarget条件不成立时,获取一个标记是否让child取消处理事件的布尔值cancelChild,同时以第二个参数传入ViewGroup#dispatchTransformedTouchEvent()。</br>
cancelChild的值由resetCancelNextUpFlag(target.child) || intercepted 决定。resetCancelNextUpFlag()看不懂,但当ViewGroup拦截事件时,intercepted = true,则cancelChild = true。</br>
当布尔值cancel为true时,也就是cancelChild为true时,会将事件修改为MotionEvent.ACTION_CANCEL,在分发给子View。然后复原MotionEvent。并将target从TouchTarget链表中移除。</br>
当布尔值cancel为false时,也就是cancelChild为false时,会将事件原封不动的分发给子View。走正常分发的流程。</br>
#ViewGroup.java
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,View child, int desiredPointerIdBits) {
final boolean handled;
//标记当前属于的MotionEvent类型
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//将事件改为MotionEvent.ACTION_CANCEL,并分发给子View
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
//复原当前的MotionEvent类型
event.setAction(oldAction);
return handled;
}
//....
}
总得来说就是:</br>
遍历TouchTarget链表,当本次事件已经被子View消费时,则将返回值设为true,表示本ViewGroup尝试消费该事件。</br>
判断当前是否发送MotionEvent.ACTION_CANCEL事件,如果intercepted为true导致cancelChild为true时,将事件改成ACTION_CANCEL事件,发送给链表每一个元素,同时将其从TouchTarget链表中删除。否则cancelChild为false时,将事件原封不动的发送给链表中的元素。</br>
当链表中的元素消费事件时,则将ViewGroup#dispatchTouchEvent()的返回值改为true。</br>
示例1、
正常的重载ViewGroup和View关于事件分发的方法,不做任何改动,但对事件进行打印输出。
imageDown和Up事件传递的顺序是:</br>
ViewGroup#dispatchTouchEvent() -> ViewGroup#onInterceptTouchEvent() -> View#dispatchTouchEvent -> View#onTouchEvent() -> ViewGroup#onTouchEvent()
现象:</br>
手指按下去后,无论怎么移动,然后抬起都是只有这5个打印。从打印看只触发了Down事件,没有Move事件和Up事件。并传递给自己的子View。
解析:</br>
dowm事件时,actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null条件是成立的。所以intercepted依据ViewGroup#onInterceptTouchEvent()返回值来赋值,ViewGroup#onInterceptTouchEvent()默认返回false。</br>
条件!canceled && !intercepted成立时,遍历子View,通过dispatchTransformedTouchEvent(child)对事件进行分发。即调用子View的dispatchTouchEvent()方法和View#onTouchEvent()方法。
当事件没有子View消费时,if(dispatchTransformedTouchEvent(child))条件不成立,导致mFirstTouchTarget依旧为空。
继续往下执行,到if (mFirstTouchTarget == null)时,条件成立。则调用dispatchTransformedTouchEvent(null)。传入的子View为null,调用父类也就是View的dispatchTouchEvent(),对ViewGroup的onTouchEvent()进行调用(ViewGroup没重载onTouchEvent())。
move事件时,由于已经不是down事件了,且mFirstTouchTarget依旧为空。则intercepted被设置为true。if(!canceled && !intercepted)条件不成立,无法进入该if语句中调用dispatchTransformedTouchEvent(child)对move事件进行分发。子View也就不会再接受到后续的事件。
疑问:</br>
不分发给View事件,但ViewGroup#dispatchTouchEvent()依据被调用,为什么没打印。</br>
因为该ViewGroup不是根布局,相对于DecorView,自定义的ViewGroup属于子View。是DecorView不分发事件下来,并且我们也没有消费事件。
示例2、
正常的重载ViewGroup和View关于事件分发的方法,在其中一个View#onTouchEvent()的down事件时返回true,并对事件进行打印输出。
image现象:</br>
donw事件中,事件都分发到ViewGroup的全部子View中。当子View2返回true消费事件后,后续事件都只返回给该子View组件。
解析:</br>
按照正常的事件分发流程,mFirstTouchTarget指向的child是子View2。当move事件下来时,intercepted由onInterceptTouchEvent()赋值,默认为false,条件if(!canceled && !intercepted)成立。但后续判断语句不成立。actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE(通过debug发现的,但看不懂)。不符合再次将事件全部分发给子View的情况。</br>
离开if(!canceled && !intercepted)语句。直接遍历TouchTarget链表,将事件分发给链表中的每一个元素。子View2在down事件时消费了事件,固存在与链表中,所以后续的事件都会分发给子View2。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!canceled && !intercepted) {
//不满足条件时表示不需要再重新寻找响应事件的View(看不懂)
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
}
}
}
示例3、
正常的重载ViewGroup和View关于事件分发的方法,在其中一个View#onTouchEvent()的down事件时返回true,ViewGroup在第一个move事件时拦截事件,并对事件进行打印输出。
image
现象:</br>
子View在onTouchEvent中return true,后续事件都会返回到自定义的ViewGroup中,但在第一个move事件时,ViewGroup#onInterceptTouchEvent()返回true将事件拦截了,交由ViewGroup处理事件,后续的事件都交由ViewGroup处理。同时子View接受到CANCEL事件。
解析:</br>
ViewGroup#onInterceptTouchEvent()返回true时,intercepted也为true。if(!canceled && !intercepted)条件不成立,直接遍历TouchTarget链表。</br>
但此时intercepted也为true,cancelChild = resetCancelNextUpFlag(target.child) || intercepted,也就是说cancelChild为true,当cancelChild第二个参数传入dispatchTransformedTouchEvent()时,会给子View发送CANCEL事件,并将该子View从链表中删除。直至到TouchTarget清空。</br>
当TouchTarget被清空后,mFirstTouchTarget为null,后续只调用ViewGroup#onTouchEvent()尝试消费事件。
总结归纳图:
image遗留问题:</br>
为什么子View消费了事件,mFirstTouchTarget!= null,在move事件时if(actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE)不成立?</br>
按debug看到的结果,直接跳到遍历TouchTarget链表来将后续的事件分发。不再通过分发事件寻找消费事件的子View,直接根据TouchTarget链表将事件分发下去可以理解。但链表什么情况下才会存在更多的元素,并且这段代码是在什么情况下才被使用到?希望大佬们指点下。
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
image
网友评论