android 触摸事件分析
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了android 触摸事件分析相关的知识,希望对你有一定的参考价值。
背景知识:
- 触摸屏可以有多个触控点
- 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) { //是否不允许intercept final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; if (!disallowIntercept) { //允许intercept intercepted = 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_FOCUS ev.setTargetAccessibilityFocus(false); } // Check for cancelation. //取消PFLAG_CANCEL_NEXT_UP_EVENT标记,返回之前是否已经 PFLAG_CANCEL_NEXT_UP_EVENT final 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==false if (!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中id final 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 list final 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 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(); //加入到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; } //移除这个target if (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; }
以上是关于android 触摸事件分析的主要内容,如果未能解决你的问题,请参考以下文章