Activity事件分发
当在指定页面触发MotionEvent事件时,首先进入Activity的dispatchTouchEvent方法
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
onUserInteraction();
}
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
onUserInteraction是一个空实现,可以由用户自定义需求
Activity分发事件给Window
具体的工作由Activity的Window完成,window的实现类为PhoneWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
PhoneWindow的superDispatchTouchEvent方法将事件传递给mDecor, DecorView一般就是当前界面的底层容器,也就是setContentView所设置的父容器,也就是到达了顶层ViewGroup。
mDecor = (DecorView) preservedWindow.getDecorView();
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
Activity自己消费事件
若Activity中window未消费该事件,事件会回调至Activity的onTouchEvent回调,由自定义的onTouchEvent处理
顶级ViewGroup事件分发
大概流程
点击事件到达顶级ViewGroup,先调用ViewGroup的dispatchTouchEvent方法.
//伪代码逻辑
public boolean dispatchTouchEvent(MotionEvent event){
if(onInterceptTouchEvent(event)){
return onTouchEvent(event);
}else{
return child.dispatchTouchEvent(event);
}
}
自己拦截处理就调用自己的onTouchEvent,此时如果设置了onTouchListener,onTouch方法也会被调用,onTouch会屏蔽onTouchEvent,否则会调用onTouchEvent。
在onTouchEvent中如果设置了onClickListener则,onClick会被调用。
自己不处理就调用当前位置的子ViewGroup或View的dispatch方法。
ViewGroup分发事件详细描述
ViewGropu如何拦截事件
接收到down事件后先重置事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
//当开始一个新的触摸手势时,扔掉所有以前的状态.
cancelAndClearTouchTargets(ev);
resetTouchState();
}
ViewGroup判定自己是否需要拦截
// Check for interception.
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
intercepted = false;
}
} else {
// There are no touch targets and this action is not an initial down
// so this view group continues to intercept touches.
intercepted = true;
}
- 如果DOWN事件被当前ViewGroup拦截成功,mFirstTouchTarget != null就不成立了,比如接下来的MOVE和UP事件到来后会直接拦截
- 如果DOWN事件被当前ViewGroup拦截成功, 后续的事件会默认都交给ViewGroup处理,并且不需要判定拦截方法了
ViewGroup不拦截事件的处理方式
if (!canceled && !intercepted) {
// If the event is targeting accessibility focus we give it to the
// view that has accessibility focus and if it does not handle it
// we clear the flag and dispatch the event to all children as usual.
// We are looking up the accessibility focused host to avoid keeping
// state since these events are very rare.
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex(); // always 0 for down
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
// Clean up earlier touch targets for this pointer id in case they
// have become out of sync.
removePointersFromTouchTargets(idBitsToAssign);
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 = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
// If there is a view that has accessibility focus we want it
// to get the event first and if not handled we will perform a
// normal dispatch. We may do a double iteration but this is
// safer given the timeframe.
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!child.canReceivePointerEvents()
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// Child is already receiving touch within its bounds.
// Give it the new pointer in addition to the ones it is handling.
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// Child wants to receive touch within its bounds.
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
// childIndex points into presorted list, find original index
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
// The accessibility focus didn't handle the event, so clear
// the flag and do a normal dispatch to all children.
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
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;
}
}
}
当事件健康并且未被拦截,首先遍历子View根据坐标查询到该事件的触发点处于哪个子View的位置上
// Dispatch to touch targets.
if (mFirstTouchTarget == null) {
// No touch targets so treat this as an ordinary view.
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.
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
final boolean cancelChild = resetCancelNextUpFlag(target.child)
|| intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
if (cancelChild) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
查询到指定位置的View后mFirstTouchTarget,如果为空,说明该位置不存在子View,仍需要当前的ViewGroup处理事件;如果不为空,通过dispatchTransformedTouchEvent将事件传递至子View,从而完成ViewGroup对子View的事件分发。
View分发事件详细描述
View的dispatch方法比较简单,View是一个单独的元素,不需要将事件向下传递,只能自己处理事件。
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)) {
if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
result = true;
}
//noinspection SimplifiableIfStatement
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
if (!result && onTouchEvent(event)) {
result = true;
}
}
return result;
}
首先判定是否设置了mOnTouchListener回调,如果设置并且在自定义的onTouch方法中返回true,那么onTouchEvent就不会再被调用了。onTouchEvent中设定的所有回调事件均不再触发,比如onClickListener。下面来看View的onTouchEvent实现。
- step1: View在不可点击状态下的事件处理,当不可点击时也会消费当前事件。
final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
|| (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
|| (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
if ((viewFlags & ENABLED_MASK) == DISABLED) {
if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
// A disabled view that is clickable still consumes the touch
// events, it just doesn't respond to them.
return clickable;
}
- step2:如果View设置了代理,会执行mTouchDelegate.onTouchEvent方法,该方法的实现为判定当前事件触发位置是否在代理视图中,如果在,就分发至代理视图消费。
- step3:响应MotionEvent