接着上次Android View事件分发本篇来分析ViewGroup的事件分发(源码基于Android3.0)
ViewGroup事件分发:
在开始之前我们首先要明确一点,Android无论是测量、布局、还是绘制都是从大到小即从父布局到子 view逐层进行,当然事件分发也不例外,所以每次触摸屏幕触发事件必定是先触发父布局的事件,再一层层的到对应的子 view,明白了这一点再结合上篇提到的每次触摸屏幕必定触发view的dispatchTouchEvent方法的结论,我们相应的找到ViewGroup中的该方法,而ViewGroup的事件分发正是从该方法开始:
/**
* {@inheritDoc}
*/
@Override
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;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
// this is weird, we got a pen down, but we thought it was
// already down!
// XXX: 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
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--) {
final View child = children[i];
if ((child.mViewFlags & VISIBILITY_MASK) == VISIBLE
|| child.getAnimation() != null) {
child.getHitRect(frame);
if (frame.contains(scrolledXInt, scrolledYInt)) {
// offset the event to the view's coordinate system
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)) {
// Event handled, we have a target now.
mMotionTarget = child;
return 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) {
// Note, we've already copied the previous state to our local
// variable, so this takes effect on the next event
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) {
// 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;
}
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);
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;
}
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);
}
相对于view的dispatchTouchEvent方法ViewGroup中的该方法长了许多,我们挑重点来看一下。在判断了action为down之后出现一个if判断disallowIntercept || !onInterceptTouchEvent(ev),通过注释可以看出是禁用拦截或者没有禁用但是不需要拦截该事件就会进入if判断内部,在判断内部对viewGroup的所有子view进行遍历,判断当前点击区域是否在某个子 view中(即拿到点击的子 view),然后通过if (child.dispatchTouchEvent(ev)) 来调用的子 view的dispatchTouchEvent()方法,如果子 view的dispatchTouchEvent()方法返回true就会把该子 view赋给mMotionTarget并且ViewGroup的dispatchTouchEvent方法返回true,即子 view处理了事件并返回true,父view就不再处理该事件了。
而如果遍历之后没有得到点击的子 view即mMotionTarget没有得到赋值,或者说子 view的divpatchTouchEvent返回了false,就会继续向下执行,我们看到如果mMotionTarget为null之后会调用return super.dispatchTouchEvent(ev),而ViewGroup继承自View所以这里还是调用的View的dispatchTouchEvent方法处理了事件。
另外这里对disallowIntercept || !onInterceptTouchEvent(ev)该判断的两个参考值做个说明:disallowIntercept是指是否禁用掉事件拦截的功能,默认是false,也可以通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改。!onInterceptTouchEvent(ev)就是对onInterceptTouchEvent方法的返回值取反!也就是说如果我们在onInterceptTouchEvent方法中返回false,就会让第二个值为true,从而进入到条件判断的内部,如果我们在onInterceptTouchEvent方法中返回true,就会让第二个值为false,从而跳出了这个条件判断。
至此对于ViewGroup的事件分发就完了,我们可以清楚的知道以下几点结论:
- 1.Android事件分发是从父到子进行
- 2.在viewgroup中可以通过onInterceptTouchEvent方法对事件进行拦截,该方法返回true表示事件由自己处理不会向子 view传递,如果返回false,事件会传向子 view,默认返回false。
- 3.如果事件由子 view处理了,父view将无法接收到任何事件。
view group的事件分发调用图:
参考资料:
https://blog.youkuaiyun.com/guolin_blog/article/details/9153747