android Touch事件傳遞小結

此次仍是先貼上測試代碼吧。。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,基本的規則是:

  1. down事件首先會傳遞到onInterceptTouchEvent()方法
  2. 若是該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成以後return false,那麼後續的move, up等事件將繼續會先傳遞給該ViewGroup,以後才和down事件同樣傳遞給最終的目標view的onTouchEvent()處理。
  3. 若是該ViewGroup的onInterceptTouchEvent()在接收到down事件處理完成以後return true,那麼後續的move, up等事件將再也不傳遞給onInterceptTouchEvent(),而是和down事件同樣傳遞給該ViewGroup的onTouchEvent()處理,注意,目標view將接收不到任何事件。
  4. 若是最終須要處理事件的view的onTouchEvent()返回了false,那麼該事件將被傳遞至其上一層次的view的onTouchEvent()處理。
  5. 若是最終須要處理事件的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;

相關文章
相關標籤/搜索