首页 > 代码库 > android Touch事件传递小结

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 already117                 // 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 (canceled149                     || actionMasked == MotionEvent.ACTION_UP150                     || 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;