版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/haohengyuan/article/details/52224125
序言
这篇博文不是对事件分发机制全面的介绍,只是从源码的角度分析ACTION_DOWN、ACTION_MOVE、ACTION_UP事件在ViewGroup中的分发逻辑,了解各个事件在ViewGroup的分发逻辑对理解、解决滑动冲突问题很有帮助。
ViewGroup中事件分发流程
这里我是用的是2.3.3版本的源码,原因在于这个版本的源码易读,当你理顺了整个分发流程,再去看其他更加高级版本的源码,你会发现思想是一样。如果一个事件传递给ViewGroup,那么dispatchTouchEvent方法会被调用,以下是对dispatchTouchEvent的分析。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (!onFilterTouchEventForSecurity(ev)) {
return false;
}
final int action = ev.getAction();
//获取事件的坐标
final float xf = ev.getX();
final float yf = ev.getY();
final float scrolledXFloat = xf + mScrollX;
final float scrolledYFloat = yf + mScrollY;
final Rect frame = mTempRect;
//disallowIntercept 默认是false,
//可以通过requestDisallowItercepctTouchEvent来设置参数
//被设置成true后,ViewGroup无法拦截除ACTION_DOWN以外的事件(只能拦截Down)
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//这里是ACTION_DOWN的处理逻辑
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
//We should probably send an ACTION_UP to the current target
mMotionTarget = null;
}
// If we're disallowing intercept or if we're allowing and we didn't
// intercept
//默认情况下disallowIntercept为false,表示允许拦截,
//默认情况ViewGroup的onInterceptTouchEvent返回false
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// reset this event's action (just to protect ourselves)
ev.setAction(MotionEvent.ACTION_DOWN);
// We know we want to dispatch the event down, find a child
// who can handle it, start with the front-most child.
final int scrolledXInt = (int) scrolledXFloat;
final int scrolledYInt = (int) scrolledYFloat;
final View[] children = mChildren;
final int count = mChildrenCount;
for (int i = count - 1; i >= 0; i--) {//遍历子View
final View child = children[i];
//判断子元素是否可以接收到事件,两条件决定
//条件一:子View是VISIBLE或者在播动画
//条件二:点击坐标落在子View区域内(体现在内嵌的if)
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
//判断是否点击坐标落在子控件区域内
if (frame.contains(scrolledXInt, scrolledYInt)) {
final float xc = scrolledXFloat - child.mLeft;
final float yc = scrolledYFloat - child.mTop;
ev.setLocation(xc, yc);
child.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
//将事件派发给子View,返回true表示子View处理该事件,
if (child.dispatchTouchEvent(ev)) {
// Event handled, we have a target now.
mMotionTarget = child;//将处理事件的目标View保存在变量
return true;//返回true,表示消耗事件
}
// The event didn't get handled, try the next view.
// Don't reset the event's location, it's not
// necessary here.
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
//重置mGroupFlags,
//使得在下一个事件ACTION_DOWN来临时disallowIntercept为false
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
final View target = mMotionTarget;
if (target == null) {//没有找到可以处理事件的子View
// We don't have a target, this means we're handling the
// event as a regular view.
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
//子控件不处理,所以此处判断一下自己是否处理
//此时ViewGroup调用的是父类View的dispatchTouchEvent
return super.dispatchTouchEvent(ev);
}
// if have a target, see if we're allowed to and want to intercept its
// events
//允许并且想要拦截事件
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
ev.setAction(MotionEvent.ACTION_CANCEL);//设置ACTION_CANCEL
ev.setLocation(xc, yc);
if (!target.dispatchTouchEvent(ev)) {//告知目标子控件事件被拦截
// target didn't handle ACTION_CANCEL. not much we can do
// but they should have.
}
// clear the target
mMotionTarget = null;//重置为null,使得下个事件来临时target=null
// Don't dispatch this event to our own view, because we already
// saw it when intercepting; we just want to give the following
// event to the normal onTouchEvent().
return true;//返回true,表示事件被消耗
}
if (isUpOrCancel) {
mMotionTarget = null;
}
// finally offset the event to the target's coordinate system and
// dispatch the event.
final float xc = scrolledXFloat - (float) target.mLeft;
final float yc = scrolledYFloat - (float) target.mTop;
ev.setLocation(xc, yc);
if ((target.mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
target.mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
mMotionTarget = null;
}
//将事件分发给目标子控件
return target.dispatchTouchEvent(ev);
}
我们知道一些操作会产生事件,比方说在屏幕上滑动一下,这样会产生一系列事件,但这些事件是属于同一序列,它以ACTION_DOWN事件开始,中间有若干个ACTION_MOVE事件,以ACTION_UP事件结束。
ACTION_DOWN事件分发过程
ACTION_DOWN事件被分发到ViewGroup的时候是如何进行逻辑判断的呢,在第31行代码可以看到,首先会通过条件:disallowIntercept||!onInterceptTouchEvent判断是否拦截,disallowIntercept 默认是false,可以通过requestDisallowItercepctTouchEvent来设置参数,若这个条件不成立表示拦截则此时mMotionTarget = null,则来到第80行,那么target = null,表示 没有找到可以处理事件的子控件,接下来执行第91行代码,此时调用super.dispatchTouchEvent即View的dispatchTouchEvent方法表示ViewGroup尝试自己处理事件;若条件成立表示不拦截,见第40行代码首先是遍历该ViewGroup的子控件,结合第45、49行代码可以知道在每遍历一个子控件的时候,首先判断子控件是不是visiable或者正在播动画并且点击事件的坐标落在子控件的区域内,假如两个条件同时满足则表明子控件可以接收事件,这时候会来到第55行代码,通过调用child.dispatchTouchEvent将事件分发给子控件,如果child.dispatchTouchEvent返回true,则表示事件被该子控件消耗了,此时执行第57行,该子控件被视作消耗事件的目标View并将其保存mMotionTarget变量中,返回true结束循环遍历;如果child.dispatchTouchEvent返回false,则表示该子控件没有消耗事件,如果该子控件不是ViewGroup遍历的最后一个子控件,则在继续循环遍历下一个子控件。如果此时该子控件是ViewGroup遍历的最后一个子控件,则表明所有的ViewGroup的子控件均不能处理事件,此时循环遍历结束,则mMotiononView = null,程序来到第80行,那么target = null,接下来调用super.dispatchTouchEvent即View的dispatchTouchEvent方法表明ViewGroup尝试自己处理事件.ACTION_DOWN事件用流程图表示如下:
ACTION_MOVE事件分发过程
ACTION_MOVE事件被分发到ViewGroup的时候,从第81行可以看到首先会判断target是否等于null,若等于null,表示子控件均不能消耗事件,则调用super.dispatchTouchEvent即View的dispatchTouchEvent来处理事件。若不等于空,此时会来到第97行,此时通过条件(!disallowIntercept&&onInterceptTouchEvent(ev))判断是否拦截,若条件不成立表示不拦截则执行第131行代码,调用target.dispatchTouchEvent将事件分发给目标子控件处理,如果拦截则首先生成ACTION_CANCEL事件(见第101行)并分发给目标子控件target(见第103行),告知事件已被拦截,之后执行第108行将mMotionTarget重置为null,目的是让接下来的ACTION_UP事件直接能给ViewGroup自己处理,最后在第112行返回true表示事件被消耗。
ACTION_MOVE事件用流程图表示如下:
ACTION_UP事件分发过程
ACTION_UP分发到ViewGroup的时候,首先会通过执行mGrouFlags & = ~FALG_DISALLOW_INTERCEPT使得下一个ACTION_DOWN事件来临时disallowIntecept重置为默认的false,之后的处理逻辑和ACTION_MOVE基本一致,这里不再重复
ACTION_UP事件用流程图表示如下: