1.事件分发机制
事件分发简单而言就是点击事件怎么从Activity流转到ViewGroup,然后流转到View,最后这个点击事件有谁处理,怎么处理?时间分发有三个重要的方法,dispatchTouchEvent(分发事件的方法),onInterceptTouchEvent(拦截事件的方法),onTouchEvent(处理事件的方法)搞清楚Activity,ViewGroup和View的这三个方法,事件分发的机制也就了解了,咱们一个类一个类的进行讲解,首先深呼吸放松一下,然后进入代码的世界,大家慢慢的看。
2.事件分发u型图(网络流行的一个图,能详细反应这个事件分发的流程,我这里直接搬过来了)
3.Activity层的处理
//很简单捕捉点击向下按下的事件,然后执行个空方法,然后判断Window是不是对事件进行了处理有处理Activity就不管了,没有处理得对事件进行处理
```事件分发由Activity开始
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
//由window处理分发
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
getWindow只有一个实现类,PhoneWindow
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
交给DecorView
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
最后流转到ViewGroup的dispatchToucEvent处理
**3.ViewGroup层的处理**
dispatchTouchEvent方法
//每次Action_Down事件 都会清空标志位如清空disallowIntercept (是否允许拦截)
//清空mFirstTouchTarget这些操作,所以从点击的瞬间一系列操作跟之前的点击没有关系
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Throw away all previous state when starting a new touch gesture.
// The framework may have dropped the up or cancel event for the previous gesture
// due to an app switch, ANR, or some other state change.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//处理的结果
boolean handled = false;
//伪代码实现 mFirstTouchTarget 点击事件的处理绑定的View 子布局
//只有是第一次触摸的ACTION_DOWN 按下的瞬间或者 有子的布局处理这个事件的时候 会对事件判断是否拦截
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//FLAG_DISALLOW_INTERCEPT 通过这个标志判断是否对他进行拦截
disallowIntercept 默认是false的,可以拦截
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
//只要进入到ViewGroup的 dispatchTouchEvent 这个方法证明上次事件处理是在ViewGroup里面执行的OnTouchEvent返回的true
//所有默认是拦截的,因为如果dispatchTouchEvent 返回的是false,根本不会到ViewGroup的dispatchTouchEvent方法
intercepted = true;
}
//当当前不拦截的时候,并且down事件的时候才会循环遍历每个子View 判断他们对事件是不是进行了处理,在不在这个区域
if(!intercepted){
if (action == MotionEvent.ACTION_DOWN) {
final View[] children = mChildren;
final ArrayList<View> preorderedList = buildTouchDispatchChildList();//这里面有一个根据Z轴坐标进行排序,z轴越大排在List后面,Z轴越大,代表View越在前面,会遮挡z轴小的,所以从后往前去,后面的view会遮挡前面的View
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//判断点击的区域是不是在View 的范围内
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//在点击区域内调用 实际调用的是 子View的dispatchTouchEvent
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//判断是不是子View已经处理过这个事件
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
//链表保存处理点击事件的VIew,新的处理的VIew放到链表的前面,支持多指触控所以用链表
//https://www.jianshu.com/p/1b78ccc82b8b (多指触控)
mFirstTouchTarget指向最新的VIew
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
//伪代码
if (mFirstTouchTarget == null) {
// 没有子View处理,则当前的View处理,真实的代码 最后真实调用的onTouchEvent(ev);
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//子View处理
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//在之前是不是已经让子View 处理过了
handled = true;
} else {
//之前是子View处理了,现在判断是不是当前View拦截了,要进行处理
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//这个时候是move的情况,还没有处理过事件分发给子View处理,然后
cancelChild为true的话 ,把event.setAction(MotionEvent.ACTION_CANCEL);
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果当前view拦截的话要清空mFirstTouchTarget,以后mFirstTouchTarget就为空了,以后事件就自己处理了,不会经过
子view处理了, cancelChild 就是当时间分发机制改变的是时候,当前view拦截了点击,清空mFirstTouchTarget 这些东西,然后
//执行上面那句 if (mFirstTouchTarget == null) {}交给自己处理
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
}
总结:当点一次点击event是down事件的时候判断ViewGroup是不是对事件进行拦截,如果进行的话进入ViewGroup的OnTouchEvent();如果不进行拦截进入ViewGroup的dispatch方法里面,当event事件为move的时候首先会判断有没有子View 处理这个时间,有的话,还得判断是不是拦截事件,然后依次执行,如果没有子view拦截的时候直接进入自己的OnTouchEvent(前提是会进入ViewGroup的Dispatch方法,进入前提是ViewGroup或者子View会对事件进行处理,如果都没有处理,在ViewGroup的父布局直接就会返回)
②onInterceptTouchEvent拦截方法
是否对事件进行拦截,拦截的话ViewGroup的onTouchEvent对事件进行处理,不拦截的话会调用子view的dispatchTouchEvent
3.View层的处理
①dispatchTouchEvent方法
boolean result;
//首先用ToucheLister判断 对事件处理与否,如果不处理才会调用自身的onTouchEvent
if(mOnTouchListener !=null && ENABLE
&& mOnTouchListener.onTouch(this, event)){
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
return result;
②onTouchEvent方法
//是否可以点击CLICKABLE(是否可以点) LONG_CLICKABLE(是否可以长按)
// CONTEXT_CLICKABLE每个子控件的这几个默认的属性不一样
//具体的得具体分析
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
//控件的状态是Disable的时候不会执行下面的OnClick事件
if ((viewFlags & ENABLED_MASK) == DISABLED) {
return clickable;
}
//如果是可以点击的状态 直接返回true 对这个事件进行消耗
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
//真实调用的是onClick事件
performClickInternal();
break;
}
return true;
}
3.总结
上面我对整个事件分发机制整体进行了概括,有的细节比较简略,如果有什么遗漏的重点内容,大家可以在下面进行评论,谢谢大家!