控件的状态及onClick等都由若干基本的触摸事件以及其他因素决定,控制了触摸事件的处理路径也变相改变了控件的状态。
分发的流程是Activity到ViewGroup,最后一直到叶子节点。
View的dispatchTouchEvent比较简单,主要决定执行自己的onTouchEvent还是安装的OnTouchListener等问题。题外话,onClick是在onTouchEvent中触发的。
dispatchTouchEvent方法在Activity、View、ViewGroup中都有实现,负责消息分发:
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;
if (action == MotionEvent.ACTION_DOWN) {
if (mMotionTarget != null) {
mMotionTarget = null;
}
if (disallowIntercept || !onInterceptTouchEvent(ev)) {
ev.setAction(MotionEvent.ACTION_DOWN);
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)) {
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;
}
}
}
}
}
}
boolean isUpOrCancel = (action == MotionEvent.ACTION_UP) ||
(action == MotionEvent.ACTION_CANCEL);
if (isUpOrCancel) {
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
}
final View target = mMotionTarget;
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);
}
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)) {
}
mMotionTarget = null;
return true;
}
if (isUpOrCancel) {
mMotionTarget = null;
}
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);
}
1. 总体原则
DOWN是动作序列的起始,没有它就没有然后了
默认状态下,一个消息只能有一个消息处理,中间路径只做中转
onInterceptTouchEvent是ViewGroup的方法,用来拦截消息,返回true代表拦截,就不进行后续的消息分发了,false反之
2. ACTION_DOWN消息的分发
mMonitorTarget代表当前ViewGroup处理消息的子控件,初始时其为空,当有子控件处理消息后,mMonitorTarget的值变为子控件。
先经过onInterceptTouchEvent方法,看当前ViewGroup有没有被拦截,如果没有就遍历子节点,看分发给哪个控件,
并调用他们的dispatch方法,如果子节点是ViewGroup,将会按此方法继续分发,
如果是View,根据OnTouchEvent及OnTouchListener处理消息,返回true代表已处理,false反之,
如果点击位置没有子控件或者子控件都不处理消息,代码就执行到38行。
44行,如果没有子控件处理消息,直接交给自己View的dispatchTouchEvent处理。
到这里,ACTION_DOWN的所有分支都完了,52行附近无法达到。
3. 其他消息
9~37行忽略。
38行,如果是up或者cancel消息,先禁止拦截,再在后续会清空mMonitorTarget,代表一个触摸流程的结束。
44行,如果没有子控件要处理事件,分发给自己,交给自己的View的dispatchTouchEvent处理。
52行,如果消息是要子控件处理的,先经过onInterceptTouchEvent看是否需要拦截,如果拦截就直接结束触摸流程并给子控件发流程结束消息
63行,此时不需要拦截,如果是触摸流程结束消息,清空处理消息控件mMonitorTarget,其他与52行类似,只是改成把当前消息分发给子控件。
注意:触摸流程结束消息如cancel和up是必须传递给子控件的,否则流程根本停不下来;down的拦截是不给child发消息,其他消息的拦截是直接发结束消息。
本文详细解析了Android中触摸事件的分发机制,包括Activity、ViewGroup和View之间的消息传递流程,以及ACTION_DOWN消息与其他消息的分发差异。

1621

被折叠的 条评论
为什么被折叠?



