当用户触摸屏幕的时候,最先接受到触摸事件的是Activity的dispatchTouchEvent().
我们就从这里开始分析事件的分发
Activity源码
看下Activity的dispatchTouchEvent()源码。
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
我们可以看到,这里先调用了getWindow().superDispatchTouchEvent(ev)。如果,返回的不是true。在调用,自己的onTouchEvent(),并返回。
我们都知道这个getWindow()其实就是PhoneWindow。然后,我们看看getWindow().superDispatchTouchEvent(ev)都做了什么事。
PhoneWindow.java
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
DecorView.java 继承的FrameLayout
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
所以,最后来到了ViewGroup的dispatchTouchEvent()方法里。
我们进去看看都做了什么。
ViewGroup 源码
dispatchTouchEvent()事件
看之前,我们先了解一些东西。
mFirstTouchTarget,它代表的是消耗事件的view链表。
精简代码
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
//处理开始的DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//把消耗事件的链表mFirstTouchTarget = null
cancelAndClearTouchTargets(ev);
//重置mGroupFlags(影响拦截事件)
resetTouchState();
}
// Check for interception.
//是否拦截
final boolean intercepted;
//本次的事件是DOWN或者有消耗事件的view链表
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//这里可以通过requestDisallowInterceptTouchEvent(来影响它的值)
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//是否禁止父view的拦截
if (!disallowIntercept) {
//默认返回false。如果上面是默认的话,这里就调用了onInterceptTouchEvent()事件
intercepted = onInterceptTouchEvent(ev);
} else {
//如果上面请求了不拦截,直接返回false
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
//如果这个事件不是DOWN也没有需要消耗事件的view,这个view group继续执行拦截操作
intercepted = true;
}
//如果上面的执行了拦截,下面这个方法就不会执行。下面的内容,主要是把事件传递给子view
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//当事件为DOWN的时候,值为0.找新的TouchTarget
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//触摸的坐标。
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// Find a child that can receive the event.
// Scan children from front to back.
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
//遍历子view
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = customOrder
? getChildDrawingOrder(childrenCount, i) : i;
final View child = (preorderedList == null)
? children[childIndex] : preorderedList.get(childIndex);
...
//找到Visible或者有动画并且包含点击坐标的view。不然,下面就不执行了。
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
//找到符合条件的view,并赋值给newTouchTarget
newTouchTarget = getTouchTarget(child);
resetCancelNextUpFlag(child);
//把DOWN事件,分发给子view。
//注:P2106
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//如果子view里面,返回true。
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//就把mFirstTouchTarget赋值给newTouchTarget。
//P2065 给消费事件的子view赋值
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//找到新的消费事件的view,置为true。
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
...
//上面主要对DOWN事件做了处理。有两种情况
//1,找到了消费事件的view。mFirstTouchTarget就不为null了。
//2,没有找到消费事件的view。mFirstTouchTarget依然为null。
// Dispatch to touch targets.
//分发触摸的目标
//注:2090
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
//没有找到消费事件的view,把这个当作一个普通的view来处理。
//第三个参数是null。会调用super.dispatchTouchEvent()。
//会调用onTouchEvent()
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// Dispatch to touch targets, excluding the new touch target if we already
// dispatched to it. Cancel touch targets if necessary.
//这里找到了消费事件的view。分发给消费事件的view。但是,不包括消费了DOWN事件的view
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
//如果已经消费了DOWN事件,直接返回true
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
//把后续的事件,传递给消费事件的子view
//如果拦截的话,cancelChild就为true。子view就会收到cancel事件
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
//如果DOWN事件没有被拦截,后续事件被拦截了。我们需要发送CANCLE事件
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
}
return handled;
}
上面就是viewgroup的dispatchTouchEvent方法。
- 1,首先是如果没有请求拦截的话,就会执行onIntercepterTouchEvent()事件。
- 2,如果viewgroup的子view,包含在DOWN事件的点击坐标时,就会调用P2106 dispatchTransformedTouchEvent()方法。把事件传递下去。
- 3,如果P2106 dispatchTransformedTouchEvent()返回true的话,就会给mFirstTouchTarget赋值。
- 4,走到P2090的事件就会根据mFirstTouchTarget来判断
- 5,如果mFirstTouchTarget=null,就会把自己当作一个普通的view来处理(第三个参数是null)。调用super.dispatchTouchEvent(),调用onTouchEvent()。
- 6,如果mFirstTouchTarget != null。就会把后续事件传递给消费事件的子view。
注:P2106,调用的dispatchTransformedTouchEvent()方法
当没有拦截的时候,会走到这里。分发给子view。从上面的判断知道,子view肯定做点击的坐标内。
否则,直接continue;不执行了。所以child不为null。
并且,这个方法返回true。才会执行里面的代码。给mFirstTouchTarget赋值P2065。
所以,这个方法返回true了,说明,有子view消费了此事件。
//就是判断有没有子ViewGroup/View消耗此事件
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
// Canceling motions is a special case. We don't need to perform any transformations
// or filtering. The important part is the action, not the contents.
final int oldAction = event.getAction();
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
//如果第二个参数为true的话,子view接收到的后续事件就是CANCEL事件。
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
// Perform any necessary transformations and dispatch.
if (child == null) {
//如果没有child的话,就是调用View的dispatchTouchEvent。
//当作普通的view处理。返回false
handled = super.dispatchTouchEvent(transformedEvent);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
transformedEvent.offsetLocation(offsetX, offsetY);
if (! child.hasIdentityMatrix()) {
transformedEvent.transform(child.getInverseMatrix());
}
//1,如果child是view的话
//1-1,比如button就会消耗此时间,返回true
//1-2,比如TextView就不会消耗此事件。返回false
//2,child是ViewGroup的话,就会递归,最终还是判断是否有child或者view的此方法
//注:2375
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
注:P2065,调用的addTouchTarget()方法
执行到这一步,说明是DOWN事件,并且dispatchTransformedTouchEvent()事件返回的是true。
说明child View消费此事件。给mFirstTouchTarget赋值。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
注:P2375
这里要么是view group递归,要么是view。最后,都会到view.dispatchToucheEvent()方法的。
下面看下View 的dispatchTouchEvent()方法。
View
dispatchTouchEvent()方法
public boolean dispatchTouchEvent(MotionEvent event) {
...
boolean result = false;
...
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
if (onFilterTouchEventForSecurity(event)) {
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
//如果设置了OnTouchListener并返回true的话,就会优先消费事件。
//不会执行onTouchEvent事件
result = true;
}
//如果onTouchEvent返回true的话,这个也返回true
if (!result && onTouchEvent(event)) {
result = true;
}
}
...
//默认返回false
return result;
}
onTouch()方法
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == 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仍然会消耗触摸事件,但它不会做出响应。
return (((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE);
}
//如果是clickable/long_clickable等就会返回 true
if (((viewFlags & CLICKABLE) == CLICKABLE ||
(viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE) ||
(viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE) {
...
return true;
}
//默认返回false
return false;
}
到这里,我们就大概分析完了onTouch的事件传递。
我们如果想让一个view,执行我们的onTouch事件,我们需要怎么做呢?
首先,我们需要让mFirstTouchTarget有值。
我们想下它的赋值过程
if (!canceled && !intercepted) {
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
...
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
...
}
}
}
因此,我们在DOWN事件的时候不能拦截。然后,dispatchTransformedTouchEvent事件返回true。
它调用的是view的dispatchTouchEvent()方法,而这个方法默认是返回false的。
如果让它返回true,我们只能让mOnTouchListener.onTouch/onTouchEvent()返回true;
所以,如果我们需要一个view的onTouch事件的话,我们需要让它的onTouchEvent返回true;
最后,做个小实验。
一个ViewGroup嵌套一个View。
需求:当手指左滑的时候,执行view的onTouch。当手指右滑的时候,执行ViewGroup的onTouch。
分析:
首先:如果让ViewGroup和View都可以接收到onTouch事件,我们需要让onDispatchTouchEvent在DOWN的时候,里面的mFirstTouchTarget都不能为空。那么,我们就需要,它们的onTouch都返回true来保证mFirstTouchTarget不为null,能接收到后续事件。
然后:在onMove事件发生的时候,根据条件。让view group的的onInterceptTouchEvent有选择的拦截(就是 return true)。当拦截后,子view就会收到cancel事件。
ViewGroup
public class MyRelativeLayout1 extends RelativeLayout {
LogTouch logTouch = new LogTouch("ViewGroup1");
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
logTouch.dispatchTouchEvent(ev);
return super.dispatchTouchEvent(ev);
}
float x;
@Override
public boolean onInterceptTouchEvent(MotionEvent event) {
logTouch.onInterceptTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_DOWN) {
x = event.getX();
}
if (event.getAction() == MotionEvent.ACTION_MOVE && event.getX() - x > 5) {
return true;
}
return super.onInterceptTouchEvent(event);
}
@Override
public boolean onTouchEvent(MotionEvent event) {
logTouch.onTouchEvent(event);
return true;
}
}
view
public class MyTextView extends TextView {
LogTouch logTouch = new LogTouch("View");
@Override
public boolean onTouchEvent(MotionEvent event) {
logTouch.onTouchEvent(event);
if (event.getAction() == MotionEvent.ACTION_MOVE) {
getParent().requestDisallowInterceptTouchEvent(true);
}
return true;
}
}