当你点击屏幕的时候最开始得到的事件是最外层的ViewRoot–>ViewGroup….这样依次传递过来的,在这里的ViewRoot就直接忽视了,那就来看看ViewGroup,我们知道布局文件的最外层都是使用五大布局包裹着的,而五大布局又是ViewGroup的子类,因此最开始拿到事件的就是ViewGroup(我们这里直接忽视了ViewRoot),ViewGroup得到事件之后,触发的第一个方法就是dospatchTouchEvent(ev)分发事件,那么在该方法中都干了些什么呢?我们来到源码:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
...省略
//记录当前点击的触摸事件的坐标,同时得到一个矩形用于搜索锁定触摸事件的坐标的范围
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;
//是否是触发的DOWN事件
if (action == MotionEvent.ACTION_DOWN) {
...省略
// 没有进行任何的事件拦截处理
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
// 再次确定是DOWN事件(只为了让自己放心)
ev.setAction(MotionEvent.ACTION_DOWN);
...省略
}
}
...省略
}
不容置疑的是点击事件一开始绝壁是ACTION_DOWN事件,当然Google工程师还是在触发的第一个方法(dispatchTouchEvent())中进行了两次判断该点击事件是否是ACTION_DOWN,因此一旦事件的发生就会执行dispatchTouchEvent()方法,那么在该方法中第一件事就是记录当前点击的触摸事件的坐标,同时得到一个矩形用于搜索锁定触摸事件的坐标的范围,然后再进行是否是DOWN事件判断。
//是否是触发的DOWN事件
if (action == MotionEvent.ACTION_DOWN) {
...省略
// 没有进行任何的事件拦截处理
if (disallowIntercept || !onInterceptTouchEvent(ev)) {//调用onInterceptTouchEvent()方法
// 再次确定是DOWN事件(只为了让自己放心)
ev.setAction(MotionEvent.ACTION_DOWN);
...省略
}
}
如果是ACTION_DOWN事件,那么在当前的dispatchTouchEvent()方法中去调用onInterceptTouchEvent(ev)中断事件方法。
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}
如果onInterceptTouchEvent(ev)方法返回的是false(默认返回false),那么就说明在当前ViewGroup类中不进行中断事件,那么当前类的onTouchEvent()处理事件方法就不会去执行(而是交给子View去解决)。
onInterceptTouchEvent(ev)方法返回的是false(交给子View去解决,那么交给子View解决就得得到所有的子View):if条件满足,继续执行下面的代码,把当前点击的触摸事件的坐标记录下来,并得到该ViewGroup类中所有的子View,存储到数组中,并得到子View的总个数。
//我们知道我们想要分派事件,找到可以处理事件的孩子
for (int i = count - 1; i >= 0; i--) {
final View child = children[i];
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;
if (child.dispatchTouchEvent(ev)) {
// 事件处理,现在我们有了目标
mMotionTarget = child;
return true;
}
}
}
}
循环遍历存储当前ViewGroup中所有的子View数组,循环通过变量i的值得到对应角标的子View,首先判断该子View能否被点击。
如果子View能被点击,那就说明它有可能被点击了,如果连点击都不能点击,那么DOWN事件就绝壁不会发生。因此既然有可能被点击了,那么就得通过子View得到矩形(搜索锁定触摸事件的坐标的范围),如果该矩形有包含当前点击的触摸事件的坐标,那么就再去判断一下,子View调用dispatcheTouchEvent(ev)方法,这里要特别注意:如果子View还是ViewGroup类的子View,那么子View调用的dispatcheTouchEvent(ev)方法就是本dispatcheTouchEvent(ev)方法,那么执行的过程也是一样的。这样一次一次的调用dispatcheTouchEvent(ev)方法,其实这样的设计思路很简单只要你是ViewGroup类或者其子类,那么我就一直寻找其子View的子View甚至是子View的子View,就这样一直找到最后一个子View并且该子View是View,而不是ViewGroup,是View就不会调用ViewGroup的dispatcheTouchEvent(ev)方法,而是去调用View中的dispatcheTouchEvent(ev),而在View中的dispatcheTouchEvent(ev)方法中又进行了是否设置了onTouchListener对象,OnTouchListener.onTouch(this, event)返回的是false or true之类的判断。
//View类的dispatchTouchEvent(ev):
public boolean dispatchTouchEvent(MotionEvent event) {
if (!onFilterTouchEventForSecurity(event)) {
return false;
}
if (mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && mOnTouchListener.onTouch(this, event)) {
return true;
}
return onTouchEvent(event);
}
如果为true,说明这个子子…View要了这个事件,那么View中的dispatcheTouchEvent(ev)方法也就整体返回true,这样我们又来到了ViewGroup中的dispatcheTouchEvent(ev)方法中的子View调用dispatcheTouchEvent(ev)得到的值也就为true,这样的话就继续执行下面的代码,把这个子子…View赋为事件目标,这样所有的事件都由子子…View来处理。
if (child.dispatchTouchEvent(ev)) {
// 事件处理,现在我们有了目标
mMotionTarget = child;
return true;
}
如果OnTouchListener.onTouch(this, event)返回的是false,我也不怕,因为我还得执行onTouchEvent(event)方法,如果子子…View中的onTouchEvent(event)方法返回true,这也说明这个事件我子子…View要了,那么View中的dispatcheTouchEvent(ev)方法也就整体返回true,这样我们又来到了ViewGroup中的dispatcheTouchEvent(ev)方法中的子View调用dispatcheTouchEvent(ev)得到的值也就为true,这样的话就继续执行下面的代码,把这个子子…View赋为事件目标,这样所有的事件都由子子…View来处理。
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);
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;
// 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;
}
...省略
return target.dispatchTouchEvent(ev);//所有的事件交给子View来处理
如果View中的dispatcheTouchEvent()方法中的代码执行完之后都返回false,就说明子子…View不要这个事件,那么这个事件又返回给了其父View,这样我们又来到了ViewGroup中的dispatcheTouchEvent(ev)方法中的子View调用dispatcheTouchEvent(ev)得到的值也就为false,这样的话事件目标没有赋值,为空。
那么,就又来到了ViewGroup类中,既然事件目标(mMotionTarget)没有赋值,为空了,那么赋值给target也就为null,既然target为空,也就满足了if(target == null)的if语句,执行其中的方法,最终调用了父类的dispatchTouchEvent(ev),由其父View来处理。
if (target == null) {
//我们没有目标(孩子不处理这些事件),这意味着我们自己来处理
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev); //所有的事件交给父View来处理
}
如果,在onInterceptTouchEvent()一上来就返回true(父View中进行了该方法的覆写),意味着父View进行了事件的拦截,其身要了这些事件了,那么,代码会直接来到这:
if (target == null) {
//我们没有目标(孩子不处理这些事件),这意味着我们自己来处理
ev.setLocation(xf, yf);
if ((mPrivateFlags & CANCEL_NEXT_UP_EVENT) != 0) {
ev.setAction(MotionEvent.ACTION_CANCEL);
mPrivateFlags &= ~CANCEL_NEXT_UP_EVENT;
}
return super.dispatchTouchEvent(ev); //所有的事件交给父View来处理
}
另外,附上两张图,将上面的理论知识进行了图片的展示,以便大家更好的掌握事件处理!
图一:
图二:
到此为止,事件的处理2.3源码分析我就讲的这,谢谢您能浏览到最后,如果您觉得本人写的不错,请给我一个好评,谢谢!