触摸事件分发 一般可以说是从activity开始,经过PhoneWindow到DecorView,再在ViewTree上进行分发。
如果要更深一步了解触摸事件怎么到activity的,可以看以下文章
1、Android 输入事件一撸到底之源头活水(1)
2、Android 输入事件一撸到底之DecorView拦路虎(2)
3、Android 输入事件一撸到底之View接盘侠(3
触摸事件从activity到DecorView源码
//activity中
public boolean dispatchTouchEvent(MotionEvent ev) {
……
//getWindow()就是拿到了PhoneWindow
if (getWindow().superDispatchTouchEvent(ev)) {
return true;
}
return onTouchEvent(ev);
}
//PhoneWindow中
@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
//mDecor就是DecorView
return mDecor.superDispatchTouchEvent(event);
}
//DecorView中
public boolean superDispatchTouchEvent(MotionEvent event) {
//调用ViewGroup的事件分发
return super.dispatchTouchEvent(event);
}
过程比较简单,其实在DecorView中复写了dispatchTouchEvent方法,而activity到DecorView的过程中并没有调用到,如果想知道具体原因,可以看上面的第二篇文章。
由简入繁,先分析单点触摸事件中有ACTION_DOWN,ACTION_MOVE,ACTION_UP
在没有任何特殊处理下,我们知道在ACTION_DOWN事件时,会找到消费触摸事件的视图,然后ACTION_MOVE,ACTION_UP的时候会沿着原来的路径传到该视图让其继续消费。
down事件怎么形成虚拟路径
关键在dispatchTouchEvent和dispatchTransformedTouchEvent代码中
去除其他的无关的,只分析一般情况
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
if (actionMasked == MotionEvent.ACTION_DOWN) {
//down事件的一些初始化和重置
cancelAndClearTouchTargets(ev);
resetTouchState();
}
// 检测是否拦截
final boolean intercepted;
……
//一般情况,非拦截,非取消
if (!canceled && !intercepted) {
if (是down事件) {
final int childrenCount = mChildrenCount;
if (newTouchTarget == null && childrenCount != 0) {
//后add进来的view开始遍历
for (int i = childrenCount - 1; i >= 0; i--) {
final int childIndex = getAndVerifyPreorderedIndex(
childrenCount, i, customOrder);
final View child = getAndVerifyPreorderedView(
preorderedList, children, childIndex);
if (如果子view不接收触摸事件,或者点击范围不在子view区域) {
continue;
}
//非常关键的一步:
//这个方法中会先调用子View的dispatchTouchEvent,是一种责任链的模式
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
//能进来,说明子View或者孙子View消费了事件
//也是非常关键的一步:这里赋值了mFirstTouchTarget
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
}
}
if (mFirstTouchTarget == null) {
// 说明没有子View或者孙子View消费了触摸事件
// 也是子View的touch没有消费事件,事件又往parent的Touch传的表现
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
TouchTarget target = mFirstTouchTarget;
while (target != null) {
// alreadyDispatchedToNewTouchTarget为true,说明是点击事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//否者就move或者up事件
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
}
}
}
return handled;
}
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
View child, int desiredPointerIdBits) {
final boolean handled;
……
if (child == null) {
//如果child为null,就调super,说明这个ViewGroup当成view来处理分发事件
handled = super.dispatchTouchEvent(transformedEvent);
} else {
//调用子View的dispatchTouchEvent,直接决定了dispatchTransformedTouchEvent的返回值
handled = child.dispatchTouchEvent(transformedEvent);
}
return handled;
}
假如viewTree的关系如下
A(父) -> B1、B2、B3
B2 (父)-> C1、C2、C3
假设C2消费了down事件,事件分发的路径是 A -> B2 -> C2
可以看到,在B2的dispatchTouchEvent中的这段代码里,
会创建mFirstTouchTarget,同样A中也是
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
newTouchTarget = addTouchTarget(child, idBitsToAssign);
alreadyDispatchedToNewTouchTarget = true;
break;
}
所以在down事件结束后,A 和 B2里的mFirstTouchTarget都被创建,而TouchTarget中的View正是child,即A中的mFirstTouchTarget里持有了B2。B2中的mFirstTouchTarget里持有了C2
这样,一个虚拟的事件分发链路就出现了。
在move事件中,就直接走这段代码里的else逻辑,直接从touchtarget中获取child
TouchTarget target = mFirstTouchTarget;
while (target != null) {
// alreadyDispatchedToNewTouchTarget为true,说明是点击事件
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
handled = true;
} else {
//否者就move或者up事件
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
}
}
cancel事件什么时候会被触发
看下这样的布局:
activity里放一个FrameLayout1,
FrameLayout1里放一个FrameLayout2,
FrameLayout2里放一个View1
down事件让View1消费,move事件被FrameLayout1拦截,打印出日志如下,
可以看到FrameLayout2和View1都出现了cancle
activity: dispatchTouchEvent ACTION_DOWN
FrameLayout1: dispatchTouchEvent ACTION_DOWN
FrameLayout1: onInterceptTouchEvent ACTION_DOWN
FrameLayout2: dispatchTouchEvent ACTION_DOWN
FrameLayout2: onInterceptTouchEvent ACTION_DOWN
View1: dispatchTouchEvent ACTION_DOWN
View1: onTouchEvent ACTION_DOWN
activity: dispatchTouchEvent ACTION_MOVE
FrameLayout1: dispatchTouchEvent ACTION_MOVE
FrameLayout1: onInterceptTouchEvent ACTION_MOVE
FrameLayout2: dispatchTouchEvent ACTION_CANCEL
View1: dispatchTouchEvent ACTION_CANCEL
View1: onTouchEvent ACTION_CANCEL
主要的代码在处理move事件那里:
final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;
if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {
handled = true;
}
拦截后,cancelChild为true,然后在dispatchTransformedTouchEvent中
if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
event.setAction(MotionEvent.ACTION_CANCEL);
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
handled = child.dispatchTouchEvent(event);
}
event.setAction(oldAction);
return handled;
}
- 如果是View,调用dispatchTouchEvent和onTouchEvent
- 如果是ViewGroup,则调用ViewGroup的dispatchTouchEvent,里边的canceled为true,最终调resetTouchState清除mFirstTouchTarget。