Android 事件传递
看了很多的博客都没有涉及到源码的层面来讲解事件传递,看完都感觉有点理解又有点疑惑,所以自己整理一篇。
我是从Activity的dispatchTouchEvent方法入手的,至于Activity的dispatchTouchEvent方法是怎么被调用的,具体是在WindowManagerService中,有兴趣的可以自己去了解。
代码流程:
一般我们点击屏幕之后,WindowManagerService会获取到Touch事件,并将该Touch事件派发给Activity,也就是会调用Activity的dispatchTouchEvent方法。具体的可以看代码。
代码都是从Android 5.1.1中截出来的
Activity的dispatchTouchEvent方法:
/**
* Called to process touch screen events. You can override this to
* intercept all touch screen events before they are dispatched to the
* window. Be sure to call this implementation for touch screen events
* that should be handled normally.
*
* @param ev The touch screen event.
*
* @return boolean Return true if this event was consumed.
*/
public boolean dispatchTouchEvent(MotionEvent ev) {
if (ev.getAction() == MotionEvent.ACTION_DOWN) {
//onUserInteraction是一个空函数,什么都没做
onUserInteraction();
}
//getWindow获取到的是PhoneWindow类
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
//如果superDispatchTouchEvent返回false,则调用Activity的onTouchEvent方法。
return onTouchEvent(ev);
}
从getWindow().superDispatchTouchEvent(ev)这里可以看到,Activity又将事件派发给PhoneWindow来处理。
如果superDispatchTouchEvent返回false,则会调用Activity的OnTouchEvent方法。
我们接着看一下PhoneWindow的代码。
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
这个方法很简单,又将事件传给mDecor来处理,mDecor是一个DecorView对象,DecorView又继承了FrameLayout。
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public boolean superDispatchTouchEvent(MotionEvent event) {
//这里又调用了父类的dispatchTouchEvent,
//也就是FrameLayout的dispatchTouchEvent方法。
return super.dispatchTouchEvent(event);
}
}
FrameLayout没有重写dispatchTouchEvent方法,因此调用的是ViewGroup的dispatchTouchEvent方法。该方法代码比较多,慢慢分析。需要注意TouchTarget,TouchTarget主要是用来记录事件接收情况,包括接收该事件的子view、事件详情等。dispatchTouchEvent就是根据TouchTarget来决定派发事件到哪些子View中。
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
......
//最后会返回这个handled
boolean handled = false;
//应用安全策略来过滤一部分事件,比如View被遮挡了就会被过滤
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
// 处理初始的Down事件,只要收到Down事件,就认为是一个新的Touch事件
if (actionMasked == MotionEvent.ACTION_DOWN) {
cancelAndClearTouchTargets(ev);
resetTouchState();
}
//检查是否拦截,默认false
//mFirstTouchTarget 是TouchTarget链表的头节点。
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果允许拦截
if (!disallowIntercept) {
//这里调用了onInterceptTouchEvent方法,
//该方法默认返回false,即不进行拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action);
} else {
intercepted = false;
}
} else {
intercepted = true;
}
// 检查cancel事件.
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
TouchTarget newTouchTarget = null;
boolean alreadyDispatchedToNewTouchTarget = false;
if (!canceled && !intercepted) {//判断是否取消或者被拦截
View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
? findChildWithAccessibilityFocus() : null;
//只有DOWN事件(包括多点触控)才会走这个流程,
//这个流程主要是找到能接受该MotionEvent的TouchTarget
//后续的其他事件则直接跳过该流程
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;
//Removes the pointer ids from consideration.
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
//由前往后遍历所有的子View,
//寻找一个能接受该事件的子view,并生成对应的TouchTarget
final ArrayList<View> preorderedList = buildOrderedChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
final View[] children = mChildren;
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);
// 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;
}
//判断该view是否能接受事件且事件坐标不超过view范围
if(!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
continue;
}
//获取child对应的TouchTarget,
//如果child还没有对应的TouchTarget,则返回null
newTouchTarget = getTouchTarget(child);
// 若子View已经在TouchTarget链表中
if (newTouchTarget != null) {
// 将事件id加入到touchTarget中
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent这个方法会把事件传递给子View,
//方法内部会调用子View的dispatchTouchEvent方法。
//这个方法后面会讲到。
//并返回子View的dispatchTouchEvent的返回值。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//为true代表子View接收了该事件
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();
//重新生成一个TouchTarget,包含了子view
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//标记为已经将事件分发,避免后面重新分发Down事件。
alreadyDispatchedToNewTouchTarget = true;
//已经找到TouchTarget了,结束遍历
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();
}
// 没有发现接收事件的子View
if (newTouchTarget == null && mFirstTouchTarget != null) {
// 把事件赋给最早的TouchTarget
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
//将事件派发给TouchTarget
if (mFirstTouchTarget == null) {
//没有TouchTarget,所以派发给自己
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
// 派发事件给TouchTarget
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
//遍历TouchTarget链表
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;
}
}
//如果是UP或者Cancel事件,则清空TouchTarget链表
if (canceled || actionMasked==MotionEvent.ACTION_UP
|| actionMasked==MotionEvent.ACTION_HOVER_MOVE){
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
从上面的代码可以看到,对事件的派发工作都是在dispatchTransformedTouchEvent方法中。
dispatchTransformedTouchEvent会将事件的坐标转换为特定子view中的坐标,并调用子view的dispatchTouchEvent方法,最后返回dispatchTouchEvent的返回值。
/**
* Transforms a motion event into the coordinate space of a particular child view,
* filters out irrelevant pointer ids, and overrides its action if necessary.
* If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
*/
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
//最后会返回该值
final boolean handled;
final int oldAction = event.getAction();
//取消事件不需要转换坐标或者过滤,因为取消事件不需要对坐标进行处理。
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
//派发cancel事件
if (child == null) {
//注意是调用super的dispatchTouchEvent方法
//即View的dispatchTouchEvent方法
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
final int oldPointerIdBits = event.getPointerIdBits();
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
//注意是调用super的dispatchTouchEvent方法
handled = super.dispatchTouchEvent(event);
} else {
//坐标转换
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
//分发给子View
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
if (child == null) {
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());
}
//派发给子view
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
到这里就把事件从Activity分发到View上中。
接下来看看View的dispatchTouchEvent方法。
/**
* Pass the touch screen motion event down to the target view, or this
* view if it is the target.
*
* @param event The motion event to be dispatched.
* @return True if the event was handled by the view, false otherwise.
*/
public boolean dispatchTouchEvent(MotionEvent event) {
//最后会返回这个值
boolean result = false;
......
final int actionMasked = event.getActionMasked();
if (actionMasked == MotionEvent.ACTION_DOWN) {
// Defensive cleanup for new gesture
stopNestedScroll();
}
//应用安全策略来过滤一部分事件,比如View被遮挡了就会被过滤
if (onFilterTouchEventForSecurity(event)) {
//ListenerInfo类含有View的很多监听事件,包括OnTouchListener、OnClickListener等。
ListenerInfo li = mListenerInfo;
//这里执行li.mOnTouchListener.onTouch方法,也就是执行我们通过setOnTouchListener的监听事件。
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//这里要注意,如果我们设置的TouchListener返回false,则还会执行View自身的OnTouchEvent事件。
if (!result && onTouchEvent(event)) {
result = true;
}
}
if (!result && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
}
// Clean up after nested scrolls if this is the end of a gesture;
// also cancel it if we tried an ACTION_DOWN but we didn't want the rest
// of the gesture.
if (actionMasked == MotionEvent.ACTION_UP ||
actionMasked == MotionEvent.ACTION_CANCEL ||
(actionMasked == MotionEvent.ACTION_DOWN && !result)) {
//停止滚动
stopNestedScroll();
}
return result;
}
TouchTarget
TouchTarget的定义和相关方法
/* Describes a touched view and the ids of the pointers that it has captured.
*
* This code assumes that pointer ids are always in the range 0..31 such that
* it can use a bitfield to track which pointer ids are present.
* As it happens, the lower layers of the input dispatch pipeline also use the
* same trick so the assumption should be safe here...
*/
private static final class TouchTarget {
private static final int MAX_RECYCLED = 32;
private static final Object sRecycleLock = new Object[0];
private static TouchTarget sRecycleBin;
private static int sRecycledCount;
public static final int ALL_POINTER_IDS = -1; // all ones
// The touched child view.
public View child;
// The combined bit mask of pointer ids for all pointers captured by the target.
public int pointerIdBits;
// The next target in the target list.
public TouchTarget next;
private TouchTarget() {
}
public static TouchTarget obtain(View child, int pointerIdBits) {
final TouchTarget target;
synchronized (sRecycleLock) {
if (sRecycleBin == null) {
target = new TouchTarget();
} else {
target = sRecycleBin;
sRecycleBin = target.next;
sRecycledCount--;
target.next = null;
}
}
target.child = child;
target.pointerIdBits = pointerIdBits;
return target;
}
public void recycle() {
synchronized (sRecycleLock) {
if (sRecycledCount < MAX_RECYCLED) {
next = sRecycleBin;
sRecycleBin = this;
sRecycledCount += 1;
} else {
next = null;
}
child = null;
}
}
}
/**
* Resets all touch state in preparation for a new cycle.
*/
private void resetTouchState() {
clearTouchTargets();
resetCancelNextUpFlag(this);
mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
mNestedScrollAxes = SCROLL_AXIS_NONE;
}
/**
* Resets the cancel next up flag. Returns true if the flag was previously set.
*/
private static boolean resetCancelNextUpFlag(View view) {
if ((view.mPrivateFlags & PFLAG_CANCEL_NEXT_UP_EVENT) != 0) {
view.mPrivateFlags &= ~PFLAG_CANCEL_NEXT_UP_EVENT;
return true;
}
return false;
}
/**
* Clears all touch targets.
*/
private void clearTouchTargets() {
TouchTarget target = mFirstTouchTarget;
if (target != null) {
do {
TouchTarget next = target.next;
target.recycle();
target = next;
} while (target != null);
mFirstTouchTarget = null;
}
}
/**
* Cancels and clears all touch targets.
*/
private void cancelAndClearTouchTargets(MotionEvent event) {
if (mFirstTouchTarget != null) {
boolean syntheticEvent = false;
if (event == null) {
final long now = SystemClock.uptimeMillis();
event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
syntheticEvent = true;
}
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
resetCancelNextUpFlag(target.child);
dispatchTransformedTouchEvent(event, true, target.child, target.pointerIdBits);
}
clearTouchTargets();
if (syntheticEvent) {
event.recycle();
}
}
}
/**
* Gets the touch target for specified child view. Returns null if not found.
*/
private TouchTarget getTouchTarget(View child) {
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
/**
* Adds a touch target for specified child to the beginning of the list.
* Assumes the target child is not already present.
*/
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
/**
* Removes the pointer ids from consideration.
*/
private void removePointersFromTouchTargets(int pointerIdBits) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if ((target.pointerIdBits & pointerIdBits) != 0) {
target.pointerIdBits &= ~pointerIdBits;
if (target.pointerIdBits == 0) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
target = next;
continue;
}
}
predecessor = target;
target = next;
}
}
private void cancelTouchTarget(View view) {
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (target.child == view) {
if (predecessor == null) {
mFirstTouchTarget = next;
} else {
predecessor.next = next;
}
target.recycle();
final long now = SystemClock.uptimeMillis();
MotionEvent event = MotionEvent.obtain(now, now,
MotionEvent.ACTION_CANCEL, 0.0f, 0.0f, 0);
event.setSource(InputDevice.SOURCE_TOUCHSCREEN);
view.dispatchTouchEvent(event);
event.recycle();
return;
}
predecessor = target;
target = next;
}
}
总结:
Activity的传递
- WindowManagerService将事件传给Activity。
- Activity调用dispatchTouchEvent()方法分发事件。
- 如果是Down事件,会调用onUserInteraction方法。
- 通过PhoneWindow把事件分发给Activity最底层的ViewGroup。
- 如果最底层的ViewGroup返回false,则调用Activity的onTouchEvent。
ViewGroup的分发过程
- Activity将事件传给最底层的ViewGroup,调用ViewGroup的dispatchTouchEvent()方法。
- 如果接受到Down事件,则代表是一个新的Touch事件,会进行初始化,并清除之前的所有状态。
- ViewGroup调用onInterceptTouchEvent判断是否拦截事件分发。
- 如果没有被拦截,且为Down事件,则遍历所有的子View。
- 如果子view不能接受事件,或者事件的坐标不在子View的范围内,则跳过。
- 如果子View已经有对应TouchTarget对象,则由那个TouchTarget分发事件。
- 调用dispatchTransformedTouchEvent将事件传给子View。
- 返回true,表示该View接受了该事件,会生成对应的TouchTarget,并加入到TouchTarget链表中。
- 返回false,表示该View不接受该事件。
- 根据TouchTarget链表分发事件。
- TouchTarget链表为空,则调用super.dispatchTouchEvent(),将事件分发给自己。
- 否则遍历TouchTarget链表,并分别调用dispatchTransformedTouchEvent进行分发事件。
- 如果child的参数是空的,则代表是调用给自身的dispatchTouchEvent()。
- 如果child的参数不为空,则调用child的dispatchTouchEvent()。
View
- ViewGroup通过调用View的dispatchTouchEvent方法将事件分发给View。
- 如果View设置了OnTouchListener,则调用OnTouchListener的onTouch方法。
- onTouch返回true,则dispatchTouchEvent返回true。
- View没有设置OnTouchListener事件或者onTouch返回false,则调用View的onTouchEvent方法,如果onTouchEvent方法返回true,dispatchTouchEvent也会返回true。
- dispatchTouchEvent返回true,则表示该事件已经被接受。