ViewGroup事件分发
ViewGroup点击事件流程
ViewGroup:dispatchTouchEvent()
->ViewGroup:onInterceptTouchEvent()
- > View:dispatchTouchEvent()
- > View:onTouchEvent()
- >ViewGroup:onTouchEvent()
ViewGroup:dispatchTouchEvent()源码
- 因为代码较多,分段思考。首先看ACTION_DOWN的代码
//是否超屏幕
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) {
//---------------关键点1
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;
}
//------------关键点2
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;
//关键点3
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.
}
}
}
}
}
在关键点1中我们能看到有一个字段mMotionTarget
,先判断其是否为null, 如果不为null,则将其置为空。该字段主要目的是保存消费触摸事件的View的。
在关键点2中,对disallowIntercept
和!onInterceptTouchEvent(ev)
进行了判段,如果两者有一个为true就可以向下分发。disallowIntercept
的默认为false,可以通过在子View的dispatchTouchEvent()
中调用getParent().requestDisallowInterceptTouchEvent(true)
方法修改其值。onInterceptTouchEvent()
方法在当前类中进行实现,返回false表示不拦截,返回true表示拦截。
在关键点3中,首先在之前判断了该View是否包含触摸点,其次if中判断childView的dispatchTouchEvent()
是否返回true,如果返回true,表示childView捕获了本次触摸,则将childView 赋值给mMotionTarget
,同时结束ViewGroup的disaptchTouchEvent()
方法,并返回true。
- ACTION_MOVE的代码
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;
boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//...ACTION_DOWN
//...ACTIN_UP or ACTION_CANCEL
// The event wasn't an ACTION_DOWN, dispatch it to our target if
// we have one.
//-------关键点1
final View target = mMotionTarget;
// if have a target, see if we're allowed to and want to intercept its
// events
if (!disallowIntercept && onInterceptTouchEvent(ev)) {
//....
}
// 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);
return target.dispatchTouchEvent(ev);
}
在关键点1中,直接将在ACTION_DOWN中保存的mMotionTarget
赋值给target,并return target.dispatchTouchEvent(ev);
。由此可见,之所以在childView中当ACTION_DOWN没有捕获,就无法捕获后续的ACTION_MOVE和ACTION_UP触摸,是因为此时在ViewGroup中直接将事件交给了在ACTION_DOWN中保存的处理触摸的View对象。
- ACTION_UP的代码
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) {...}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
if(target ==null ){...}
if (!disallowIntercept && onInterceptTouchEvent(ev)) {...}
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);
return target.dispatchTouchEvent(ev);
}
在ACTION_UP实现没有太大的难点,清楚一下字段属性,调用之前保存的childView对象的分发方法。
- 当没有childView来处理触摸时
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);
}
当没有触摸时,会调用父类的dispatchTouchEvent(ev)
,因为ViewGroup继承自View,那么一切就很简单了。就会调用ViewGroup本身的onTouchEvent()
。
总结
- 在ACTION_DOWN中,如果ViewGroup中有能处理该次触摸的childView,则将会调用childView的
dispatchTouchEvent
,同时保存childView对象。当本次触摸的ACTION_MOVE和ACTION_UP事件,也将有此次childView来处理。 - 当ViewGroup中没有能够处理此次触摸的childView,则会调用其父类View的
dispatchTouchEvent()
方法,那么就类似childView的调用了。