android Touch事件传递小结
这次还是先贴上测试代码吧。。
主布局文件是个三层结构,最外层和中间层都是LinearLayout的子类,里层是个TextView:
<com.example.touchevent.OutterLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:id="@+id/outter_layout" android:layout_width="match_parent" android:layout_height="match_parent" tools:context="com.example.touchevent.MainActivity" tools:ignore="MergeRootFrame" android:orientation="vertical" > <com.example.touchevent.InnerLayout android:id="@+id/inner_layout" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="50dp" android:orientation="vertical" android:background="#ff0" android:layout_gravity="center" > <com.example.touchevent.MyTextView android:id="@+id/text_view" android:layout_width="match_parent" android:layout_height="match_parent" android:layout_margin="50dp" android:background="#0ff" android:layout_gravity="center" /> </com.example.touchevent.InnerLayout> </com.example.touchevent.OutterLayout>
外层的OutterLayout:
public class OutterLayout extends LinearLayout { private final String TAG = "OutterLayout"; public OutterLayout(Context context, AttributeSet attrs) { super(context, attrs); // TODO Auto-generated constructor stub } @Override public boolean dispatchTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub Log.d(TAG, "dispatchTouchEvent"); return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { // TODO Auto-generated method stub int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "onInterceptTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "onInterceptTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onInterceptTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "onInterceptTouchEvent action:ACTION_CANCEL"); break; } return false; } @Override public boolean onTouchEvent(MotionEvent event) { // TODO Auto-generated method stub int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "onTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "onTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "onTouchEvent action:ACTION_CANCEL"); break; } return true; } }
中间层的InnerLayout
public class InnerLayout extends LinearLayout{ private final String TAG = "InnerLayout"; public InnerLayout(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { return super.dispatchTouchEvent(ev); } @Override public boolean onInterceptTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: Log.d(TAG,"onInterceptTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG,"onInterceptTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG,"onInterceptTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG,"onInterceptTouchEvent action:ACTION_CANCEL"); break; } return false; } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: Log.d(TAG,"onTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG,"onTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG,"onTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG,"onTouchEvent action:ACTION_CANCEL"); break; } return false; } }
内层的TextView:
public class MyTextView extends TextView{ private final String TAG = "MyTextView"; public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); } @Override public boolean onTouchEvent(MotionEvent event) { int action = event.getAction(); switch(action){ case MotionEvent.ACTION_DOWN: Log.d(TAG,"onTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG,"onTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG,"onTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG,"onTouchEvent action:ACTION_CANCEL"); break; } return true; } }
MainActivity什么也没写,就不贴出来了。
android的事件传递,就是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent这三个家伙纠缠不清,今天就来看看它们的关系。
1.dispatchTouchEvent
首先先说下dispatchTouchEvent,字面意思就是分发Touch事件。我最早看的关于Touch事件传递的一篇博文中(http://mobile.51cto.com/abased-374715.htm),这样写道:
当TouchEvent发生时,首先Activity将TouchEvent传递给最顶层的View, TouchEvent最先到达最顶层 view 的 dispatchTouchEvent ,然后由 dispatchTouchEvent 方法进行分发,如果dispatchTouchEvent返回true ,则交给这个view的onTouchEvent处理,如果dispatchTouchEvent返回 false ,则交给这个 view 的 interceptTouchEvent 方法来决定是否要拦截这个事件,如果 interceptTouchEvent 返回 true ,也就是拦截掉了,则交给它的 onTouchEvent 来处理,如果 interceptTouchEvent 返回 false ,那么就传递给子 view ,由子 view 的 dispatchTouchEvent 再来开始这个事件的分发。如果事件传递到某一层的子 view 的 onTouchEvent 上了,这个方法返回了 false ,那么这个事件会从这个 view 往上传递,都是 onTouchEvent 来接收。而如果传递到最上面的 onTouchEvent 也返回 false 的话,这个事件就会“消失”,而且接收不到下一次事件。
我表示红色部分对我误导很大啊。来看看源码:
1 @Override 2 public boolean dispatchTouchEvent(MotionEvent ev) { 3 if (mInputEventConsistencyVerifier != null) { 4 mInputEventConsistencyVerifier.onTouchEvent(ev, 1); 5 } 6 7 boolean handled = false; 8 if (onFilterTouchEventForSecurity(ev)) { 9 final int action = ev.getAction(); 10 final int actionMasked = action & MotionEvent.ACTION_MASK; 11 12 // Handle an initial down. 13 if (actionMasked == MotionEvent.ACTION_DOWN) { 14 // Throw away all previous state when starting a new touch gesture. 15 // The framework may have dropped the up or cancel event for the previous gesture 16 // due to an app switch, ANR, or some other state change. 17 cancelAndClearTouchTargets(ev); 18 resetTouchState(); 19 } 20 21 // Check for interception. 22 final boolean intercepted; 23 if (actionMasked == MotionEvent.ACTION_DOWN 24 || mFirstTouchTarget != null) { 25 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 26 if (!disallowIntercept) { 27 intercepted = onInterceptTouchEvent(ev); 28 ev.setAction(action); // restore action in case it was changed 29 } else { 30 intercepted = false; 31 } 32 } else { 33 // There are no touch targets and this action is not an initial down 34 // so this view group continues to intercept touches. 35 intercepted = true; 36 } 37 38 // Check for cancelation. 39 final boolean canceled = resetCancelNextUpFlag(this) 40 || actionMasked == MotionEvent.ACTION_CANCEL; 41 42 // Update list of touch targets for pointer down, if needed. 43 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 44 TouchTarget newTouchTarget = null; 45 boolean alreadyDispatchedToNewTouchTarget = false; 46 if (!canceled && !intercepted) { 47 if (actionMasked == MotionEvent.ACTION_DOWN 48 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 49 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 50 final int actionIndex = ev.getActionIndex(); // always 0 for down 51 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) 52 : TouchTarget.ALL_POINTER_IDS; 53 54 // Clean up earlier touch targets for this pointer id in case they 55 // have become out of sync. 56 removePointersFromTouchTargets(idBitsToAssign); 57 58 final int childrenCount = mChildrenCount; 59 if (childrenCount != 0) { 60 // Find a child that can receive the event. 61 // Scan children from front to back. 62 final View[] children = mChildren; 63 final float x = ev.getX(actionIndex); 64 final float y = ev.getY(actionIndex); 65 66 final boolean customOrder = isChildrenDrawingOrderEnabled(); 67 for (int i = childrenCount - 1; i >= 0; i--) { 68 final int childIndex = customOrder ? 69 getChildDrawingOrder(childrenCount, i) : i; 70 final View child = children[childIndex]; 71 if (!canViewReceivePointerEvents(child) 72 || !isTransformedTouchPointInView(x, y, child, null)) { 73 continue; 74 } 75 76 newTouchTarget = getTouchTarget(child); 77 if (newTouchTarget != null) { 78 // Child is already receiving touch within its bounds. 79 // Give it the new pointer in addition to the ones it is handling. 80 newTouchTarget.pointerIdBits |= idBitsToAssign; 81 break; 82 } 83 84 resetCancelNextUpFlag(child); 85 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 86 // Child wants to receive touch within its bounds. 87 mLastTouchDownTime = ev.getDownTime(); 88 mLastTouchDownIndex = childIndex; 89 mLastTouchDownX = ev.getX(); 90 mLastTouchDownY = ev.getY(); 91 newTouchTarget = addTouchTarget(child, idBitsToAssign); 92 alreadyDispatchedToNewTouchTarget = true; 93 break; 94 } 95 } 96 } 97 98 if (newTouchTarget == null && mFirstTouchTarget != null) { 99 // Did not find a child to receive the event. 100 // Assign the pointer to the least recently added target. 101 newTouchTarget = mFirstTouchTarget; 102 while (newTouchTarget.next != null) { 103 newTouchTarget = newTouchTarget.next; 104 } 105 newTouchTarget.pointerIdBits |= idBitsToAssign; 106 } 107 } 108 } 109 110 // Dispatch to touch targets. 111 if (mFirstTouchTarget == null) { 112 // No touch targets so treat this as an ordinary view. 113 handled = dispatchTransformedTouchEvent(ev, canceled, null, 114 TouchTarget.ALL_POINTER_IDS); 115 } else { 116 // Dispatch to touch targets, excluding the new touch target if we already 117 // dispatched to it. Cancel touch targets if necessary. 118 TouchTarget predecessor = null; 119 TouchTarget target = mFirstTouchTarget; 120 while (target != null) { 121 final TouchTarget next = target.next; 122 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 123 handled = true; 124 } else { 125 final boolean cancelChild = resetCancelNextUpFlag(target.child) 126 || intercepted; 127 if (dispatchTransformedTouchEvent(ev, cancelChild, 128 target.child, target.pointerIdBits)) { 129 handled = true; 130 } 131 if (cancelChild) { 132 if (predecessor == null) { 133 mFirstTouchTarget = next; 134 } else { 135 predecessor.next = next; 136 } 137 target.recycle(); 138 target = next; 139 continue; 140 } 141 } 142 predecessor = target; 143 target = next; 144 } 145 } 146 147 // Update list of touch targets for pointer up or cancel, if needed. 148 if (canceled 149 || actionMasked == MotionEvent.ACTION_UP 150 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 151 resetTouchState(); 152 } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { 153 final int actionIndex = ev.getActionIndex(); 154 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); 155 removePointersFromTouchTargets(idBitsToRemove); 156 } 157 } 158 159 if (!handled && mInputEventConsistencyVerifier != null) { 160 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); 161 } 162 return handled; 163 }
略长,不用都看明白了主要有以下几个关键点:
1) 21行的注释在说:检查是否拦截;
2) 27行调用了onInterceptTouchEvent()方法,并把其返回结果赋给了Intercepted变量;
3) 46行再次看到了Intercepted变量,作为if()语句的判断条件。
也就是说,dispatchTouchEvent()方法中调用了onInterceptTouchEvent()方法,而且将其返回值保存,此时dispatchTouchEvent()还没有返回,所以并不是根据dispatchTouchEvent()方法的返回值来决定事件下一步怎么走。在本例中,如果在OutterLayout中重写dispatchTouchEvent()方法,直接返回true或false,而不是return super.dispatchTouchEvent(ev)的话,则根本没有调用onInterceptTouchEvent()方法,事件根本不会传递到InnerLayout和MyTextView中去,而且也没有触发OutterLayout的onTouchEvent()方法。在实际开发中,不建议重写dispatchTouchEvent()方法,就算重写也应该return super.dispatchTouchEvent(ev)。
2.onInterceptTouchEvent()
参照http://blog.csdn.net/ddna/article/details/5473293,基本的规则是:
- down事件首先会传递到onInterceptTouchEvent()方法
- 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return false,那么后续的move, up等事件将继续会先传递给该ViewGroup,之后才和down事件一样传递给最终的目标view的onTouchEvent()处理。
- 如果该ViewGroup的onInterceptTouchEvent()在接收到down事件处理完成之后return true,那么后续的move, up等事件将不再传递给onInterceptTouchEvent(),而是和down事件一样传递给该ViewGroup的onTouchEvent()处理,注意,目标view将接收不到任何事件。
- 如果最终需要处理事件的view的onTouchEvent()返回了false,那么该事件将被传递至其上一层次的view的onTouchEvent()处理。
- 如果最终需要处理事件的view 的onTouchEvent()返回了true,那么后续事件将可以继续传递给该view的onTouchEvent()处理。
首先onInterceptTouchEvent()是ViewGroup的方法,View是没有该方法的。再来看onInterceptTouchEvent()源码:
public boolean onInterceptTouchEvent(MotionEvent ev) { return false; }
默认返回false。那么修改测试程序(把dispatchTouchEvent()方法注释掉):
1)让OutterLayout的onInterceptTouchEvent()返回true(整体返回true或者ACTION_DOWN返回true是一样的),onTouchEvent()返回true,然后看Log:
2)让OutterLayout的onInterceptTouchEvent()返回false,onTouchEvent()返回true,InnerLayout的onInterceptTouchEvent()返回true,onTouchEvent()返回false,然后看Log:
3)让OutterLayout的onInterceptTouchEvent()返回false,onTouchEvent()返回true,InnerLayout的onInterceptTouchEvent()返回true,onTouchEvent()返回true,然后看Log:
4)让OutterLayout的onInterceptTouchEvent()返回false,onTouchEvent()返回true,InnerLayout的onInterceptTouchEvent()返回false,onTouchEvent()返回true,MyTextView的onTouchEvent()返回true,然后看Log:
我的理解是:OutterLayout的onInterceptTouchEvent()先收到Touch事件:
1.如果OutterLayout的onInterceptTouchEvent()返回true,表示拦截这次事件,则事件不再传递给InnerLayout和MyTextView,而是交由OutterLayout的onTouchEvent()处理:
1.1如果OutterLayout的onTouchEvent()返回true,表示事件被消费了,事件传播到此结束;
1.2如果OutterLayout的onTouchEvent()返回true,但因为OutterLayout是顶层布局,所以无法继续向上传播,事件就此消失。
2.如果OutterLayout的onInterceptTouchEvent()返回false,表示不拦截,事件将传递给InnerLayout的onInterceptTouchEvent()处理:
2.1如果InnerLayout的onInterceptTouchEvent()返回true,表示拦截这次事件,则事件不再传递给MyTextView,而是交由InnerLayout的onTouchEvent()处理:
2.1.1如果InnerLayout的onTouchEvent()返回true,表示事件被消费了,事件传播到此结束;
2.1.2如果InnerLayout的onTouchEvent()返回false,则事件继续传递到OutterLayout的onTouchEvent()处理——跳到1.1;
2.2如果InnerLayout的onInterceptTouchEvent()返回false,表示不拦截,则事件将传递给MyTextView的onTouchEvent()处理:
2.2.1如果MyTextView的onTouchEvent()返回true,表示事件被消费了,事件传播到此结束;
2.2.2如果MyTextView的onTouchEvent()返回false,则事件继续传递到InnerLayout的onTouchEvent()处理——跳到2.1.1;