[置顶] Android Touch 事件传递机制
转载请注明出处:http://blog.csdn.net/fishle123/article/details/50638795
在AndroidUI开发中,经常涉及touch(触摸)事件和手势。最经常使用的点击事件(OnClickListener)也与touch事件相关。因此,理解touch事件在View层级中的传递机制尤为重要。然而,dispatchTouchEvent、onInterceptTouchEvent、onTouchEvent等一系列接口方法很容易让人混淆。
本文将介绍touch事件的一些基础知识,并通过分析AndroidFrameWork源码来深入理解touch事件的分发机制。
一、MotionEvent与事件分发相关的方法
1.MotionEvent
MotionEvent类封装了一个Touch事件的相关参数,我们通常所说的一个Touch事件,就是通过一个MotionEvent类的实例来描述。一个MotionEvent可以分为多种类型,即ACTION_DOWN(按下)、ACTION_MOVE(移动)、ACTION_UP(抬起)和ACTION_CANCEL(取消)等。
1)ACTION_DOWN:
一般来说,通常的Touch事件触发的流程都是DOWN-->UP,或者DOWN-->MOVE-->...-->MOVE-->UP。所以ACTION_DOWN事件通常都是一系列连续操作事件的起点,MOVE事件是否能够被触发取决于操作手势里面是否包含了移动的动作。
2)ACTION_MOVE:
当手指按下后在屏幕上移动时,就会产生ACTION_MOVE事件,并且通常会随着手指移动而连续产生很多个。
3)ACTION_UP:
UP是一系列手势操作的结束点,程序会在收到ACTION_UP事件时做一些收尾性的工作,例如恢复View的点击状态,值得一提的是,View的onClick方法就是在ACTION_UP时加以判断满足其他条件之后被触发的。
4)ACTION_CANCEL:
CANCEL事件不是由用户触发的,而是系统经过逻辑判断后对某个View发送“取消”消息时产生的。收到CANCEL事件时,View应该负责将自己的状态恢复。
2.事件分发方法
publicbooleandispatchTouchEvent(MotionEventev):
事件由上一层的View传递到下一层View的过程称为事件分发。dispatchTouchEvent方法负责事件分发。Activity、ViewGroup、View类中都定义了该方法,所以它们都具有事件分发的能力。
Activity.dispatchTouchEvent实际上是调用了DecorView的dispatchTouchEvent方法,而DecorView实际上是一个FrameLayout,因此,Activity的dispatchTouchEvent最终也是调用到了ViewGroup的dispatchTouchEvent方法。
另外,由于View没有管理子View的能力,所以View.dispatchTouchEvent方法实际上不是用来向下分发事件,而是将事件分发给自己,调用了自己的事件响应方法去响应事件。
3.事件拦截
publicbooleanonInterceptTouchEvent(MotonEventevent)
事件在ViewGroup的分发过程中,ViewGroup可以决定是否拦截事件而不对子View分发。该方法的返回值决定是否需要拦截的,返回true表示拦截,false表示不拦截。该方法只定义在ViewGroup类中.
4.事件响应方法
publicbooleanonTouchEvent(MotionEventevent):
该方法负责响应事件,并且返回一个boolean型,表示是否消费掉事件,返回true表示消费,false表示不消费。Activity、View、ViewGroup都有这个方法,所以它们都具有事件响应的能力,并且通过返回值来表示事件是否已经消费。如果子View没有消费掉Touch事件,那么会传递给上一层的View来处理。
消息分发流程,从上到下,从父到子:Activity->ViewGroup1->ViewGroup1的子ViewGroup2->…->TargetView
消息响应流程,从下到上,从子到父:TargetView->…->ViewGroup1的子ViewGroup2->ViewGroup1->Activity
二、View中Touch事件的分发逻辑
先来看一下View.dispatchTouchEvent的源码:
/** * Pass the touch screen motion event down to the target view, or this * view if it is the target. * * @param event The motion event to be dispatched. * @return True if the event was handled by the view, false otherwise. */ public boolean dispatchTouchEvent(MotionEvent event) { boolean result = false; if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(event, 0); } final int actionMasked = event.getActionMasked(); if (actionMasked == MotionEvent.ACTION_DOWN) { // Defensive cleanup for new gesture stopNestedScroll(); } if (onFilterTouchEventForSecurity(event)) { //noinspection SimplifiableIfStatement ListenerInfo li = mListenerInfo; if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED && li.mOnTouchListener.onTouch(this, event)) { //只要view设置了OnTouchListerner并且enable了,那么就调用OnTouchListener.onTouch来处理 result = true; } //如果view没有设置OnTouchListener或者OnTouchListener.onTouch返回false(表明没有消费该Touch事件),那么调用View的onTouchEvent来处理 if (!result && onTouchEvent(event)) { result = true; } } if (!result && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(event, 0); } ...... return result; }
通过上面的注释可以看到:View的事件分发过程主要涉及两个方法:mOnTouchListener.onTouch和onTouchEvent,并且当mOnTouchListener存在时,mOnTouchListener.onTouch调用的优先级比较高。
而mOnTouchListener可以通过View的setOnTouchListener(OnTouchListenerl)方法中来设置。当我们通过setOnTouchListener(OnTouchListenerl)方法设置了onClickListener,并在onClickListener.onTouch方法中返回true消费了事件之后,onTouchEvent将不会再被调用。
mOnTouchListener.onTouch是由外部设置到View里去的,而onTouchEvent只能通过Override去重写自己的逻辑,且View的onTouchEvent方法自身已经有不少逻辑。所以mOnTouchListener.onTouch更适合添加一些不太复杂的touch逻辑,并且可以不妨碍onTouchEvent的正常调用;而onTouchEvent更适用于用Override的形式来改变View本身touch逻辑。
三、ViewGroup中Touch事件的分发逻辑
虽然ViewGroup是View的子类,但是因为ViewGroup涉及对子View的处理,所以其事件分发逻辑比View的分发逻辑会复杂很多。
看ViewGroup.dispatchTouchEvent的源码:
</pre><pre name="code" class="java">@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) { if (mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onTouchEvent(ev, 1); } // If the event targets the accessibility focused view and this is it, start // normal event dispatch. Maybe a descendant is what will handle the click. if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) { ev.setTargetAccessibilityFocus(false); } boolean handled = false; if (onFilterTouchEventForSecurity(ev)) { final int action = ev.getAction(); final int actionMasked = action & MotionEvent.ACTION_MASK; //处理初始的ACTION_DOWN if (actionMasked == MotionEvent.ACTION_DOWN) { //清除之前的手势 cancelAndClearTouchTargets(ev); resetTouchState();//核心操作mFirstTouchTarget=null } //检查是否拦截touch事件 final boolean intercepted; /*是ACTION_DOWN事件或者mFirstTouchTarget!=null(已经找到事件的接收者),如果已经拦截了ACTION_DOWN事件,那么mFisrtTouchTarget必然为null,后续的Touch事件将直接由当前ViewGroup处理,不会再询问是否拦截*/ if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) { final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0; //判断是否允许拦截,因为requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法可以禁止执行是否需要拦截的判断 if (!disallowIntercept) { // 禁止拦截的FLAG为false,说明可以执行拦截判断,则执行此ViewGroup的onInterceptTouchEvent方法 intercepted = onInterceptTouchEvent(ev);// 该方法默认返回false,如果想修改默认的行为,需要override此方法,修改返回值。 ev.setAction(action); // restore action in case it was changed } else { // 禁止拦截的FLAG为ture,说明没有必要去执行是否需要拦截了,这个事件是无法拦截的,所以设置拦截变量为false intercepted = false; } } else { // 当不是ACTION_DOWN事件并且mFirstTouchTarget为null(意味着没有touch的目标组件)时,这个ViewGroup应该继续执行拦截的操作。 intercepted = true; } // If intercepted, start normal event dispatch. Also if there is already // a view that is handling the gesture, do normal event dispatch. if (intercepted || mFirstTouchTarget != null) { ev.setTargetAccessibilityFocus(false); } // Check for cancelation. //检查是否取消当前ACTION的处理,如果resetCancelNextUpFlag返回为true表明取消,或者当前ACTION为ACTION_CANCEL final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL; // Update list of touch targets for pointer down, if needed. final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0; TouchTarget newTouchTarget = null; boolean alreadyDispatchedToNewTouchTarget = false; if (!canceled && !intercepted) { //非取消事件 并且不需要拦截事件,分发给子View处理 View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus() ? findChildWithAccessibilityFocus() : null; // 在ACTION_DOWN时去寻找这次DOWN事件新出现的TouchTarget if (actionMasked == MotionEvent.ACTION_DOWN || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN) || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { final int actionIndex = ev.getActionIndex(); // always 0 for down ...... final int childrenCount = mChildrenCount; if (newTouchTarget == null && childrenCount != 0) { // 根据触摸的坐标查找能够接收这个事件的子View final float x = ev.getX(actionIndex); final float y = ev.getY(actionIndex); // Find a child that can receive the event. // Scan children from front to back. final ArrayList<View> preorderedList = buildOrderedChildList(); final boolean customOrder = preorderedList == null && isChildrenDrawingOrderEnabled(); final View[] children = mChildren; //遍历所有的child for (int i = childrenCount - 1; i >= 0; i--) { final int childIndex = customOrder ? getChildDrawingOrder(childrenCount, i) : i; final View child = (preorderedList == null) ? children[childIndex] : preorderedList.get(childIndex); // If there is a view that has accessibility focus we want it // to get the event first and if not handled we will perform a // normal dispatch. We may do a double iteration but this is // safer given the timeframe. if (childWithAccessibilityFocus != null) { if (childWithAccessibilityFocus != child) { continue; } childWithAccessibilityFocus = null; i = childrenCount - 1; } // 寻找可接收这个事件,且组件区域内包含点击坐标的子View if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) { ev.setTargetAccessibilityFocus(false); continue; } // 找到了可以接收事件的子组件,赋值给newTouchTarget newTouchTarget = getTouchTarget(child); if (newTouchTarget != null) { // Child is already receiving touch within its bounds. // Give it the new pointer in addition to the ones it is handling. newTouchTarget.pointerIdBits |= idBitsToAssign; break; } resetCancelNextUpFlag(child); if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) { /*把事件传递给child处理,并且 子View消费了这个ACTION_DOWN的touch事件,就把子View添加到TouchTarget链表的最前面;反之,如果子View没有消费这个touch事件(即子View的dispatchTouchEvent返回false),那么子View就不会添加到TouchTarget中,后续的Touch事件也都不会再分发给该子View了*/ mLastTouchDownTime = ev.getDownTime(); ...... mLastTouchDownX = ev.getX(); mLastTouchDownY = ev.getY(); //将child添加到TouchTarget链表中(在表头插入),并为mFirstTouchTarget赋值为newTouchTarget,此子View成为新的touch事件的起点 newTouchTarget = addTouchTarget(child, idBitsToAssign); alreadyDispatchedToNewTouchTarget = true; break;//找到一个target之后就跳出循环 } // The accessibility focus didn't handle the event, so clear // the flag and do a normal dispatch to all children. ev.setTargetAccessibilityFocus(false); } if (preorderedList != null) preorderedList.clear(); } ...... } }/* 到目前为止,在 (不拦截 && 不取消 && 是 DOWN 事件) 的前提下,已经在子 View 中寻找过一次事件的响应者。 如果有子 View 消费了事件,那么事件已经通过 dispatchTransformedTouchEvent 方法分发到了该子 View 中,并且 alreadyDispatchedToNewTouchTarget = true,并且将响应者记录在局部变量 newTouchTarget 和 成员变量 mFirstTouchTarget 链表中。*/ /* 如果子View无法消费该touch事件,或者需要拦截touch事件(如果ACTION_DOWN被拦截,那么mFirstTouchTarget =null,后续的touch事件都需要拦截)。这里就作为普通的View处理,调用View.dispatchTouchEvent处理(间接触发onTouchEvent方法)*/ // Dispatch to touch targets. if (mFirstTouchTarget == null) { // No touch targets so treat this as an ordinary view. handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS); } else { //两种情形会进入else(1.非ACTION_DOWN事件;2.不需要拦截,且为ACTION_DOWN--这种情形在上面已经处理过,下面需要判断一下,避免重复分发该事件) //mFirstTouchTarget!=null,找到了能够消费touch事件的子组件,将touch事件传递到子View来处理 TouchTarget predecessor = null; TouchTarget target = mFirstTouchTarget; while (target != null) { final TouchTarget next = target.next; /*如果前面利用ACTION_DOWN事件寻找符合接收条件的子View的同时消费掉了ACTION_DOWN事件,这里直接返回true ,这里不会重复分发该事件 --上面提到的第2种情形 */ if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) { handled = true; } else {/*处理上面提到的第1种情形:不是ACTION_DOWN事件,继续传递给target子View进行处理,符合某些条件的话,会传递ACTION_CANCEL给target子组件 判断条件是:如果ACTION_DOWN时没有被拦截(这时候mFirstTouchTarget !=null,已经找到了事件的接收者),而后面的touch事件被拦截(intercepted为true),则需要发送ACTION_CANCEL给target子组件,反之,直接把该事件传递给子View,这里也可以把ATION_CANCEL拦截下来*/ final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted; // 对于非ACTION_DOWN事件,则继续传递给目标子组件进行处理 if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) { handled = true; } if (cancelChild) { /*/如果ACTION_DOWN没有拦截,但是后续事件被拦截了,将ACTION_CANCEL传递给子View后,还需要把子View从TouchTarget链表中删除,后续的Touch事件将不会再分发给这个子View,这步之后TouchTarget最后为空表,mFirstTouchTarget(TouchTarget第一个节点)为null,后续的Touch事件全部由当前ViewGroup处理*/ if (predecessor == null) { mFirstTouchTarget = next; } else { predecessor.next = next; } target.recycle(); target = next; continue; } } predecessor = target; target = next; } } // 如果是ACTION_CANCEL或者ACTION_UP,重置Touch状态标识,mFirstTouchTarget赋值为null,后面的Touch事件都无法派发给子View if (canceled || actionMasked == MotionEvent.ACTION_UP || actionMasked == MotionEvent.ACTION_HOVER_MOVE) { resetTouchState(); } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) { final int actionIndex = ev.getActionIndex(); final int idBitsToRemove = 1 << ev.getPointerId(actionIndex); removePointersFromTouchTargets(idBitsToRemove); } } if (!handled && mInputEventConsistencyVerifier != null) { mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1); } return handled;}
这里希望大家仔细阅读一下上面对源码的注释,通过这些注释可以很好的帮助我们理解ViewGroup的事件分发逻辑,几乎所有与ViewGroup事件分发相关的结论都可以从上面的注释中找到根据。
如果大家有仔细读过上面的代码和注释,就会发现ViewGroup的dispatchTouchEvent处理逻辑主要分为以下4个步骤:
1)如果是DOWN事件,则清理之前的变量和状态
2)检查是否拦截该Touch事件
3)分发DOWN事件或其他初始事件
4)将事件分发到touchTarget中或分发到ViewGroup自己身上。
mFirstTouchTarget变量在什么时候赋值,它的作用是什么?
mFirstTouchTarget是用来记录在DOWN事件中消费了事件的子View,它以链表的形式保存,mFirstTouchTarget指向链表的第一个节点。在DOWN事件中,如果通过点击的坐标找到了某个子View,且该子View消费了事件,那么就将这个子View插入到TouchTarget链表中,具体地,在调用addTouchTarget(child,idBitsToAssign)的时候赋值。这样在后续的MOVE、UP事件中,能直接根据这个链表,将事件分发给目标子View,不需再重复遍历子View去查找事件的接收者。
ViewGroup在哪些情况下可以拦截事件?
是否拦截是由onInterceptTouchEvent方法的返回值决定的。假设该ViewGroup没有被设置为不允许拦截(即默认情况下),那么对于DOWN事件,onInterceptTouchEvent方法肯定会被调用。如果是ACTION_MOVE、ACTION_UP或其他事件类型,只要满足mFirstTouchTarget!=null时也会调用onInterceptTouchEvent。
onInterceptTouchEvent方法对不同类型的事件进行拦截,会有哪些影响?
对分发的事件进行拦截,注意拦截ACION_DOWN与其他ACTION的差异。这里分两种情况讨论:
1)如果ACTION_DOWN的事件没有被拦截,顺利找到了TargetView,那么后续的MOVE与UP都能够下发。如果后续的MOVE与UP下发时还有继续拦截的话,事件只能传递到拦截层,并且发出ACTION_CANCEL给子View处理(不会传递到当前View(或Activity)的onTouchEvent),再后面的Touch事件也都不会再分发给子View了,极端情况mFirstTouchTarget为null,所有事件都由当前的ViewGroup的onTouchEvent来处理。
2)如果ACITON_DOWN的事件下发时被拦截,导致没有找到TargetView,那么后续的MOVE与UP都无法向下派发了,在当前View(也可能是Activity)这一层就终止了传递,直接由当前View的onTouchEvent来处理。后续的MOVE、UP事件也不会再询问是否需要拦截,而是直接分发到当前ViewGroup的onTouchEvent方法去处理。
因此,DOWN事件在ViewGroup的事件拦截、分发过程中是一个特殊的角色,对其处理的结果将直接影响后续事件的分发流程。
四、Activity中Touch事件的分发逻辑
了解完View和ViewGroup的事件分发逻辑后,再来看Activity的分发逻辑就简单多了。
看Activity.dispatchTouchEvent的源码:
/** * Called to process touch screen events. You can override this to * intercept all touch screen events before they are dispatched to the * window. Be sure to call this implementation for touch screen events * that should be handled normally. * * @param ev The touch screen event. * * @return boolean Return true if this event was consumed. */ public boolean dispatchTouchEvent(MotionEvent ev) { if (ev.getAction() == MotionEvent.ACTION_DOWN) { onUserInteraction(); } if (getWindow().superDispatchTouchEvent(ev)) { return true; } return onTouchEvent(ev); }
在Actvity的dispatchTouchEvent中,先尝试调用window.superDispatchTouchEvent方法,返回false时才调用onTouchEvent方法。而window.superDispatchTouchEvent方法,实际上是调用了Window的DecorView的dispatchTouchEvent方法,由于DecorView是FrameLayout的子类,也就是一个ViewGroup,所以Activity.dispatchTouchEvent方法最终也是调用了ViewGroup.dispatchTouchEvent方法。
至此为止,我们将View、ViewGroup、Activity的事件分发流程都了解完了。可以想象,当用户触发了一个触摸事件,Android系统会将其传递到当前触摸的Activity.dispatchTouchEvent方法中,接着,就由Activity、ViewGroup、View的dispatchTouchEvent方法把事件传递给某个目标View,然后再逐层返回。
五、例子
最后,我们再通过一个例子来回顾一下整个分发过程。
下面的例子中有一个Activity,在Activity中有一个MyViewGroup,在MyViewGroup中有一个MyButton。然后模拟不同的事件分发情形,并观察打印的log来验证上文对源码中Touch事件分发机制的分析。下面贴出部分代码
Activity源码:
public class MainActivity extends Activity {private static final String TAG = MainActivity.class.getSimpleName();@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);}@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubswitch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "dispatchTouchEvent:ACTION_DOWN");// return true;break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "dispatchTouchEvent:ACTION_MOVE");break;// break;case MotionEvent.ACTION_UP:Log.i(TAG, "dispatchTouchEvent:ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "dispatchTouchEvent:ACTION_CANCEL");break;default:Log.i(TAG, "dispatchTouchEvent:other");}return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "onTouchEvent:ACTION_DOWN");// return true;break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onTouchEvent:ACTION_MOVE");break;case MotionEvent.ACTION_UP:Log.i(TAG, "onTouchEvent:ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "onTouchEventt:ACTION_CANCEL");break;default:break;}return super.onTouchEvent(event);}}MyViewGroup源码:
public class MyViewGroup extends FrameLayout {private static final String TAG = MyViewGroup.class.getSimpleName();......@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubswitch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "dispatchTouchEvent:ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "dispatchTouchEvent:ACTION_MOVE");break;case MotionEvent.ACTION_UP:Log.i(TAG, "dispatchTouchEvent:ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "dispatchTouchEvent:ACTION_CANCEL");break;default:Log.i(TAG, "dispatchTouchEvent:other");}//return true;return super.dispatchTouchEvent(ev);}@Overridepublic boolean onTouchEvent(MotionEvent event) {// TODO Auto-generated method stubswitch (event.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "onTouchEvent:ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onTouchEvent:ACTION_MOVE");break;case MotionEvent.ACTION_UP:Log.i(TAG, "onTouchEvent:ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "onTouchEventt:ACTION_CANCEL");break;default:Log.i(TAG, "dispatchTouchEvent:other");}//return true;return super.onTouchEvent(event);}@Overridepublic boolean onInterceptTouchEvent(MotionEvent ev) {// TODO Auto-generated method stubswitch (ev.getAction()) {case MotionEvent.ACTION_DOWN:Log.i(TAG, "onInterceptTouchEvent:ACTION_DOWN");break;case MotionEvent.ACTION_MOVE:Log.i(TAG, "onInterceptTouchEvent:ACTION_MOVE");break;// return true;case MotionEvent.ACTION_UP:Log.i(TAG, "onInterceptTouchEvent:ACTION_UP");break;case MotionEvent.ACTION_CANCEL:Log.i(TAG, "onInterceptTouchEvent:ACTION_CANCEL");break;default:break;}return false;// return super.onInterceptTouchEvent(ev);}}
R.layout.activity_main布局文件
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".MainActivity" > <com.example.toucheventdispatchsample.MyViewGroup android:layout_width="match_parent" android:layout_height="match_parent"> <com.example.toucheventdispatchsample.MyButton android:layout_width="match_parent" android:layout_height="wrap_content" android:text="myButton"/> </com.example.toucheventdispatchsample.MyViewGroup> </LinearLayout>
下面通过修改dispatchTouchEvent,oninterceptTouchEvent,onTouchEvent的返回值,来模拟几个典型的情形:
1)MyButton消费所有事件,MyViewGroup不拦截任何事件
2)在MyButton的onTouchEvent中返回false,MyViewGroup也不消费任何事件,后续的touch事件直接由Activity处理了
3)MyViewGroup拦截了ACTION_DOWN事件,但是不消费任何事件,因此ACTION_DOWN由MyViewGroup处理,由于MyViewGroup并没有消费任何事件(onTouchEvent中返回了false),所以后续事件直接交给Activity处理
4)ViewGroup拦截ACTION_MOVE,且在自己的onTouchEvent中针对ACTION_MOVE和ACTION_UP返回true,MyButton接收到ACTION_CANCEL后,后续的touch事件由MyViewGroup处理
5)MyViewGroup在dispatchTouchEvent中直接返回true,那么Touch事件将不会任何被处理。在dispatchTouchEvent中返回true,上层的Activity就会任何Touch事件已经被下层的某个View处理了,但实际上并不会调用onTouchEvent。
到此为止,针对Android中Touch事件的分发机制的分析就结束了。理解有误的地方,欢迎讨论。
更多相关文章
- Android的Activity的launchMode与onActivityResult方法的关系
- Android DrawerLayout和NavigationView 的使用方法
- Android编译本地C++程序方法
- Android开发者网站打不开的解决方法
- android sdk manager 无法更新解决方法
- Android下app生成coredump方法
- 详细讲解下Hook技术,以Hook点击事件来示范
- Android使用webview调用js方法传参,参数无法传入的问题
- android 事件派发流程详解