背景知识:
- 触摸屏可以有多个触控点
- android中管理触控点通过一个数组来管理,涉及到index和id两个变量,
index表示在数组中的下标,id表示这个触控点(pointer)的id,pointer对应的index子不同的MotionEvent中是可以变化的,
但是它的id是不会变的。
在不同的控件类型上,touch事件的传递方式会不一样。
- 普通View的touch事件处理过程:
1.view消耗touch事件的地方要有两个一个是OnTouchListener,另一个则是onTouchEvent,OnTouchListener 比onTouchEvent优先级高
2.只要view是CLICKABLE或则LONG_CLICKABLE 或者CONTEXT_CLICKABLE ,无论是否 disable,它在onTouchEvent阶段默认都会消耗这个事件
- ViewGroup的touch事件处理思路:
ViewGroup的touch事件下面几个方面是要考虑的
1.一般的事件序列的过程是ACTION_DOWN,ACTION_MOVE.................,最后ACTION_UP
2.确定是否是ACTION_DOWN事件
3.确定是否intercept
4.确定是否要cancel
源代码代码中mFirstTouchTarget 变量很关键,它保存着处理当前事件序列的view,在ACTION_DOWN的时候确定将新的view加入到这个列表中来,
当在ACTION_MOVE过程中确定当前ViewGroup需要intercept了,那么应该清理掉mFirstTouchTarget中所有的view,因为这个事件序列之后的的event都不会派发给这些view了。
总之,ViewGroup处理touch时间的需要遵守下面几条
1.事件是从父view传递到子view的,如果子view不处理,那么父view再来处理
2.如果ViewGroup的某个子view处理了ACTION_DOWN或则 ACTION_POINTER_DOWN(View.OnTouchEvent默认是返回ture的,也就是处理该事件序列的), 那么这个事件序列后续的事件都传给这个view处理,除非View.dispatchTouchEvent返回false或者 当前ViewGroup确定需要intercept了。
3.ViewGroup的onInterceptTouchEvent 这个函数有机会在将事件传递给子view之前获得调用的机会,以确定是否需要intercept
下面是对ViewGroup.dispatchTouchEvent代码一段分析
public boolean dispatchTouchEvent(MotionEvent ev) {if (mInputEventConsistencyVerifier != null) {mInputEventConsistencyVerifier.onTouchEvent(ev, 1);}// If the event targets the accessibility focused view and this is it, start// normal event dispatch. Maybe a descendant is what will handle the click.//ev 是否设置 FLAG_TARGET_ACCESSIBILITY_FOCUS这个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;// Handle an initial down.if (actionMasked == MotionEvent.ACTION_DOWN) {// Throw away all previous state when starting a new touch gesture.// The framework may have dropped the up or cancel event for the previous gesture// due to an app switch, ANR, or some other state change./** ACTION_DOWN重新设置状态,给mFirstTouchTarget中的target发生cancel事件,* 清空mFirstTouchTarget列表* 清空mGroupFlags的FLAG_DISALLOW_INTERCEPT标志* mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT; */cancelAndClearTouchTargets(ev);resetTouchState();}// Check for interception.final boolean intercepted;/*在 ACTION_DOWN 和 mFirstTouchTarget不为空的时候就会去intercept* 在ACTION_MOVE事件的时候,虽然有子view处理事件序列了,* 但是viewgroup还是有机会插手事件序列* */ if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {//是否不允许interceptfinal boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;if (!disallowIntercept) {//允许interceptintercepted = 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;}// If intercepted, start normal event dispatch. Also if there is already// a view that is handling the gesture, do normal event dispatch.if (intercepted || mFirstTouchTarget != null) {//取消FLAG_TARGET_ACCESSIBILITY_FOCUSev.setTargetAccessibilityFocus(false);}// Check for cancelation.//取消PFLAG_CANCEL_NEXT_UP_EVENT标记,返回之前是否已经 PFLAG_CANCEL_NEXT_UP_EVENTfinal boolean canceled = resetCancelNextUpFlag(this)|| actionMasked == MotionEvent.ACTION_CANCEL;// Update list of touch targets for pointer down, if needed.final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;TouchTarget newTouchTarget = null;boolean alreadyDispatchedToNewTouchTarget = false;//canceled==false && interepted==falseif (!canceled && !intercepted) {//没有intercepted也没有cancel掉,转发到子view中去// If the event is targeting accessiiblity 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;//ACTION_POINTER_DOWN,会有多个触控点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//根据索引查找到在ev中idfinal 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.//根据Z-order得到拍好序的children view listfinal ArrayList<View> preorderedList = buildOrderedChildList();final boolean customOrder = preorderedList == null&& isChildrenDrawingOrderEnabled();final View[] children = mChildren;for (int i = childrenCount - 1; i >= 0; i--) {//child 在绘制次序中的次序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;}if (!canViewReceivePointerEvents(child)|| !isTransformedTouchPointInView(x, y, child, null)) {ev.setTargetAccessibilityFocus(false);continue;}//child是否已经在mFirstTouchTarget列表中,多点触控时,几个点都在一个view上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)) {//将事件投递到子view成功// Child wants to receive touch within its bounds.mLastTouchDownTime = ev.getDownTime();if (preorderedList != null) {// childIndex points into presorted list, find original indexfor (int j = 0; j < childrenCount; j++) {if (children[childIndex] == mChildren[j]) {mLastTouchDownIndex = j;break;}}} else {mLastTouchDownIndex = childIndex;}mLastTouchDownX = ev.getX();mLastTouchDownY = ev.getY();//加入到mFirstTouchTarget队头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();}//如果都没有没找到,那么用mFirstTouchTarget队列中尾巴上的那个target来处理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;}}}// Dispatch to touch targets.//如果是ACTION_MOVE事件,上面的这个if 是没有不会执行的,直接到这个if了if (mFirstTouchTarget == null) {/*这种状况会是 ACTION_MOVE事件,并且这个时候是 intercepted==true的状况*这个会转发到View.dispatchTouchEvent() ,这View类中会调用OnTouchListener,和onTouchEvent */// 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) {//新加入的 target,跳过handled = true;} else {/** 如果之前设置calcel标记或者intercepted了* 如果当前viewgroup 确定intercepted了,* 那么给mFirstTouchTarget中的所有view发送ACTION_MOVE事件,* 并且移除mFirstTouchTarget中所有的target* * 如果没有intercepted那么照原事件发送**/final boolean cancelChild = resetCancelNextUpFlag(target.child)|| intercepted;//如果intecepted了,那么会给之前的target发送一个cancel事件if (dispatchTransformedTouchEvent(ev, cancelChild,target.child, target.pointerIdBits)) {handled = true;}//移除这个targetif (cancelChild) {if (predecessor == null) {//第一个节点mFirstTouchTarget = next;} else {predecessor.next = next;}target.recycle();target = next;//这个continue很关键,它会保持predecessor==null的状况,最后会出现mFirstTouchTarget==null的状况continue;}}predecessor = target;target = next;}}// 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;}