转载请注明出处: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不拦截任何事件

[置顶] Android Touch 事件传递机制_第1张图片

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处理

[置顶] Android Touch 事件传递机制_第2张图片

5)MyViewGroup在dispatchTouchEvent中直接返回true,那么Touch事件将不会任何被处理。在dispatchTouchEvent中返回true,上层的Activity就会任何Touch事件已经被下层的某个View处理了,但实际上并不会调用onTouchEvent。

[置顶] Android Touch 事件传递机制_第3张图片

到此为止,针对AndroidTouch事件的分发机制的分析就结束了。理解有误的地方,欢迎讨论。


更多相关文章

  1. Android的Activity的launchMode与onActivityResult方法的关系
  2. Android DrawerLayout和NavigationView 的使用方法
  3. Android编译本地C++程序方法
  4. Android开发者网站打不开的解决方法
  5. android sdk manager 无法更新解决方法
  6. Android下app生成coredump方法
  7. 详细讲解下Hook技术,以Hook点击事件来示范
  8. Android使用webview调用js方法传参,参数无法传入的问题
  9. android 事件派发流程详解

随机推荐

  1. Android 动态获取文本宽度
  2. 【移动安全实战篇】————5、Android屏
  3. Android webview should overrideUrlLoad
  4. android lrucache使用
  5. Android通讯
  6. 状态开关按钮ToggleButton
  7. android 发送短信
  8. AnimatedStateListDrawable介绍
  9. Android之列表对话框
  10. Android图片旋转实例