事件分发源码解析
前言
对于Android的事件分发,很早之前是看的图解 Android 事件分发机制,其中的事件分发的U型图很好的解释了分发的过程。理解起来就类似领导给下属分发任务,任务从上到下分发,下属能完成就处理掉任务,不能处理然后返回给领导,让领导处理。
对于事件传递主要涉及3个方法,Android底层监听到触摸产生事件,事件一路传递到Activity,这一过程参考Android触摸事件全过程分析:由产生到Activity.dispatchTouchEvent(),最终activity接受到事件后调用其dispatchTouchEvent方法开始事件分发
dispatchTouchEvent
此方法三个层级都有,在View 层级中主要是处理事件,在其他两个层级主要是事件分发onInterceptTouchEvent
此方法只有在ViewGroup中存在,用于事件拦截,拦截后的事件交给ViewGroup的onTouchEventonTouchEvent
此方法主要用于事件处理,ViewGroup没有实现onTouchEvent,使用父类的View的onTouchEvent
dispatchTouchEvent | onInterceptTouchEvent | onTouchEvent | |
---|---|---|---|
Activity | √ | √ | |
ViewGroup | √ | √ | √ |
View | √ | √ |
事件分发U型图
在分析源码前还是放一张图解 Android 事件分发机制中的经典图,对于不太熟悉事件怎么传递的可以先熟悉这张图,然后带着这张图的流程去看源码。
Activity
下面源码来自Activity,前面说了Android底层监听到触摸产生事件,事件一路传递到Activity,最终会调用Activity的dispatchTouchEvent方法。而在dispatchTouchEvent
方法里面的if (getWindow().superDispatchTouchEvent(ev))
判断将事件向下传递。
getWindow()获取的是Window的唯一实现类PhoneWindow,在PhoneWindow
中调用mDecor.superDispatchTouchEvent(event)
,mDecor其实是DecorView
,DecorView继承FrameLayout继承ViewGroup,在DecorView中调用的super.dispatchTouchEvent(event)
其实是ViewGroup
的方法,这里就将Activity的事件传递到了ViewGroup。
我们继续看Activity的方法,在Activity中对事件的处理取决于关键代码getWindow().superDispatchTouchEvent(ev)
- 判断getWindow().superDispatchTouchEvent(ev),代码回调到ViewGroup::dispatchTouchEvent方法,
- 如果ViewGroup::dispatchTouchEvent返回true,那么Activity的dispatchTouchEvent返回true,不走自身onTouchEvent,
- 如果ViewGroup::dispatchTouchEvent返回false,那么Activity调用自身onTouchEvent并得到onTouchEvent的返回值
- 如果在自己Activity中直接重写dispatchTouchEvent()方法,直接返回true或者false,那么就不会分发到ViewGroup,也不会执行Activity的onTouchEvent(),只有在自己Activity中不实现dispatchTouchEvent(),或者返回super.dispatchTouchEvent(ev)那么才有可能向下传递。
上面一大段的判断其实就是看getWindow().superDispatchTouchEvent(ev)
返回什么,你看完这个再对照U型图,看看是不是一样的
dispatchTouchEvent
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback,
AutofillManager.AutofillClient {
...
public boolean dispatchTouchEvent(MotionEvent ev) {
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
}
onTouchEvent
...
public boolean onTouchEvent(MotionEvent event) {
if (mWindow.shouldCloseOnTouch(this, event)) {
finish();
return true;
}
return false;
}
...
public class PhoneWindow extends Window implements MenuBuilder.Callback {
...
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
return mDecor.superDispatchTouchEvent(event);
}
...
}
到这里就将Activity的事件传递到ViewGroup
public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
...
public boolean superDispatchTouchEvent(MotionEvent event) {
return super.dispatchTouchEvent(event);
}
...
}
ViewGroup
在ViewGroup中重写了dispatchTouchEvent方法,但是onTouchEvent是复用View的。
当activity中将事件传递到了ViewGroup,在ViewGroup中首先由dispatchTouchEvent用于事件分发。ViewGroup的dispatchTouchEvent()方法非常的长,下面我会做3个部分讲解
首先在MotionEvent中我们知道有几种触摸状态
- ACTION_DOWN:按下的状态
- ACTION_MOVE:移动的时候状态
- ACTION_UP:抬起的时候状态
虽然源码很长,但是大部分源码都在if (onFilterTouchEventForSecurity(ev))
判断里面,我将判断里面的源码分为3个部分
判断是否拦截
第一部分源码:这一部分主要是判断事件有没有拦截和取消
。在下面这段源码里面,会得到3个属性
- 调用
intercepted = onInterceptTouchEvent(ev)
,判断是否有拦截 - 得到
canceled
是否取消事件 - 声明一个
TouchTarget
类型变量newTouchTarget
, TouchTarget是一个单链表结构,在下面会介绍到。
简单介绍下:intercepted和canceled会直接影响第二部分代码是否执行if (!canceled && !intercepted)
,在没有取消和没有拦截的情况下才会走第二部分代码。newTouchTarget也和重要,在第二部分判断中if (newTouchTarget == null && childrenCount != 0)
只有等于空和ViewGroup中有View才能开始循环
final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
...
intercepted = onInterceptTouchEvent(ev)
...
}else{
intercepted = false
}
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
TouchTarget newTouchTarget = null;
得到newTouchTarget
final View[] children = mChildren; mChildren是ViewGroup中的子View集合,比如ViewGroup中有A,B ,C,三个View,A中还有子View,但是在mChildren只有A、B、C,也就是只包含这个视图的子视图。
第二部分源码,主要是获取事件分发的消耗树
。 首先是判断拦截和取消,并且这个判断if (actionMasked == MotionEvent.ACTION_DOWN ||
会让ACTION_MOVE和ACTION_UP类型的事件不能进入方法,也就是只有第一次的ACTION_DOWN能进入。
- 首先看
for (int i = childrenCount - 1; i >= 0; i--)
,用于遍历ViewGroup里面的View - 然后就是两个重要方法调用
dispatchTransformedTouchEvent
和addTouchTarget
我们要知道在循环下,会找到一条事件的消耗树
,这一个消耗树
就是事件从activity传递到触摸View的路径。dispatchTransformedTouchEvent
方法就是判断那一个View会进行消耗;如果它会消耗,那么在addTouchTarget
方法里面有一个TouchTarget变量用于保存这个View,由于TouchTarget是一个链表,那么他就能将整个事件链收集起来。
//2 没有拦截和取消的情况下得到mFirstTouchTarget
if (!canceled && !intercepted) {
//下面代码只针对ACTION_DOWN事件
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
for (int i = childrenCount - 1; i >= 0; i--) {
//遍历ViewGroup中的View
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
//关键步骤
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
}
}
}
}
...
dispatchTransformedTouchEvent
dispatchTransformedTouchEvent()会根据传入的child是否为空去调用相应的方法。
- 当child == null时会调用
super.dispatchTouchEvent(event)
也就是交给自身的dispatchTouchEvent(),如果自己不处理,那么往上抛给它的父View或Activity - 当child != null时会调用该子view的
dispatchTouchEvent(event)
处理,当然该view可能是一个View也可能是一个ViewGroup,也就是说调用的可能是view的dispatchTouchEvent(),也可能是ViewGroup的dispatchTouchEvent(),
如果是ViewGroup那就在循环我们讲解的过程,我们直接看child == null的时候,看下面一小节
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);
//当child == null时会将Touch事件传递给该ViewGroup自身的dispatchTouchEvent()处理
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//当child != null时会调用该子view(当然该view可能是一个View也可能是一个ViewGroup)的dispatchTouchEvent(event)处理.
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
...
final MotionEvent transformedEvent;
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
final float offsetX = mScrollX - child.mLeft;
final float offsetY = mScrollY - child.mTop;
event.offsetLocation(offsetX, offsetY);
handled = child.dispatchTouchEvent(event);
event.offsetLocation(-offsetX, -offsetY);
}
return handled;
}
transformedEvent = MotionEvent.obtain(event);
} else {
transformedEvent = event.split(newPointerIdBits);
}
// Perform any necessary transformations and dispatch.
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());
}
handled = child.dispatchTouchEvent(transformedEvent);
}
// Done.
transformedEvent.recycle();
return handled;
}
View的dispatchTouchEvent()核心方法
如果是ViewGroup那就在循环我们讲解的过程,我们直接看child == null的时候,看View的dispatchTouchEvent()核心方法,后面会单独讲解View的dispatchTouchEvent()
在View的dispatchTouchEvent()中有一段代码
li.mOnTouchListener.onTouch(this, event)
:当view设置了onTouch事件并且返回true
,那么dispatchTransformedTouchEvent就返回true,并且这个子View的onTouchEvent都不能执行if (!result && onTouchEvent(event))
:当onTouch返回false 或者没有设置这个事件,但是onTouchEvent返回true。那么dispatchTransformedTouchEvent照样返回true。
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;
}
}
addTouchTarget
这里可以看到,如果调用了addTouchTarget,那么会生成一个TouchTarget
,并且将生产的target
设置为mFirstTouchTarget
。每个ViewGroup都持有一个mFirstTouchTarget变量,该变量指向一个TouchTarget链表
(不带头结点)的首结点,该链表的结点用于存放当前ViewGroup的子结点
,该子结点必须是“消耗树”上的结点。TouchTarget链表会在ACTION_DOWN被清空即“消耗树”消亡。总之,ViewGroup 的 TouchTarget链表存放该 ViewGroup 在“消耗树”上的所有子结点,mFirstTouchTarget指向它的首结点
。
private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
target.next = mFirstTouchTarget;
mFirstTouchTarget = target;
return target;
}
循环消耗树
第三部分源码:经过步骤2在ACTION_DOWN的时候已经收集到了消耗树,这里主要是判断消耗树,在ACTION_MOVE和ACTION_UP的时候遍历消耗树
- mFirstTouchTarget为空,那么就调用
dispatchTransformedTouchEvent
方法,这个方法传入的childView为空,就会将事件交给自己的dispatchTouchEvent()处理,自己不处理就想上抛 - mFirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View,那么久开始循环消耗树将事件开始传递,这里要知道,能进入到这里的循环只有ACTION_MOVE和ACTION_UP事件。
//3 根据newTouchTarget的值调用dispatchTransformedTouchEvent()方法,判断中不同的地方在于传入的第三个参数一个是null一个是子View
if (mFirstTouchTarget == null) {
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
}else{
TouchTarget target = mFirstTouchTarget;
while (target != null) {
if (dispatchTransformedTouchEvent(ev, cancelChild,
target.child, target.pointerIdBits)) {
handled = true;
}
}
}
dispatchTouchEvent
这里是ViewGroup的dispatchTouchEvent全部代码,代码中有注释讲解。
public abstract class ViewGroup extends View implements ViewParent, ViewManager {
...
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
ev.setTargetAccessibilityFocus(false);
}
//是否已经处理
boolean handled = false;
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
final int actionMasked = action & MotionEvent.ACTION_MASK;
//对ACTION_DOWN进行处理
if (actionMasked == MotionEvent.ACTION_DOWN) {
//当开始新的触摸手势时,扔掉所有以前的状态,将mFirstTouchTarget设置为null , mFirstTouchTarget是触摸目标链接列表中的第一个触摸目标。
cancelAndClearTouchTargets(ev);
//重置Touch状态标识
resetTouchState();
}
// 检查是否有拦截
// 使用变量intercepted来标记ViewGroup是否拦截Touch事件的传递.
final boolean intercepted;
//事件为ACTION_DOWN或者mFirstTouchTarget不为null(即已经找到能够接收touch事件的目标组件)时if成立
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
//判断disallowIntercept(禁止拦截)标志位
//可调用requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法,禁止执行是否需要拦截的判断,所以判断标志位
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
if (!disallowIntercept) {
//当没有禁止时进行事件拦截
intercepted = onInterceptTouchEvent(ev);
ev.setAction(action); // restore action in case it was changed
} else {
//当禁止拦截判断时(即disallowIntercept为true)设置intercepted = false
intercepted = false;
}
} else {
//没有触摸目标并且此操作不是初始向下操作,因此设置 intercepted = true表示ViewGroup执行Touch事件拦截的操作。
intercepted = true;
}
// 如果拦截,开始正常事件调度。如果有一个视图是处理手势,则进行正常事件调度
if (intercepted || mFirstTouchTarget != null) {
ev.setTargetAccessibilityFocus(false);
}
//检查取消
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;
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
final int actionIndex = ev.getActionIndex();
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//清除此指针ID的早期触摸目标,以防它们不同步。
removePointersFromTouchTargets(idBitsToAssign);
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
// 找到可以接收事件的子级。从前到后扫描子视图
final ArrayList<View> preorderedList = buildTouchDispatchChildList();
final boolean customOrder = preorderedList == null
&& isChildrenDrawingOrderEnabled();
//此视图组的子视图
final View[] children = mChildren;
//childrenCount赋值在 addInArray()方法中
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (childWithAccessibilityFocus != null) {
if (childWithAccessibilityFocus != child) {
continue;
}
childWithAccessibilityFocus = null;
i = childrenCount - 1;
}
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y, child, null)) {
ev.setTargetAccessibilityFocus(false);
continue;
}
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
// 找到接收Touch事件的子View
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
resetCancelNextUpFlag(child);
//dispatchTransformedTouchEvent()会调用子元素的dispatchTouchEvent()。
/*
*如果dispatchTransformedTouchEvent()返回false即子View
的onTouchEvent返回false(即Touch事件未被消费)那么就不满足该if条件,也就无法执行addTouchTarget()
从而导致mFirstTouchTarget为null.那么该子View就无法继续处理ACTION_MOVE事件
和ACTION_UP事件
*/
/*
* 对于此处ACTION_DOWN的处理具体体现在dispatchTransformedTouchEvent()
* 该方法返回boolean,如下:
* true---->事件被消费----->mFirstTouchTarget!=null
* false--->事件未被消费---->mFirstTouchTarget==null
* 因为在dispatchTransformedTouchEvent()会调用递归调用dispatchTouchEvent()和onTouchEvent()
* 所以dispatchTransformedTouchEvent()的返回值实际上是由onTouchEvent()决定的.
* 简单地说onTouchEvent()是否消费了Touch事件(true or false)的返回值决定了dispatchTransformedTouchEvent() 的返回值,从而决定了mFirstTouchTarget是否为null,从而进一步决定了ViewGroup是否
* 处理Touch事件
*/
/**
* 如果dispatchTransformedTouchEvent()返回true即子View
* 的onTouchEvent返回true(即Touch事件被消费)那么就满足该if条件.
* 从而mFirstTouchTarget不为null
*/
//该方法返回true则表示子View消费掉该事件,同时进入该if判断
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 子视图想要在触摸范围内接受触摸
mLastTouchDownTime = ev.getDownTime();
if (preorderedList != null) {
//子视图的下标在 presorted集合中,找到原来的下标
for (int j = 0; j < childrenCount; j++) {
if (children[childIndex] == mChildren[j]) {
mLastTouchDownIndex = j;
break;
}
}
} else {
mLastTouchDownIndex = childIndex;
}
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//将ViewGroup中触摸的View添加到列表开头
newTouchTarget = addTouchTarget(child, idBitsToAssign);
//已经将Touch派发给新的TouchTarget
alreadyDispatchedToNewTouchTarget = true;
break;
}
ev.setTargetAccessibilityFocus(false);
}
if (preorderedList != null) preorderedList.clear();
}
if (newTouchTarget == null && mFirstTouchTarget != null) {
//找不到要接收事件的子级。将指针分配给最近添加的目标
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
//newTouchTarget指向了最初的TouchTarget
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
/*
* 将touch事件派发给目标View
* 经过上面对于ACTION_DOWN的处理后mFirstTouchTarget有两种情况:
* 1 mFirstTouchTarget为null
* 2 mFirstTouchTarget不为null
* 并且上面那些复杂的流程只是针对ACTION_DOWN
* 而是从此处开始执行,比如ACTION_MOVE和ACTION_UP
*/
if (mFirstTouchTarget == null) {
/*
* 情况1:mFirstTouchTarget为null
* mFirstTouchTarget == null 即没有找到能够消费touch事件的子组件或Touch事件被拦截了
* 则调用ViewGroup的dispatchTransformedTouchEvent()方法处理Touch事件则和普通View一样
* 即子View没有消费Touch事件,那么子View的上层ViewGroup才会调用其onTouchEvent()处理Touch事件.
* 也就是说此时ViewGroup像一个普通的View那样调用dispatchTouchEvent(),且在dispatchTouchEvent()中会去调用onTouchEvent()方法
* 这就是为什么子view对于Touch事件处理返回true那么其上层的ViewGroup就无法处理Touch事件了
*/
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
/**
* 情况2: FirstTouchTarget不为null即找到了可以消费Touch事件的子View且后续Touch事件可以传递到该子View
*/
// 发送到触摸目标,不包括新的触摸目标,如果我们已经发送到它。必要时取消触摸目标
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;
//对于非ACTION_DOWN事件继续传递给目标子组件进行处理,依然是递归调用dispatchTransformedTouchEvent()
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;
}
}
/**
* 处理ACTION_UP和ACTION_CANCEL
* Update list of touch targets for pointer up or cancel, if needed.
* 在此主要的操作是还原状态
*/
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;
}
...
...
...
}
onInterceptTouchEvent
//判断是否事件拦截
public boolean onInterceptTouchEvent(MotionEvent ev) {
if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
&& ev.getAction() == MotionEvent.ACTION_DOWN
&& ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
&& isOnScrollbarThumb(ev.getX(), ev.getY())) {
return true;
}
return false;
}
View
上面我们知道了ViewGroup的dispatchTouchEvent方法调用的流程,我们知道View的dispatchTouchEvent方法被调用时在ViewGroup的 dispatchTransformedTouchEvent方法中,那么View的dispatchTouchEvent主要做了什么操作呢
dispatchTouchEvent
当我们对一个View设置setOnTouchListener和setOnClickListener的时候,我们知道如果setOnTouchListener返回true,则不会调用setOnClickListener方法,在源码里面我们知道为什么,以为setOnTouchListener返回true,那么if (!result && onTouchEvent(event))
就不成立,在View的onTouchEvent方法里面才调用了OnClickListener事件
。
对于view的dispatchTouchEvent方法,核心就是上面介绍ViewGroup时候介绍的。
public class View implements Drawable.Callback, KeyEvent.Callback,
AccessibilityEventSource {
...
public boolean dispatchTouchEvent(MotionEvent event) {
// 如果事件应首先由可访问性焦点处理。
if (event.isTargetAccessibilityFocus()) {
// We don't have focus or no virtual descendant has it, do not handle the event.
if (!isAccessibilityFocusedViewOrHost()) {
return false;
}
// 我们有焦点并得到了事件,然后使用正常的事件发送.
event.setTargetAccessibilityFocus(false);
}
// 是否消耗事件
boolean result = false;
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(event, 0);
}
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;
}
//若设置了OnTouchListener,则先调用onTouch()。
ListenerInfo li = mListenerInfo;
if (li != null && li.mOnTouchListener != null
&& (mViewFlags & ENABLED_MASK) == ENABLED
&& li.mOnTouchListener.onTouch(this, event)) {
result = true;
}
//若onTouch()没有消耗事件则调用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;
}
...
}
onTouchEvent
对于onTouchEvent方法看 performClickInternal()
,其中调用performClick()
,最后能看到熟悉的 li.mOnClickListener.onClick(this);
public boolean onTouchEvent(MotionEvent event) {
final float x = event.getX();
final float y = event.getY();
final int viewFlags = mViewFlags;
final int action = event.getAction();
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;
}
if (mTouchDelegate != null) {
if (mTouchDelegate.onTouchEvent(event)) {
return true;
}
}
if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
switch (action) {
case MotionEvent.ACTION_UP:
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
if ((viewFlags & TOOLTIP) == TOOLTIP) {
handleTooltipUp();
}
if (!clickable) {
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
break;
}
boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
// take focus if we don't have it already and we should in
// touch mode.
boolean focusTaken = false;
if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
focusTaken = requestFocus();
}
if (prepressed) {
// The button is being released before we actually
// showed it as pressed. Make it show the pressed
// state now (before scheduling the click) to ensure
// the user sees it.
setPressed(true, x, y);
}
if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
// This is a tap, so remove the longpress check
removeLongPressCallback();
// Only perform take click actions if we were in the pressed state
if (!focusTaken) {
// Use a Runnable and post this rather than calling
// performClick directly. This lets other visual state
// of the view update before click actions start.
if (mPerformClick == null) {
mPerformClick = new PerformClick();
}
if (!post(mPerformClick)) {
performClickInternal();
}
}
}
if (mUnsetPressedState == null) {
mUnsetPressedState = new UnsetPressedState();
}
if (prepressed) {
postDelayed(mUnsetPressedState,
ViewConfiguration.getPressedStateDuration());
} else if (!post(mUnsetPressedState)) {
// If the post failed, unpress right now
mUnsetPressedState.run();
}
removeTapCallback();
}
mIgnoreNextUpEvent = false;
break;
case MotionEvent.ACTION_DOWN:
if (event.getSource() == InputDevice.SOURCE_TOUCHSCREEN) {
mPrivateFlags3 |= PFLAG3_FINGER_DOWN;
}
mHasPerformedLongPress = false;
if (!clickable) {
checkForLongClick(0, x, y);
break;
}
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();
}
mPendingCheckForTap.x = event.getX();
mPendingCheckForTap.y = event.getY();
postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
} else {
// Not inside a scrolling container, so show the feedback right away
setPressed(true, x, y);
checkForLongClick(0, x, y);
}
break;
case MotionEvent.ACTION_CANCEL:
if (clickable) {
setPressed(false);
}
removeTapCallback();
removeLongPressCallback();
mInContextButtonPress = false;
mHasPerformedLongPress = false;
mIgnoreNextUpEvent = false;
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
break;
case MotionEvent.ACTION_MOVE:
if (clickable) {
drawableHotspotChanged(x, y);
}
// Be lenient about moving outside of buttons
if (!pointInView(x, y, mTouchSlop)) {
// Outside button
// Remove any future long press/tap checks
removeTapCallback();
removeLongPressCallback();
if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
setPressed(false);
}
mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
}
break;
}
return true;
}
return false;
}
小结:整个事件分发的关键步骤还是在ViewGroup的dispatchTouchEvent方法里面,这个方法里面dispatchTransformedTouchEvent
方法涉及到View的dispatchTouchEvent
的是需要理解的核心。
但整个事件分发的逻辑还是离不开上面的U型图,然后每个流程可以看下面这张图。图片来源为下面连接中博客。
最后以一个问题来结束本篇文章:如果有一个ViewGroup,里面有一个ChildView A,当只设置ChildView A的OnTouchListener事件并返回false,点击ChildView A同时并移动手指,那么ChildView A能否接收到ACTION_MOVE事件?为什么?如果返回true呢?
当返回false,A是Activity,F是父View,C2222代表ChildView
07-12 19:18:25.661 23424-23424/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_DOWN
===F=== dispatchTouchEvent --> ACTION_DOWN
===F=== onInterceptTouchEvent --> ACTION_DOWN
===C2222=== dispatchTouchEvent --> ACTION_DOWN
onTouch------------
===C2222=== onTouchEvent --> ACTION_DOWN
===F=== onTouchEvent --> ACTION_DOWN
===A=== onTouchEvent --> ACTION_DOWN
07-12 19:18:25.680 23424-23424/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
===A=== onTouchEvent --> ACTION_MOVE
07-12 19:18:25.688 23424-23424/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
===A=== onTouchEvent --> ACTION_MOVE
===A=== dispatchTouchEvent --> ACTION_UP
===A=== onTouchEvent --> ACTION_UP
当返回true
07-12 19:16:39.863 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_DOWN
===F=== dispatchTouchEvent --> ACTION_DOWN
===F=== onInterceptTouchEvent --> ACTION_DOWN
===C2222=== dispatchTouchEvent --> ACTION_DOWN
onTouch------------
07-12 19:16:39.875 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
===F=== dispatchTouchEvent --> ACTION_MOVE
07-12 19:16:39.876 23003-23003/com.hongliang.demo I/sunzn: ===F=== onInterceptTouchEvent --> ACTION_MOVE
===C2222=== dispatchTouchEvent --> ACTION_MOVE
onTouch------------
07-12 19:16:39.895 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
07-12 19:16:39.896 23003-23003/com.hongliang.demo I/sunzn: ===F=== dispatchTouchEvent --> ACTION_MOVE
===F=== onInterceptTouchEvent --> ACTION_MOVE
===C2222=== dispatchTouchEvent --> ACTION_MOVE
onTouch------------
07-12 19:16:39.909 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
===F=== dispatchTouchEvent --> ACTION_MOVE
===F=== onInterceptTouchEvent --> ACTION_MOVE
===C2222=== dispatchTouchEvent --> ACTION_MOVE
onTouch------------
07-12 19:16:39.927 23003-23003/com.hongliang.demo I/sunzn: ===A=== dispatchTouchEvent --> ACTION_MOVE
===F=== dispatchTouchEvent --> ACTION_MOVE
===F=== onInterceptTouchEvent --> ACTION_MOVE
===C2222=== dispatchTouchEvent --> ACTION_MOVE
07-12 19:16:39.928 23003-23003/com.hongliang.demo I/sunzn: onTouch------------
===A=== dispatchTouchEvent --> ACTION_UP
===F=== dispatchTouchEvent --> ACTION_UP
===F=== onInterceptTouchEvent --> ACTION_UP
===C2222=== dispatchTouchEvent --> ACTION_UP
onTouch------------
为什么是C2222
的dispatchTouchEvent
打印ACTION_MOVE,而不是onTouchEvent
?
Android事件分发机制本质是树的深度遍历
dispatchTouchEvent源码解析
图解 Android 事件分发机制
Android触摸事件全过程分析:由产生到Activity.dispatchTouchEvent()