以ontouch为例说明android事件发送机制
android里面和touch相关的方法最常见的有四个:onTouch,dispatchTouchEvent,onTouchEvent,如果是一个GroupView的话还有一个onInterceptTouchEvent。
这四个方法有什么关系?很多新手搞不明白,我在网上找了很多的资料,发现自己的研究结果与资料上的有出入。所以将自己的结论写出来,Android的事件传递机制到底是怎么样的,也可以由此一探究竟。
我这例子实在网上的例子改造的,原来文章的链接:http://www.blogjava.net/lzqdiy/archive/2011/05/08/349794.html
先贴出我的代码,在做仔细的分析
<?xml version="1.0" encoding="utf-8"?> <view android:layout_width="fill_parent" android:layout_height="fill_parent" class="com.example.AndroidTouchTest.MyLinearLayout" xmlns:android="http://schemas.android.com/apk/res/android" android:id="@+id/view"> <com.example.AndroidTouchTest.MyTextView android:layout_width="200px" android:layout_height="200px" android:id="@+id/tv" android:text="lzqdiy" android:textSize="40sp" android:textStyle="bold" android:background="#FFFFFF" android:textColor="#0000FF"/> </view>
package com.example.AndroidTouchTest; import android.app.Activity; import android.os.Bundle; public class MyActivity extends Activity { /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
package com.example.AndroidTouchTest; import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.LinearLayout; /** * Created with IntelliJ IDEA. * User: dothegod * Date: 3/24/13 * Time: 10:01 PM * To change this template use File | Settings | File Templates. */ public class MyLinearLayout extends LinearLayout { private final String TAG = "MyLinearLayout"; public MyLinearLayout(Context context, AttributeSet attrs) { super(context, attrs); Log.d(TAG, TAG); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "onTouch action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "onTouch action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onTouch action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "onTouch action:ACTION_CANCEL"); break; } boolean flag = false; Log.d(TAG, "onTouch action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } }); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "dispatchTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "dispatchTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "dispatchTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "dispatchTouchEvent action:ACTION_CANCEL"); break; } boolean flag = super.dispatchTouchEvent(ev); Log.d(TAG, "dispatchTouchEvent action: flag " + (flag == true? "true":"false")); // flag = true; // Log.d(TAG, "dispatchTouchEvent action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } @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; } boolean flag = super.onInterceptTouchEvent(ev); Log.d(TAG, "onInterceptTouchEvent action: flag " + (flag == true? "true":"false")); // flag = true; // Log.d(TAG, "onInterceptTouchEvent action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.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; } boolean flag = super.onTouchEvent(ev); Log.d(TAG, "---onTouchEvent action: flag " + (flag == true? "true":"false")); // flag = true; // Log.d(TAG, "---onTouchEvent action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } }
package com.example.AndroidTouchTest; /** * Created with IntelliJ IDEA. * User: dothegod * Date: 3/24/13 * Time: 10:02 PM * To change this template use File | Settings | File Templates. */ import android.content.Context; import android.util.AttributeSet; import android.util.Log; import android.view.MotionEvent; import android.view.View; import android.widget.TextView; public class MyTextView extends TextView { private final String TAG = "MyTextView"; public MyTextView(Context context, AttributeSet attrs) { super(context, attrs); setOnTouchListener(new OnTouchListener() { @Override public boolean onTouch(View v, MotionEvent event) { int action = event.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "onTouch action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "onTouch action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "onTouch action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "onTouch action:ACTION_CANCEL"); break; } boolean flag = false; Log.d(TAG, "onTouch action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } }); } @Override public boolean dispatchTouchEvent(MotionEvent ev) { int action = ev.getAction(); switch (action) { case MotionEvent.ACTION_DOWN: Log.d(TAG, "dispatchTouchEvent action:ACTION_DOWN"); break; case MotionEvent.ACTION_MOVE: Log.d(TAG, "dispatchTouchEvent action:ACTION_MOVE"); break; case MotionEvent.ACTION_UP: Log.d(TAG, "dispatchTouchEvent action:ACTION_UP"); break; case MotionEvent.ACTION_CANCEL: Log.d(TAG, "dispatchTouchEvent action:ACTION_CANCEL"); break; } boolean flag = super.dispatchTouchEvent(ev); Log.d(TAG, "dispatchTouchEvent action: flag " + (flag == true? "true":"false")); // flag = true; // Log.d(TAG, "onTouchEvent action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } @Override public boolean onTouchEvent(MotionEvent ev) { int action = ev.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; } boolean flag = super.onTouchEvent(ev); Log.d(TAG, "---onTouchEvent action: flag " + (flag == true? "true":"false")); // flag = true; // Log.d(TAG, "---onTouchEvent action: FORCE SET flag " + (flag == true? "true":"false")); return flag; } }
package com.example.AndroidTouchTest; import android.app.Activity; import android.os.Bundle; public class MyActivity extends Activity { /** * Called when the activity is first created. */ @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); } }
点击textiew的时候,会看到下面的打印:
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): dispatchTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onInterceptTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onInterceptTouchEvent action: flag false
03-23 17:22:23.545: DEBUG/MyTextView(2459): dispatchTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyTextView(2459): onTouch action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyTextView(2459): onTouch action: FORCE SET flag false
03-23 17:22:23.545: DEBUG/MyTextView(2459): ---onTouchEvent action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyTextView(2459): ---onTouchEvent action: flag false
03-23 17:22:23.545: DEBUG/MyTextView(2459): onTouchEvent action: flag false
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onTouch action:ACTION_DOWN
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): onTouch action: FORCE SET flag false
03-23 17:22:23.545: DEBUG/MyLinearLayout(2459): ---onTouchEvent action:ACTION_DOWN
03-23 17:22:23.554: DEBUG/MyLinearLayout(2459): ---onTouchEvent action: flag false
03-23 17:22:23.554: DEBUG/MyLinearLayout(2459): dispatchTouchEvent action: flag false
调用顺序整理如下:颜色表示控件,缩进表示调用关系。
MyLinearLayout(2459): dispatchTouchEvent
MyLinearLayout(2459): onInterceptTouchEvent
MyTextView(2459): dispatchTouchEvent action
MyTextView(2459): onTouch
MyTextView(2459): ---onTouchEvent
MyLinearLayout(2459): onTouch
MyLinearLayout(2459): ---onTouchEvent
MyLinearLayout(2459): dispatchTouchEven
这个是什么意思呢?
首先我们要分析下这几个函数的作用和返回值的意义
dispatchTouchEvent是用来分发事件的,控件的事件的分发都是通过这个函数来完成的。
onInterceptTouchEvent是判断是否截取事件,为什么要截取事件呢?因为控件重要有可能包含其他的控件,比如说,本例子代码中linearlayout中包含了一个textiew。他的作用就是决定这个事件是否需要传递給子控件。不过注意仅仅是GroupView的控件才有哦,有些控件,像textview就没法办法包含子控件的,所以它就没有这个方法。
ontouch是事件监听器的方法,只要有触摸的操作就是有这个方法。
onTouchEvent是触摸事件发生以后产生的处理。
他们都有一个boolean的返回值,false表示该事件需要继续传播,true表示该事件不用继续传播了。
每点击一次屏幕会有三个事件发生:ACTION_DOWN, ACTION_MOVE, ACTION_UP。 这里可以看到如果dispatchTouchEvent返回值是false的话,则不会再有后续的事件,后续的事件被丢弃掉了。
LinearLayout的调用函数图
LinearLayout的 dispatchTouchEvent代码
1 public boolean dispatchTouchEvent(MotionEvent ev) { 2 if (mInputEventConsistencyVerifier != null) { 3 mInputEventConsistencyVerifier.onTouchEvent(ev, 1); 4 } 5 6 boolean handled = false; 7 if (onFilterTouchEventForSecurity(ev)) { 8 final int action = ev.getAction(); 9 final int actionMasked = action & MotionEvent.ACTION_MASK; 10 11 // Handle an initial down. 12 if (actionMasked == MotionEvent.ACTION_DOWN) { 13 // Throw away all previous state when starting a new touch gesture. 14 // The framework may have dropped the up or cancel event for the previous gesture 15 // due to an app switch, ANR, or some other state change. 16 cancelAndClearTouchTargets(ev); 17 resetTouchState(); 18 } 19 20 // Check for interception. 21 final boolean intercepted; 22 if (actionMasked == MotionEvent.ACTION_DOWN 23 || mFirstTouchTarget != null) { 24 final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; 25 if (!disallowIntercept) { 26 intercepted = onInterceptTouchEvent(ev); 27 ev.setAction(action); // restore action in case it was changed 28 } else { 29 intercepted = false; 30 } 31 } else { 32 // There are no touch targets and this action is not an initial down 33 // so this view group continues to intercept touches. 34 intercepted = true; 35 } 36 37 // Check for cancelation. 38 final boolean canceled = resetCancelNextUpFlag(this) 39 || actionMasked == MotionEvent.ACTION_CANCEL; 40 41 // Update list of touch targets for pointer down, if needed. 42 final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; 43 TouchTarget newTouchTarget = null; 44 boolean alreadyDispatchedToNewTouchTarget = false; 45 if (!canceled && !intercepted) { 46 if (actionMasked == MotionEvent.ACTION_DOWN 47 || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) 48 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 49 final int actionIndex = ev.getActionIndex(); // always 0 for down 50 final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex) 51 : TouchTarget.ALL_POINTER_IDS; 52 53 // Clean up earlier touch targets for this pointer id in case they 54 // have become out of sync. 55 removePointersFromTouchTargets(idBitsToAssign); 56 57 final int childrenCount = mChildrenCount; 58 if (childrenCount != 0) { 59 // Find a child that can receive the event. 60 // Scan children from front to back. 61 final View[] children = mChildren; 62 final float x = ev.getX(actionIndex); 63 final float y = ev.getY(actionIndex); 64 65 final boolean customOrder = isChildrenDrawingOrderEnabled(); 66 for (int i = childrenCount - 1; i >= 0; i--) { 67 final int childIndex = customOrder ? 68 getChildDrawingOrder(childrenCount, i) : i; 69 final View child = children[childIndex]; 70 if (!canViewReceivePointerEvents(child) 71 || !isTransformedTouchPointInView(x, y, child, null)) { 72 continue; 73 } 74 75 newTouchTarget = getTouchTarget(child); 76 if (newTouchTarget != null) { 77 // Child is already receiving touch within its bounds. 78 // Give it the new pointer in addition to the ones it is handling. 79 newTouchTarget.pointerIdBits |= idBitsToAssign; 80 break; 81 } 82 83 resetCancelNextUpFlag(child); 84 if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { 85 // Child wants to receive touch within its bounds. 86 mLastTouchDownTime = ev.getDownTime(); 87 mLastTouchDownIndex = childIndex; 88 mLastTouchDownX = ev.getX(); 89 mLastTouchDownY = ev.getY(); 90 newTouchTarget = addTouchTarget(child, idBitsToAssign); 91 alreadyDispatchedToNewTouchTarget = true; 92 break; 93 } 94 } 95 } 96 97 if (newTouchTarget == null && mFirstTouchTarget != null) { 98 // Did not find a child to receive the event. 99 // Assign the pointer to the least recently added target. 100 newTouchTarget = mFirstTouchTarget; 101 while (newTouchTarget.next != null) { 102 newTouchTarget = newTouchTarget.next; 103 } 104 newTouchTarget.pointerIdBits |= idBitsToAssign; 105 } 106 } 107 } 108 109 // Dispatch to touch targets. 110 if (mFirstTouchTarget == null) { 111 // No touch targets so treat this as an ordinary view. 112 handled = dispatchTransformedTouchEvent(ev, canceled, null, 113 TouchTarget.ALL_POINTER_IDS); 114 } else { 115 // Dispatch to touch targets, excluding the new touch target if we already 116 // dispatched to it. Cancel touch targets if necessary. 117 TouchTarget predecessor = null; 118 TouchTarget target = mFirstTouchTarget; 119 while (target != null) { 120 final TouchTarget next = target.next; 121 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 122 handled = true; 123 } else { 124 final boolean cancelChild = resetCancelNextUpFlag(target.child) 125 || intercepted; 126 if (dispatchTransformedTouchEvent(ev, cancelChild, 127 target.child, target.pointerIdBits)) { 128 handled = true; 129 } 130 if (cancelChild) { 131 if (predecessor == null) { 132 mFirstTouchTarget = next; 133 } else { 134 predecessor.next = next; 135 } 136 target.recycle(); 137 target = next; 138 continue; 139 } 140 } 141 predecessor = target; 142 target = next; 143 } 144 } 145 146 // Update list of touch targets for pointer up or cancel, if needed. 147 if (canceled 148 || actionMasked == MotionEvent.ACTION_UP 149 || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { 150 resetTouchState(); 151 } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { 152 final int actionIndex = ev.getActionIndex(); 153 final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); 154 removePointersFromTouchTargets(idBitsToRemove); 155 } 156 } 157 158 if (!handled && mInputEventConsistencyVerifier != null) { 159 mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); 160 } 161 return handled; 162 }
Textiew的调用流程
1 public boolean dispatchTouchEvent(MotionEvent event) { 2 if (mInputEventConsistencyVerifier != null) { 3 mInputEventConsistencyVerifier.onTouchEvent(event, 0); 4 } 5 6 if (onFilterTouchEventForSecurity(event)) { 7 //noinspection SimplifiableIfStatement 8 ListenerInfo li = mListenerInfo; 9 if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED 10 && li.mOnTouchListener.onTouch(this, event)) { 11 return true; 12 } 13 14 if (onTouchEvent(event)) { 15 return true; 16 } 17 } 18 19 if (mInputEventConsistencyVerifier != null) { 20 mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); 21 } 22 return false; 23 }
可以看到TextView的dispatchTouchEvent先调用的Ontouch(9,10行)如果调用结果返回true则返回,如果是false则继续调用Ontouchevent。
正式基于这两种传输机制,事件得以在控件中不断传输。
那么事件是怎么通过父控件传入到子控件呢?
1 while (target != null) { 2 final TouchTarget next = target.next; 3 if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { 4 handled = true; 5 } else { 6 final boolean cancelChild = resetCancelNextUpFlag(target.child) 7 || intercepted; 8 if (dispatchTransformedTouchEvent(ev, cancelChild, 9 target.child, target.pointerIdBits)) { 10 handled = true;
这是在LinearLayout中的代码段,可以看到调用了dispatchTransformedTouchEvent方法,代码比较长,只给出一部分。
private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel, View child, int desiredPointerIdBits) { final boolean handled; // Canceling motions is a special case. We don't need to perform any transformations // or filtering. The important part is the action, not the contents. final int oldAction = event.getAction(); if (cancel || oldAction == MotionEvent.ACTION_CANCEL) { event.setAction(MotionEvent.ACTION_CANCEL); if (child == null) { handled = super.dispatchTouchEvent(event); } else { handled = child.dispatchTouchEvent(event); } event.setAction(oldAction); return handled; } …………
可以看到会寻找子控件,并且调用子控件的dispatchTouchEvent,如果没有就调用父类(不是父控件)的dispatchTouchEvent。而ViewGroup的父类就是View。
机制就是这样,不同的返回值会有不同的调用顺序,但是原理都是一样的。可以修改没有方法的返回值来查看代码的调用情况。
源代码地址:https://github.com/Dothegod/AndroidTouchTest/