ViewGroup中的onTouchEvent方法是继承自View的onTouchEvent方法,因此这里只对分发和接受事件做源码分析。
分析前铺垫
首先事件流中常用的一些事件相关的细节点必须要有所了解这样才能把整个流程搞透彻。
- 常用的事件Action
MotionEvent.ACTION_DOWN:在第一个点被按下时触发
MotionEvent.ACTION_UP:当屏幕上唯一的点被放开时触发
MotionEvent.ACTION_MOVE:当有点在屏幕上移动时触发。值得注意的是,由于它的灵敏度很高,而我们的手指又不可能完全静止(即使我们感觉不到移动,但其实我们的手指也在不停地抖动),所以实际的情况是,基本上只要有点在屏幕上,此事件就会一直不停地被触发。
ACTION_CANCEL:取消事件,一般由系统给出
ACTION_OUTSIDE: 表示用户触碰超出了正常的UI边界.
多点触控
MotionEvent.ACTION_POINTER_DOWN:当屏幕上已经有一个点被按住,此时再按下其他点时触发。
MotionEvent.ACTION_POINTER_UP:当屏幕上有多个点被按住,松开其中一个点时触发(即非最后一个点被放开时)。
dispatchTouchEvent中出现频率高的关键词和方法。
- TouchTarget
/*
*描述一个接受事件的View和它捕捉到的触摸点,暂称触摸目标,触摸点范围在0-31,也就是32位,最多支持32
*个触点,能通过1位来表示当前触摸的点
*/
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;
// 触摸点的id
public int pointerIdBits;
//单链中指向的下一个节点的TouchTarget
public TouchTarget next;
private TouchTarget() {
}
//构建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;
}
}
}
- getTouchTarget
/**
* 在mFirstTouchTarget单链中寻找目标TouchTarget 如果找到即返回,没有则反
*/
private TouchTarget getTouchTarget(View child) {
//mFirstTouchTarget如果为null,那么直接返回null,如果能在mFirstTouchTarget单链中的
//TouchTarget的child找到和传入的child相同的就返回此TouchTarget。
for (TouchTarget target = mFirstTouchTarget; target != null; target = target.next) {
if (target.child == child) {
return target;
}
}
return null;
}
addTouchTarget
可认为是更新mFirstTouchTarget,举例:
如果原来mFirstTouchTarget为touchTargetA,其中的 child为childA,next为null,即:mFirstTouchTarget=touchTargetA(childA)——>null
这时候调用addTouchTarget,传入child假设为chlidB,那么addTouchTarget的做法就是先构建 touchTargetB,然后将mFirstTouchTarget,添加到touchTargetB的next,并且更新mFirstTouchTarget为touchTargetB。即:
mFirstTouchTarget=touchTargetB(childB)—>touchTargetA(childA)—>null
/**
* 添加指定的子视图构成的TouchTarget,添加到触摸目标列表的开头。
* 假定列表本来不包括此子视图的触摸目标
*/
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
//构建一个新的触摸目标
TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
//将原有的mFirstTouchTarget添加到此触摸目标其后
target.next = mFirstTouchTarget;
//更新mFirstTouchTarget列表
mFirstTouchTarget = target;
//返回此触摸目标
return target;
}
- dispatchTransformedTouchEvent
/**
*将事件交给坐标系中接收事件的View处理,过滤不必要的触点,如果需要重新组装这个事件,
*如果没有子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.
// 如果强制取消或是取消动作,则给子View或者当前ViewGroup作为View分发取消的的事件
final int oldAction = event.getAction();
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;
}
/***
* getPointerIdBits是获取触摸点的数量然后循环把各触摸点的id左移1位相或,即
* 把所有点的id信息保存到idBits中
* public final int getPointerIdBits() {
* int idBits = 0;
* final int pointerCount = nativeGetPointerCount(mNativePtr);
* for (int i = 0; i < pointerCount; i++) {
* idBits |= 1 << nativeGetPointerId(mNativePtr, i);
* }
* return idBits;
* }
*/
final int oldPointerIdBits = event.getPointerIdBits();
//两者&运算,判断desiredPointerIdBits 即此id触摸点是否已经存在其中
final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;
//一些特殊情况下,可能会产生没有触摸点的事件,这种事件并不需要分发
if (newPointerIdBits == 0) {
return false;
}
final MotionEvent transformedEvent;
//如果触摸点数量和信息没有发生改变,直接分发
if (newPointerIdBits == oldPointerIdBits) {
if (child == null || child.hasIdentityMatrix()) {
//如果子view为空,那么当前的ViewGroup作为一个View来分发
if (child == null) {
handled = super.dispatchTouchEvent(event);
} else {
//否则交给子View分发
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);
}
//如果触摸点信息不同或者不满足child == null || child.hasIdentityMatrix()条件
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;
}
接下来需要了解一些关于MotionEvent中的一些知识点:
- MotionEvent Index
MotionEvent把各个点的信息都存储在一个数组中。点的索引值就是它在数组中的位置。大多数用来与点交互的MotionEvent函数都是以索引值而不是点的ID作为参数的。getActionIndex()返回点的数据在MotionEvent中的索引Index
//ACTION_POINTER_INDEX_SHIFT 8
//ACTION_POINTER_INDEX_MASK 0xff00
//一般Action的后16位,前8位表示事件触控点的索引信息,后8位表示的是事件类型
public final int getAction() {
return nativeGetAction(mNativePtr);
}
//这里可以看出获取actionIndex是将Action&0xff00,也就是取16位中的前8位,然后右移8位,比如:
//ACTION_DOWN 0x0000&0xff00 得到的index就是0
public final int getActionIndex() {
return (nativeGetAction(mNativePtr) & ACTION_POINTER_INDEX_MASK)
>> ACTION_POINTER_INDEX_SHIFT;
}
- MotionEvent ID
每个点也都有一个ID映射,该ID映射在整个手势期间一直存在,以便我们单独地追踪每个点
使用getPointerId()可以获得一个点的ID,在手势随后的移动事件中,就可以用该ID来追踪这个点,点的ID在它的生命周期内是保证不会改变的
// Use the pointer ID to find the index of the active pointer
// and fetch its position。通过ID获取点的在数组中的索引Index
int pointerIndex = event.findPointerIndex(mActivePointerId);
// Get the pointer's current position,通过索引获取点的位置坐标
float x = event.getX(pointerIndex);
float y = event.getY(pointerIndex);
关于MotionEvent的index和id的问题,可以参考
> http://blog.csdn.net/hamiton/article/details/51326075
ViewGroup中的dispatchTouchEvent
了解完细节的基础,下面开始对dispatchTouchEvent的分析:
@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
//系统调试 忽略
if (mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
}
----------
1、事件初始化重置一些属性和状态
//事件处理的标记
boolean handled = false;
//判断事件流能否被分发
//onFilterTouchEventForSecurity()用安全机制来过滤触摸事件,true为不过滤分发下去,false
//则销毁掉该事件。方法具体实现是去判断是否被其它窗口遮挡住了,如果遮挡住就要过滤掉该事件。
if (onFilterTouchEventForSecurity(ev)) {
final int action = ev.getAction();
//action 用16位记录触摸点和事件类型,8-15位表示触摸点,0-7位表示事件类型
//MotionEvent.ACTION_MASK 0xff数值上等同于0x00ff 两者&可以过滤触摸点,
//得到低8位的事件类型,action和ACTION_POINTER_INDEX_MASK(0xff00)&可得到触摸点
final int actionMasked = action & MotionEvent.ACTION_MASK;
//当前事件是一个按下事件,按下事件是一个初始化事件,所以分发之前需要重置一些状态
//mFirstTouchTarget单链表置为Null,清理触摸操作的所有痕迹
if (actionMasked == MotionEvent.ACTION_DOWN) {
//在启动新的触摸手势时清除所有先前的状态。
//由于一个应用程序切换,ANR,或一些其他的状态变化,framework可能已经失去了前一个手势
//事件的向上或者取消事件,重置接受者
cancelAndClearTouchTargets(ev);
resetTouchState();
}
----------
2、事件是否拦截的判断
//事件拦截的标记
final boolean intercepted;
//ACTION_DOWN是事件流的起始事件,mFirstTouchTarget是为null的,
//mFirstTouchTarget不为空,也就是已经经历过ACTION_DOWN且找到了事件接受的
//对象,所以这两种情况都需要派发事件处理
if (actionMasked == MotionEvent.ACTION_DOWN
|| mFirstTouchTarget != null) {
/**
* FLAG_DISALLOW_INTERCEPT不允许接受事件的标志,ViewGroup提供了一个
* requestDisallowInterceptTouchEvent来改变这个标记,默认是允许的,即
* ~FLAG_DISALLOW_INTERCEPT
* 所以这个里的disallowIntercept默认是false
*/
final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
//如果当前状态为可拦截,就交由当前Viewgroup的onInterceptTouchEvent
//方法来判断是否需要拦截
if (!disallowIntercept) {
//这里就是是否拦截事件调用的地方,onInterceptTouchEvent默认返回的是
//false,即不拦截。
intercepted = onInterceptTouchEvent(ev);
// 复原按下事件,防止事件发生变化
ev.setAction(action);
} else {
//如果状态是不可以拦截 那么将拦截的标记置为false
intercepted = false;
}
} else {
//当如果这不是一个按下事件,且没有事件接受对象,因此继续接受事件流
intercepted = true;
}
----------
3、按下事件确定事件接受对象
//事件是否取消
final boolean canceled = resetCancelNextUpFlag(this)
|| actionMasked == MotionEvent.ACTION_CANCEL;
//是否需要分发给多个View,比如多个子View叠加
final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
//保存当前要分发给的目标
TouchTarget newTouchTarget = null;
//分发给新TouchTarget的标记
boolean alreadyDispatchedToNewTouchTarget = false;
//ACTION_HOVER_MOVE 4.0引入的鼠标相关的事件,代表鼠标在view之上
if (!canceled && !intercepted) {
//ACTION_DOWN、ACTION_HOVER_MOVE或者多点触控触发
if (actionMasked == MotionEvent.ACTION_DOWN
|| (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
//这是获取触摸事件的序号 以及 触摸点的id信息。
final int actionIndex = ev.getActionIndex(); // always 0 for down
//如果split 为true,即多点触控,就把这个新触摸点的id左移1位保留在
//idBitsToAssign
final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
: TouchTarget.ALL_POINTER_IDS;
//将触摸目标链表中与触摸点id相同的TouchTarget移除
removePointersFromTouchTargets(idBitsToAssign);
//遍历子View来寻找事件接受者
if (childrenCount != 0) {
// Find a child that can receive the event.
// Scan children from front to back.
final View[] children = mChildren;
final float x = ev.getX(actionIndex);
final float y = ev.getY(actionIndex);
for (int i = childrenCount - 1; i >= 0; i--) {
final View child = children[i];
//如果子view不能接受事件或者事件发生的位置不在其内则直接跳过
if (!canViewReceivePointerEvents(child)
|| !isTransformedTouchPointInView(x, y,
child, null)) {
continue;
}
//能够处理这个触点的View是否是已经处理之前的View,即:
//查找child是否存在于mFirstTouchTarget的单链表中。
//比如我在action_down的时候食指放在一个的视图上,
//后面我又放下了我的中指再这个视图上,触发
//ACTION_POINTER_DOWN,这样mFirstTouchTarget
//不为null,且这两个手指touch的视图都是同一个
newTouchTarget = getTouchTarget(child);
if (newTouchTarget != null) {
//mFirstTouchTarget不为null且找到了接受事件的视图
//将触摸点Id复制给新的TouchTarget对象,跳出遍历
newTouchTarget.pointerIdBits |= idBitsToAssign;
break;
}
//重置view的状态,能够重新分发事件
resetCancelNextUpFlag(child);
// 如果上面没有break,只有newTouchTarget为null,说明上面我们找
// 的Child View和之前的肯定不是同一个,而是是新增的, 比如
// mFirstTouchTarget为null,或者多点触
// 摸的时候,一个手指按在了这个当前视图上,另一个手指按在
// 了另一个视图,这时候我们就看此child是否分发该事件。
if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
// 如果分发处理的时候处理了,那么会进入这里
mLastTouchDownTime = ev.getDownTime();
mLastTouchDownIndex = i;
mLastTouchDownX = ev.getX();
mLastTouchDownY = ev.getY();
//把最新处理的事件的视图转化为TouchTarget
//添加到事件接受视图队首,并更新mFirstTouchTarget
//如果原来mFirstTouchTarget为touchTargetA,其中的child为
//childA,next为null,这里新child暂定为chlidB,那么
//addTouchTarget的做法就是先构建touchTargetB,然后将
//touchTargetA,添加到touchTargetB的next,并且更新
//mFirstTouchTarget为touchTargetB。即:
//mFirstTouchTarget=touchTargetB(child)——>touchTargetA
newTouchTarget = addTouchTarget(child, idBitsToAssign);
// 分发给子View了,不用再继续循环了
alreadyDispatchedToNewTouchTarget = true;
break;
}
}
}
//newTouchTarget == null mFirstTouchTarget != null没有找到新的可以分
//发该事件的子View,那最近最少使用的视图来接受
if (newTouchTarget == null && mFirstTouchTarget != null) {
newTouchTarget = mFirstTouchTarget;
while (newTouchTarget.next != null) {
newTouchTarget = newTouchTarget.next;
}
newTouchTarget.pointerIdBits |= idBitsToAssign;
}
}
}
----------
4、处理分发事件
// 将事件交给TouchTarget处理
if (mFirstTouchTarget == null) {
// 没有目标的TouchTarget,ViewGroup自己处理,自己作为普通的View分发事件给onTouchEvent
handled = dispatchTransformedTouchEvent(ev, canceled, null,
TouchTarget.ALL_POINTER_IDS);
} else {
//分发事件给接受者,但不包括已经分发过的对象
TouchTarget predecessor = null;
TouchTarget target = mFirstTouchTarget;
while (target != null) {
final TouchTarget next = target.next;
if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget)
{
//如果mFirstTouchTarget==newTouchTarget,且
//alreadyDispatchedToNewTouchTarget为true,那么表示处理了事件
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上,则重置Touch状态
if (canceled
|| actionMasked == MotionEvent.ACTION_UP
|| actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
resetTouchState();
} else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
//如果多点触控时,单个手指抬起,将触点的id从TouchTarget移除
final int actionIndex = ev.getActionIndex();
final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
removePointersFromTouchTargets(idBitsToRemove);
}
}
//系统调试,没有处理事件
if (!handled && mInputEventConsistencyVerifier != null) {
mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
}
return handled;
}
ViewGroup中的onInterceptTouchEvent
通过viewgroup的dispatchTouchEvent分发得来的事件,到这里决定是否要截获后续事件,显然这里默认返回的是false,即不需要,如果需要拦截就要返回true。当然后续事件也不会发给这个方法,而是发给viewgroup中的onTouchEvent方法,这个方法能获取的事件就只有touch_down,因为按下事件是一个事件流的开始,通过一个按下事件来判断具体是哪个view需要截获事件,接下来的事件也只会传递给拦截的view来处理。所以这里的onInterceptTouchEvent的职责就是是否拦截事件,交给自己的onTouchEvent处理事件
/**
* Implement this method to intercept all touch screen motion events. This
* allows you to watch events as they are dispatched to your children, and
* take ownership of the current gesture at any point.
*
* <p>Using this function takes some care, as it has a fairly complicated
* interaction with {@link View#onTouchEvent(MotionEvent)
* View.onTouchEvent(MotionEvent)}, and using it requires implementing
* that method as well as this one in the correct way. Events will be
* received in the following order:
*
* <ol>
* <li> You will receive the down event here.
* <li> The down event will be handled either by a child of this view
* group, or given to your own onTouchEvent() method to handle; this means
* you should implement onTouchEvent() to return true, so you will
* continue to see the rest of the gesture (instead of looking for
* a parent view to handle it). Also, by returning true from
* onTouchEvent(), you will not receive any following
* events in onInterceptTouchEvent() and all touch processing must
* happen in onTouchEvent() like normal.
* <li> For as long as you return false from this function, each following
* event (up to and including the final up) will be delivered first here
* and then to the target's onTouchEvent().
*
* <li> If you return true from here, you will not receive any
* following events: the target view will receive the same event but
* with the action {@link MotionEvent#ACTION_CANCEL}, and all further
* events will be delivered to your onTouchEvent() method and no longer
* appear here.
*
* 如果你返回true,你将不会收到事件流中接下来的其他事件,这个目标的子view将会收到action为
* action_cancle的事件,并且接下来的所有事件都将分发给你的ontouchevent方法,不会再传递到这个方
* 法里面
* </ol>
*
* @param ev The motion event being dispatched down the hierarchy.
* @return Return true to steal motion events from the children and have
* them dispatched to this ViewGroup through onTouchEvent().
* The current target will receive an ACTION_CANCEL event, and no further
* messages will be delivered here.
*/
public boolean onInterceptTouchEvent(MotionEvent ev) {
return false;
}