上一章主要说了View分发的简单流程,下面通过源码来看清楚分发机制
科普:当一个点击事件发生后,最开始的传递过程是这样的:Activity–Window–顶级View,当顶级View收到事件后,就会按分发机制把事件分发下去
当事件传递到顶级View(我们在Activity通过setContentView那个,是个ViewGroup来的),调用dispatchTouchEvent方法(拦截部分和分发事件部分)
拦截部分:
// Check
for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget !=
null) {
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 {
// There are
no touch targets
and this action
is not an initial down
// so
this view group continues to intercept touches.
intercepted =
true;
}
可以看到有个拦截属性intercepted,有两种情况要判断(有一个为true就调用onInterceptTouchEvent询问自己是否要拦截)
actionMasked == MotionEvent.ACTION_DOWN:当前点击事件为按下时,ViewGroup总是会问自己是否要拦截事件(调用onInterceptTouchEvent方法,默认不拦截)mFirstTouchTarget != null:如果不拦截Down事件,在分发过程中,若有子View成功消费Down事件,则mFirstTouchTarget 指向那个子View
结论:若上面两个条件都为false,即在一个事件序列中ViewGroup拦截Down事件或者没有子View想要处理Down事件,则后面的Move,Up事件来到ViewGroup都会直接跳过onInterceptTouchEvent方法(既然子View都不要down事件了,更何况Move,Up事件),把intercepted 赋值为true
特殊情况:子View可以通过requestDisallowInterceptTouchEvent(boolean)方法来设置父ViewGroup无法拦截出了down以外的其它事件(设置父类标志位FLAG_DISALLOW_INTERCEPT实现),因为ViewGroup在Down事件来到时会对FLAG_DISALLOW_INTERCEPT进行重置,所以父类肯定能收到Down事件
事件分发部分(前提是ViewGroup不拦截):
final View[] children = mChildren;
for(
int i = childrenCount -
1;i >=
0;i--){
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child,
null)) {
ev.setTargetAccessibilityFocus(
false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget !=
null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev,
false, child, idBitsToAssign)) {
mLastTouchDownTime = ev.getDownTime();
if (preorderedList !=
null) {
for (
int j =
0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
}
else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget =
true;
break;
}
}
遍历ViewGroup可以接受点击事件的子View(子View是否在播放动画和点击事件是否落在子View区域上),调用它们的dispatchTouchEvent,若有一个子View的dispatchTouchEvent返回true,则ViewGroup的mFirstTouchTarget 指向该View,跳出遍历,后面的点击事件都交给该子View处理
当事件传递给View时,调用dispatchTouchEvent方法
public boolean
dispatchTouchEvent(MotionEvent
event) {
boolean result =
false;
......
if (onFilterTouchEventForSecurity(
event)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(
event)) {
result =
true;
}
ListenerInfo li = mListenerInfo;
if (li !=
null && li.mOnTouchListener !=
null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(
this,
event)) {
result =
true;
}
if (!result && onTouchEvent(
event)) {
result =
true;
}
}
......
return result;
}
View的dispatchTouchEvent比较简单,首先会检查是否设置了OnTouchListener
有的话就去调用onTouch方法,如果onTouch返回true,则给result赋值true,导致它的onTouchEvent方法执行不了,所以onTouch方法优先级比onTouchEvent方法高,如果onTouch返回false,则继续执行onTouchEvent没有的话就去执行onTouchEvent方法 下面是View的部分onTouchEvent方法:
......
if(((viewFlags
&CLICKABLE)
==CLICKABLE
||
(viewFlags
&LONG_CLICKABLE)
==LONG_CLICKABLE)
||
(viewFlags
&CONTEXT_CLICKABLE)
==CONTEXT_CLICKABLE){
switch (action) {
case MotionEvent
.ACTION_UP:
boolean prepressed
= (mPrivateFlags
& PFLAG_PREPRESSED)
!= 0;
if ((mPrivateFlags
& PFLAG_PRESSED)
!= 0 || prepressed) {
......
if (
!mHasPerformedLongPress
&& !mIgnoreNextUpEvent) {
removeLongPressCallback();
if (
!focusTaken) {
if (mPerformClick
== null) {
mPerformClick
= new PerformClick();
}
if (
!post(mPerformClick)) {
performClick();
}
}
}
......
break;
case MotionEvent
.ACTION_DOWN:
......
break;
case MotionEvent
.ACTION_CANCEL:
......
break;
case MotionEvent
.ACTION_MOVE:
......
break;
}
return true;
}
......
解析:主要有两点
最上面的判断语句中如果View的CLICKABLE或LONG_CLICKABLE中有一个为true的话,则onTouchEvent方法就会返回true,消费事件,注意所有view的LONG_CLICKABLE默认为false,CLICKABLE的话看具体的控件,button默认为true,textview默认为false在UP事件中,会调用performClick(),如果view设置了OnClickListener,那么在performClick()里面会调用onClick方法
perfirnClick()方法如下:
public boolean performClick() {
final boolean result;
final ListenerInfo li = mListenerInfo;
if (li !=
null && li.mOnClickListener !=
null) {
playSoundEffect(SoundEffectConstants.CLICK);
li.mOnClickListener.onClick(
this);
result =
true;
}
else {
result =
false;
}
sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
return result;
}
到此View的事件分发机制就分析结束了,在这里总结一下:
当点击事件发生传递到顶级View时(根ViewGroup),调用它的dispatchTouchEvent方法,然后在调用拦截方法询问是否拦截
拦截,调用自己onTouchEvent,后面的同一序列事件来到时,不在调用拦截方法,直接调用自己onTouchEvent方法不拦截,遍历子View看谁需要处理事件,调用子View的dispatchTouchEvent方法(看2)调用View的dispatchTouchEvent方法时
如果设置onTouch方法,则先调用onTouch,若返回true,事件消费,onTouchEvent无法被执行;若返回false,onTouchEvent执行如果没有设置onTouch方法,则调用onTouchEvent,返回true则消费,后面其它同一序列事件都给这个View处理,若返回false则让父ViewGroup继续遍历其它子View调用onTouchEvent方法时:
如果该View的CLICKABLE或LONG_CLICKABLE中有一个为true的话,则返回true消费事件如果有设置OnClickListener的话,会在performClick()中调用该View的onClick()
这两篇关于View的事件分发机制部分出自书籍《Android开发艺术探索》,目前我在看本书看的不能自拔,这里也建议大家感兴趣的话可以去看一看。最后谢谢大家观看,希望大家看到这篇文章都能有收获
转载请注明原文地址: https://ju.6miu.com/read-667550.html