此次仍是先貼上測試代碼吧。。android
主佈局文件是個三層結構,最外層和中間層都是LinearLayout的子類,裏層是個TextView:app
<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:ide
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什麼也沒寫,就不貼出來了。this
android的事件傳遞,就是dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent這三個傢伙糾纏不清,今天就來看看它們的關係。spa
1.dispatchTouchEvent.net
首先先說下dispatchTouchEvent,字面意思就是分發Touch事件。我最先看的關於Touch事件傳遞的一篇博文中(http://mobile.51cto.com/abased-374715.htm),這樣寫道:3d
當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 的話,這個事件就會「消失」,並且接收不到下一次事件。rest
我表示紅色部分對我誤導很大啊。來看看源碼:
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,基本的規則是:
首先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;