对于一颗View树来说,它的消息传递应该是自上而下的,从根节点开始逐层往下递归传递;在消息流转过程中一旦有人处理了这个消息,那么传递即可宣告终止,从这一点来看,View树的上层具有消息处理的优先权。从理论角度来讲,因为我们并不能预先知道树的节点是View还是ViewGroup,因而递归过程中所规定的接口必定是由View提供的,然后由ViewGroup来重载#####
1.View中touchEvent的投递流程
1.1 View的dispatchTouchEvent负责事件的分发
View对象可以通过setOnTouchListener来设置一个Event监听,当有事件来临时候,onTouch会被调用;
如果没有设置TouchListener,或者flags中指明被disable了,又或者onTouch返回false,那么系统把Event传递给onTouchEvent。##
对比:onTouch简洁高效,onTouchEvent更适用于View扩展类的情况。
public boolean dispatchTouchEvent(MotionEvent event) {
if (onFilterTouchEventForSecurity(event)) {
ListenerInfo li = mListenerInfo;
//onTouch
if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
return true;
}
//onTouchEvent
if (onTouchEvent(event)) { //
return true;
}
}
return false;
}
1.2 onTouchEvent
1.2.1 disable的情况
public boolean onTouchEvent(MotionEvent event) {
final int viewFlags = mViewFlags;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
//view在被disable的情况下同样会消耗这个事件只是不作出任何响应
return (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
}
1.2.2 ACTION_DOWN
case MotionEvent.ACTION_DOWN:
mHasPerformedLongPress = false;
if (performButtonActionOnTouchDown(event)) {
break;
}
// Walk up the hierarchy to determine if we're inside a scrolling container.
boolean isInScrollingContainer = isInScrollingContainer();
// For views inside a scrolling container, delay the pressed feedback for
// a short period in case this is a scroll.
if (isInScrollingContainer) {
mPrivateFlags |= PFLAG_PREPRESSED;
if (mPendingCheckForTap == null) {
mPendingCheckForTap = new CheckForTap();
}
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true);//当前状态为pressed(pressed,normal)
checkForLongClick(0);//检查是否被长按(通过ViewConfiguration.getLongPressTimeout来获取长按事件的产生标准)
}
break;
1.2.3 ACTION_MOVE
case MotionEvent.ACTION_MOVE:
final int x = (int) event.getX();
final int y = (int) event.getY();
if (!pointInView(x, y, mTouchSlop)) {//是否已经超出了View范围
removeTapCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
// Remove any future long press/tap checks
removeLongPressCallback();
//不再是pressed状态
setPressed(false);
}
}
break;
1.2.4 ACTION_UP
case MotionEvent.ACTION_UP:
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
if (!mHasPerformedLongPress) {//已经执行长按
// This is a tap, so remove the longpress check
removeLongPressCallback();
if (!focusTaken) {
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClick();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
mUnsetPressedState.run();
}
removeTapCallback();
}
break;
1.2.5 ACTION_CANCEL
case MotionEvent.ACTION_CANCEL:
setPressed(false);
removeTapCallback();
removeLongPressCallback();
break;
2.ViewGroup中touchevent的投递流程
ViewGroup因为涉及子对象的处理,所以派发流程没有View那么直接简单,它重载了dispatchTouchEvent,对View提供的派发机制进行了重新规划。
拦截的概念,从深圳寄送快递到上海,中途会经过若干城市,那么当某个城市收到快递时,它首先判断要不要把快递拦截下来,也就是这个快递是不是要由这个城市进行处理,如果当前就是上海,那么快递就不应该继续向下传输,而是进入区->街道->门牌号等的内政处理环节,如果当前城市不是上海,而是其他中间城市,则收到快递时要继续往下一个城市运输,否则就会出现错误传递的情况。
public boolean dispatchTouchEvent(MotionEvent ev) {
boolean handled = false;//event是否被处理
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
//判断是否是down事件
final int actionMasked = action & MotionEvent.ACTION_MASK;
// Handle an initial down.
if (actionMasked == MotionEvent.ACTION_DOWN) {
//down事件是后续事件的起点,
cancelAndClearTouchTargets(ev);
resetTouchState();//一旦收到down事件,先清除以前的所有状态
}
//检查interception的情况
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//如果是down事件或者mFirstTouchTarget不为空
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {//ViewGroup允许拦截
//android系统鼓励开发者在继承viewgroup的时候覆盖整个方法,而不是覆盖dispatchtouchEvent
//
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {//ViewGroup不允许拦截
intercepted = false;//不需要拦截
}
} else {//如果不是down事件,而且之前的判断中mFirstTouchTarget为空,则ViewGroup继续拦截
//继续拦截,viewgroup拦截该事件在内部处理这个时候意味这个该事件以及后续到达的Action_UP都会直接投递到ViewGroup的onTouchEvent中而不会继续传递
intercepted = true;
}
// Check for cancelation.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
// Update list of touch targets for pointer down, if needed.
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {//不拦截的情况
//不拦截表示ViewGroup不希望拦截这一条消息,因而它的子对象将会有机会来处理它
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//子对象的个数
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {//子对象数量不为零
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
final View[] children = mChildren;
final boolean customOrder = isChildrenDrawingOrderEnabled();
for (int i = childrenCount - 1; i >= 0; i--) {
//循环查找一个能处理此事件的child
final int childIndex = customOrder ?
getChildDrawingOrder(childrenCount, i) : i;
final View child = children[childIndex];
//canViewReceivePointerEvents该函数表示这个child能否接收pointerevent(包括touchevent)
//isTransformedTouchPointInView该函数表示x,y这个点是否落在child的管辖范围内
//也就是说如果这个child既要能接收事件而且touch的点还要在view的范围内才行,不满足的直接跳过
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;//不符合要求跳过
}
//找到了此touchEvent归属的child
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent这个函数会调用到child的dispatchTouchEvent
//如果child是view,执行view的dispatchTouchEvent
//如果child是viewgroup,执行viewgroup的dispatchTouchEvent,
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//完成了任务,结束循环
break;
}
}
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
// Did not find a child to receive the event.
// Assign the pointer to the least recently added target.
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
return handled;//返回处理结果
}