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,基本的规则是:

  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;

 

 

posted @ 2014-06-27 14:43  沙发土豆  阅读(276)  评论(0编辑  收藏  举报