当前位置: 代码迷 >> Android >> Android Touch事件的散发响应机制
  详细解决方案

Android Touch事件的散发响应机制

热度:46   发布时间:2016-04-27 22:23:16.0
Android Touch事件的分发响应机制

    前言:欢迎大家给我指出错误,一起进步。谢谢,也希望大家能认真看。    

    如果没有看Activity、View、Window之间关系的朋友,推荐先看一下,因为清楚了Activity与View之间的关系,对于触摸事件的传递理解起来也更容易!Activity、View、Window之间关系:http://blog.csdn.net/u011733020/article/details/49465707

关于Touch 时间的分发响应文章有很多了,但是自己理一遍的印象 要比看别人的深刻,这里自己记一下,也便与以后回过头来翻!

    Touch 事件 对于 新手来说,是很困扰的,因为事件之间的传递过程,我们还不理解,去翻看代码,翻着 翻着,反而越发不清晰,这里 我跟大伙一起理一下传递机制。

    首先 ,一点大家需要知道  所谓的 Touch事件包含的几个动作:

    MotionEvent.ACTION_DOWN   :手指按下

    MotionEvent.ACTION_UP     :手指离开屏幕

    MotionEvent.ACTION_MOVE   :手指在屏幕上滑动

    有几个方法:


    要了解事件分发过程,肯定是从手机触摸到屏幕这个动作发起的,所以 最先开始的肯定是 ACTION_DOWN 这个动作的捕获。捕获这个动作后,再去传递给要消费该动作的具体View。

那就从简单地Demo开始了解一下View的触摸事件,这里比较基础,如果你对触摸比较了解,可以跳过。

    我们定义一个RelativeLayout 包裹一个ImageView 的简单布局:


              图一

    我们实现Activity的dispatchTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)这两个分方法。

    ImageView的dispatchTouchEvent(MotionEvent event)、onTouchEvent(MotionEvent event)以及实现setOnTouchListener

、setOnClickListener两个监听事件。

    RelativeLayout的dispatchTouchEvent(MotionEvent ev)、onInterceptTouchEvent(MotionEvent ev)和onTouchEvent(MotionEvent event)这三个方法,同时监听RelativeLayout的setOnTouchListener、setOnClickListener事件。

    将程序运行到手机上,我们先点击一下图片以外的地方,看一下打印的Log信息:

点击布局10-29 11:40:24.509: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:40:24.519: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent:   false10-29 11:40:24.519: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_DOWN--------------onTouch10-29 11:40:24.519: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_DOWN--------------onTouchEvent10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.onTouchEvent(event)   :true10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true10-29 11:40:24.519: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity  :true抬手10-29 11:40:30.649: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:40:30.649: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 11:40:30.649: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_UP--------------onTouch10-29 11:40:30.649: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_UP--------------onTouchEvent10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.onTouchEvent(event)   :true10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true10-29 11:40:30.649: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity  :true10-29 11:40:30.649: E/TouchRelativeLayout(29973):  OnClickListener--------------

    我们分析一下,传递流程,首先 Activity 的 dispatchTouchEvent 方法手机按下这一事件给RelativeLayout布局,RelativeLayout这个布局判断是否要拦截该事件的传递,false 不拦截,由于点击的是ImageView 以外的部分,所以该事件传递到RelativeLayout布局上面,就已经找到了targetView,OK,接下来RelativeLayout处理掉这个按下的动作(响应了onTouch和onTouchEvent),并且RelativeLayout的dispatchTouchEvent方法返回true,方法结束,通知Activity 的dispatchTouchEvent方法 ,OK,事件已经被消费掉了,这个按下的动作就响应完了。

    当手指抬起的时候,同样的道理,首先Activity 先知道手指抬起来了,然后通过dispatchTouchEvent 方法将这一动作往内传递。传递给RelativeLayout去分发这个抬手事件,然而RelativeLayout在这个手机抬起的范围内,只有自己能消费这一动作,由于ImageView不在手指抬起范围内,所以ImageView不能消费这一事件,故而 RelativeLayout 在dispatchTouchEvent方法判断,只能到自己这里,因此交给onTouch和onTouchEvent 去消费该事件,同时返回true 告诉Activity已经分发消费掉该事件。

    另外大家都注意到最后执行OnClickListener,为什么最后执行了OnClickListener?我们这里想一下,我们手指抬起的时候都执行了dispatchTouchEvent、onTouch和onTouchEvent。OK 这里dispatchTouchEvent是分发事件找到可以消费的View,故 onClick事件的产生应该与这个方法没有多大关系,这样只剩下onTouch和onTouchEvent了,肯定其中一个 执行了onClick!,这里我们大胆的靠直觉来说,由于onTouch 是setOnTouchListener 的回调,因此也不是这个方法调用了onClick方法,那现在就只有onTouchEvent中执行了onClick方法咯,到底是不是这样呢?

    我们知道我们实现的setOnTouchListener 默认返回的是false,如果我们改成 return true,我们在重复上面的动作会发生什么?好,看下我们改成true 后的Log 信息:

10-29 16:16:50.092: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity 10-29 16:16:50.092: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 16:16:50.092: E/TouchRelativeLayout(9735): onInterceptTouchEvent10-29 16:16:50.092: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_DOWN--------------onTouch10-29 16:16:50.092: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event)   :true10-29 16:16:50.092: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity  :true10-29 16:16:52.692: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity 10-29 16:16:52.692: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 16:16:52.692: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_UP--------------onTouch10-29 16:16:52.692: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event)   :true10-29 16:16:52.692: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity  :true

    诶,最前面我们最后执行的是onClick 事件,这里的onClick并没有执行。。。并且onTouchEvent也没有执行。。我们先想想,首先是Activity 分发给RelativeLayout,RelativeLayout分发给自己消费,当我们设置了setOnTouchListener,return true。以后就没有执行onTouchEvent 方法,那么我们是不是应该去RelativeLayout的dispatchTouchEvent方法里面看看,是不是把我们的事件给拦截掉了?,我们看一下ViewGroup的dispatchTouchEvent方法:

@Overridepublic boolean dispatchTouchEvent(MotionEvent ev) {    ......    boolean handled = false;    ......    final int action = ev.getAction();    final int actionMasked = action & MotionEvent.ACTION_MASK;    // 1)处理初始的ACTION_DOWN    if (actionMasked == MotionEvent.ACTION_DOWN) {        // 把ACTION_DOWN作为一个Touch手势的始点,清除之前的手势状态。        cancelAndClearTouchTargets(ev); //清除前一个手势,*关键操作:mFirstTouchTarget重置为null*        resetTouchState(); //重置Touch状态标识    }    // 2)检查是否会被拦截    final boolean intercepted;    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {        // 是ACTION_DOWN的事件,或者mFirstTouchTarget不为null(已经找到能够接收touch事件的目标组件)        final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;        // 判断禁止拦截的FLAG,因为requestDisallowInterceptTouchEvent(boolean disallowIntercept)方法可以禁止执行是否需要拦截的判断        if (!disallowIntercept) {            // 禁止拦截的FLAG为false,说明可以执行拦截判断,则执行此ViewGroup的onInterceptTouchEvent方法            intercepted = onInterceptTouchEvent(ev); // 此方法默认返回false,如果想修改默认的行为,需要override此方法,修改返回值。            ev.setAction(action);        } else {            // 禁止拦截的FLAG为ture,说明没有必要去执行是否需要拦截了,这个事件是无法拦截的,能够顺利通过,所以设置拦截变量为false            intercepted = false;        }    } else {        // 当不是ACTION_DOWN事件并且mFirstTouchTarget为null(意味着没有touch的目标组件)时,这个ViewGroup应该继续执行拦截的操作。        intercepted = true;    }    // 通过前面的逻辑处理,得到了是否需要进行拦截的变量值    final boolean canceled = resetCancelNextUpFlag(this) || actionMasked == MotionEvent.ACTION_CANCEL;    final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;    TouchTarget newTouchTarget = null;    boolean alreadyDispatchedToNewTouchTarget = false;    if (!canceled && !intercepted) {        // 不是ACTION_CANCEL并且拦截变量为false        if (actionMasked == MotionEvent.ACTION_DOWN) {            // 在ACTION_DOWN时去寻找这次DOWN事件新出现的TouchTarget            final int actionIndex = ev.getActionIndex(); // always 0 for down            .....            final int childrenCount = mChildrenCount;            if (newTouchTarget == null && childrenCount != 0) {                // 根据触摸的坐标寻找能够接收这个事件的子组件                final float x = ev.getX(actionIndex);                final float y = ev.getY(actionIndex);                final View[] children = mChildren;                // 逆序遍历所有子组件                for (int i = childrenCount - 1; i >= 0; i--) {                    final int childIndex = i;                    final View child = children[childIndex];                    // 寻找可接收这个事件并且组件区域内包含点击坐标的子View                    if (!canViewReceivePointerEvents(child) || !isTransformedTouchPointInView(x, y, child, null)) {                        continue;                    }                    newTouchTarget = getTouchTarget(child); // 找到了符合条件的子组件,赋值给newTouchTarget                    ......                    // 把ACTION_DOWN事件传递给子组件进行处理                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {                        // 如果此子ViewGroup消费了这个touch事件                        mLastTouchDownTime = ev.getDownTime();                        mLastTouchDownIndex = childIndex;                        mLastTouchDownX = ev.getX();                        mLastTouchDownY = ev.getY();                        // 则为mFirstTouchTarget赋值为newTouchTarget,此子组件成为新的touch事件的起点                        newTouchTarget = addTouchTarget(child, idBitsToAssign);                        alreadyDispatchedToNewTouchTarget = true;                        break;                    }                }            }            ......        }    }    // 经过前面的ACTION_DOWN的处理,有两种情况。    if (mFirstTouchTarget == null) {        // 情况1:(mFirstTouchTarget为null) 没有找到能够消费touch事件的子组件或者是touch事件被拦截了,        // 那么在ViewGroup的dispatchTransformedTouchEvent方法里面,处理Touch事件则和普通View一样,        // 自己无法消费,调用super.dispatchOnTouchEvent()把事件回递给父ViewGroup进行处理        handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);    } else {        // 情况2:(mFirstTouchTarget!=null) 找到了能够消费touch事件的子组件,那么后续的touch事件都可以传递到子View        TouchTarget target = mFirstTouchTarget;        // (这里为了理解简单,省略了一个Target List的概念,有需要的同学再查看源码)        while (target != null) {            if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {                // 如果前面利用ACTION_DOWN事件寻找符合接收条件的子组件的同时消费掉了ACTION_DOWN事件,这里直接返回true                handled = true;            } else {                final boolean cancelChild = resetCancelNextUpFlag(target.child) || intercepted;                // 对于非ACTION_DOWN事件,则继续传递给目标子组件进行处理(注意这里的非ACTION_DOWN事件已经不需要再判断是否拦截)                if (dispatchTransformedTouchEvent(ev, cancelChild, target.child, target.pointerIdBits)) {                    // 如果target子组件进行处理,符合某些条件的话,会传递ACTION_CANCEL给target子组件                    // 条件是:如果ACTION_DOWN时没有被拦截,而后面的touch事件被拦截,则需要发送ACTION_CANCEL给target子组件                    handled = true;                }                ......            }        }    }    if (canceled || actionMasked == MotionEvent.ACTION_UP) {        // 如果是ACTION_CANCEL或者ACTION_UP,重置Touch状态标识,mFirstTouchTarget赋值为null,后面的Touch事件都无法派发给子View        resetTouchState();    }    ......    return handled;}
    OK 注释很详细,感谢这位朋友类:http://hukai.me/android-deeper-touch-event-dispatch-process/,继续不废话,看代码,不懂得看三遍,重要的事情说三遍。。。三遍。。。遍!!!

    这里大体捋一捋ViewGroup的dispatchTouchEvent 方法,手抬起是Action_Up 方法。故往下看,判断是Action_Down的可以都不看啦,有一段代码 if(mFirstTouchTraget==null),注释写的很详细,我们这里是找到了RelativeLayout 因此not  null。继续往下执行 while(target!=null) 因为是Action_Up走到else里面去执行了dispatchTransformedTouchEvent方法,这里进去看一下做了什么操作,是不是 执行了onClick??让源码给我们揭晓答案吧!

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;        }        // Calculate the number of pointers to deliver.        final int oldPointerIdBits = event.getPointerIdBits();        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;        // If for some reason we ended up in an inconsistent state where it looks like we        // might produce a motion event with no pointers in it, then drop the event.        if (newPointerIdBits == 0) {            return false;        }        // If the number of pointers is the same and we don't need to perform any fancy        // irreversible transformations, then we can reuse the motion event for this        // dispatch as long as we are careful to revert any changes we make.        // Otherwise we need to make a copy.        final MotionEvent transformedEvent;        if (newPointerIdBits == oldPointerIdBits) {            if (child == null || child.hasIdentityMatrix()) {                if (child == null) {                    handled = super.dispatchTouchEvent(event);                } else {                    final float offsetX = mScrollX - child.mLeft;                    final float offsetY = mScrollY - child.mTop;                    event.offsetLocation(offsetX, offsetY);                    handled = child.dispatchTouchEvent(event);                    event.offsetLocation(-offsetX, -offsetY);                }                return handled;            }            transformedEvent = MotionEvent.obtain(event);        } else {            transformedEvent = event.split(newPointerIdBits);        }        // Perform any necessary transformations and dispatch.        if (child == null) {            handled = super.dispatchTouchEvent(transformedEvent);        } else {            final float offsetX = mScrollX - child.mLeft;            final float offsetY = mScrollY - child.mTop;            transformedEvent.offsetLocation(offsetX, offsetY);            if (! child.hasIdentityMatrix()) {                transformedEvent.transform(child.getInverseMatrix());            }            handled = child.dispatchTouchEvent(transformedEvent);        }        // Done.        transformedEvent.recycle();        return handled;    }
    代码比较长,不一行一行看,挑出下面这一部分来看

// Perform any necessary transformations and dispatch.        if (child == null) {            handled = super.dispatchTouchEvent(transformedEvent);        } else {            final float offsetX = mScrollX - child.mLeft;            final float offsetY = mScrollY - child.mTop;            transformedEvent.offsetLocation(offsetX, offsetY);            if (! child.hasIdentityMatrix()) {                transformedEvent.transform(child.getInverseMatrix());            }            handled = child.dispatchTouchEvent(transformedEvent);        }
    OK。我们不关系 child 是什么,看这个方法我们看到不论是 if 还是else 都执行了dispatchTouchEvent方法。

public boolean dispatchTouchEvent(MotionEvent event) {if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onTouchEvent(event, 0);        }        if (onFilterTouchEventForSecurity(event)) {            //noinspection SimplifiableIfStatement            ListenerInfo li = mListenerInfo;            if (li != null && li.mOnTouchListener != null && (mViewFlags & ENABLED_MASK) == ENABLED                    && li.mOnTouchListener.onTouch(this, event)) {                return true;            }            if (onTouchEvent(event)) {                return true;            }        }        if (mInputEventConsistencyVerifier != null) {            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);        }        return false;    }

  相信这个方法,我们都自己翻看过很多次,这个方法的第九行,在if判断了mOnTouchListener!=null ,mOnTouchListener是在哪里赋值呢?

public void setOnTouchListener(OnTouchListener l) {        getListenerInfo().mOnTouchListener = l;    }
    看,就是我们设置的setOnTouchListener,传递进去的OnTouchListener,只要我们注册了Touch事件,这个mOnTouchListener 就是非null 的。然后执行了mOnTouchListener 的onTouch, 我们在setOnTouchListener的时候我们把返回值改成了true(默认false)。因为if条件满足,直接返回true, 下面的if(onTouchEvent(event))就执行不到了!,所以跟我们测试的结果一样,当我们把setOnTouchListener返回值改为true 的时候 onTouchEvent 没有执行,但是前面onClick不是也没有执行吗?不要急,我们一起看一下onTouchEvent方法做了什么吧。

   /**     * Implement this method to handle touch screen motion events.     *     * @param event The motion event.     * @return True if the event was handled, false otherwise.     */    public boolean onTouchEvent(MotionEvent event) {        final int viewFlags = mViewFlags;        if ((viewFlags & ENABLED_MASK) == DISABLED) {            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {                setPressed(false);            }            // A disabled view that is clickable still consumes the touch            // events, it just doesn't respond to them.            return (((viewFlags & CLICKABLE) == CLICKABLE ||                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));        }        if (mTouchDelegate != null) {            if (mTouchDelegate.onTouchEvent(event)) {                return true;            }        }        if (((viewFlags & CLICKABLE) == CLICKABLE ||                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {            switch (event.getAction()) {                case MotionEvent.ACTION_UP:                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {                        // take focus if we don't have it already and we should in                        // touch mode.                        boolean focusTaken = false;                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {                            focusTaken = requestFocus();                        }                        if (prepressed) {                            // The button is being released before we actually                            // showed it as pressed.  Make it show the pressed                            // state now (before scheduling the click) to ensure                            // the user sees it.                            setPressed(true);                       }                        if (!mHasPerformedLongPress) {                            // This is a tap, so remove the longpress check                            removeLongPressCallback();                            // Only perform take click actions if we were in the pressed state                            if (!focusTaken) {                                // Use a Runnable and post this rather than calling                                // performClick directly. This lets other visual state                                // of the view update before click actions start.                                if (mPerformClick == null) {                                    mPerformClick = new PerformClick();                                }                                if (!post(mPerformClick)) {                                    performClick();                                }                            }                        }                        if (mUnsetPressedState == null) {                            mUnsetPressedState = new UnsetPressedState();                        }                        if (prepressed) {                            postDelayed(mUnsetPressedState,                                    ViewConfiguration.getPressedStateDuration());                        } else if (!post(mUnsetPressedState)) {                            // If the post failed, unpress right now                            mUnsetPressedState.run();                        }                        removeTapCallback();                    }                    break;                case MotionEvent.ACTION_DOWN:                    mHasPerformedLongPress = false;                    if (performButtonActionOnTouchDown(event)) {                        break;                    }                    // Walk up the hierarchy to determine if we're inside a scrolling container.                    boolean isInScrollingContainer = isInScrollingContainer();                    // For views inside a scrolling container, delay the pressed feedback for                    // a short period in case this is a scroll.                    if (isInScrollingContainer) {                        mPrivateFlags |= PFLAG_PREPRESSED;                        if (mPendingCheckForTap == null) {                            mPendingCheckForTap = new CheckForTap();                        }                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());                    } else {                        // Not inside a scrolling container, so show the feedback right away                        setPressed(true);                        checkForLongClick(0);                    }                    break;                case MotionEvent.ACTION_CANCEL:                    setPressed(false);                    removeTapCallback();                    removeLongPressCallback();                    break;                case MotionEvent.ACTION_MOVE:                    final int x = (int) event.getX();                    final int y = (int) event.getY();                    // Be lenient about moving outside of buttons                    if (!pointInView(x, y, mTouchSlop)) {                        // Outside button                        removeTapCallback();                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {                            // Remove any future long press/tap checks                            removeLongPressCallback();                            setPressed(false);                        }                    }                    break;            }            return true;        }        return false;    }

    代码比较长,这里摘出Action_Up中的一部分代码

                     if (!mHasPerformedLongPress) {                            // This is a tap, so remove the longpress check                            removeLongPressCallback();                            // Only perform take click actions if we were in the pressed state                            if (!focusTaken) {                                // Use a Runnable and post this rather than calling                                // performClick directly. This lets other visual state                                // of the view update before click actions start.                                if (mPerformClick == null) {                                    mPerformClick = new PerformClick();                                }                                if (!post(mPerformClick)) {                                    performClick();                                }                            }                        }

    里面执行了PerformClick,大家都知道PerformClick 会调用Click方法

   public boolean performClick() {        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);        ListenerInfo li = mListenerInfo;        if (li != null && li.mOnClickListener != null) {            playSoundEffect(SoundEffectConstants.CLICK);            li.mOnClickListener.onClick(this);            return true;        }        return false;    }
    前提是onClickListener我们注册过。


    好了。到这里我们得出了结论,也看晕了!!画个图表示一下整个过程加深一下。


                                                                                                                              图二

如果是点击我们图一中的图片呢?这里我简单说一下,先看一下Log信息:

点击布局中的图片10-29 11:41:05.839: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:41:05.839: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent:   false10-29 11:41:05.839: E/TouchImageView(29973):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 11:41:05.839: E/TouchImageView(29973):  MotionEvent.ACTION_DOWN--------------onTouch10-29 11:41:05.839: E/TouchImageView(29973):  MotionEvent.ACTION_DOWN--------------onTouchEvent10-29 11:41:05.839: E/TouchImageView(29973): super.onTouchEvent(event)   :true10-29 11:41:05.839: E/TouchImageView(29973): super.dispatchTouchEvent(event)   :true10-29 11:41:05.839: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true10-29 11:41:05.839: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity  :true
    这个跟前面的差不多,不浪费大家的时间,三言两语介绍一下!

    首先 Activity 感知到手指按下这一动作,然后传递给RelativeLayout,然后RelativeLayout 发现 还有子View,继续传递给了ImageView,此时ImageView 自己消费了Action_Down 这个动作,然后告诉RelativeLayout,返回true,RelativeLayout又告诉Activity ,OK 结束。

    当手指抬起时继续看下面:

抬手10-29 11:41:09.099: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity 10-29 11:41:09.099: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent:   false10-29 11:41:09.099: E/TouchImageView(29973):  MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 11:41:09.099: E/TouchImageView(29973):  MotionEvent.ACTION_UP--------------onTouch10-29 11:41:09.099: E/TouchImageView(29973):  MotionEvent.ACTION_UP--------------onTouchEvent10-29 11:41:09.099: E/TouchImageView(29973): super.onTouchEvent(event)   :true10-29 11:41:09.099: E/TouchImageView(29973): super.dispatchTouchEvent(event)   :true10-29 11:41:09.099: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true10-29 11:41:09.099: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity  :true10-29 11:41:09.099: E/TouchImageView(29973):  OnClickListener--------------
    同样的,先通过Activity传递给RelativeLayout,RelativeLayout又传递给ImageView,最终由ImageView处理了Action_Up 这一事件,处理完后,告诉RelativeLayout,RelativeLayout有告诉Activity。

     同样的用图表示一下,帮助理解:


    另外我们知道,默认RelativeLayout的onInterceptTouchEvent是返回false的,表示不拦截,如果我们改成true,再看下会发生什么??

    对于Action_Down 在RelativeLayout,打印的Log并没有区别,当按下在ImageView的时候,是有区别的,看下Log

拦截点击ImageViewdown10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity 10-29 18:43:17.004: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent10-29 18:43:17.004: E/TouchRelativeLayout(30724): onInterceptTouchEvent  true10-29 18:43:17.004: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_DOWN--------------onTouch10-29 18:43:17.004: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_DOWN--------------onTouchEvent10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.onTouchEvent(event)   :true10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event)   :true10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity  :trueup10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity 10-29 18:43:18.734: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_UP--------------dispatchTouchEvent10-29 18:43:18.734: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_UP--------------onTouch10-29 18:43:18.734: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_UP--------------onTouchEvent10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.onTouchEvent(event)   :true10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event)   :true10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity  :true10-29 18:43:18.734: E/TouchRelativeLayout(30724):  OnClickListener--------------

    对比前面的我们可以看出,当onInterceptTouchEvent 返回true 时,RelativeLayout就不会把事件往ImageView那里分发,而是自己消费掉。同时通知Activity。

    因此得出结论, 在哪一个ViewGroup 的onInterceptTouchEvent 如果拦截了该事件,那么该事件最终只能传递到当前ViewGroup,不会继续往下执行。当然是否是该ViewGroup消费该事件,还要具体看情况。

    总结起来我们得出以下结论:
    1.事件分发 是有Activity 往ViewGroup 传递,ViewGroup在传递给内部,去找到可以消费该事件的具体TargetView.
    2.触摸事件的分发动作——>Activity——>ViewGroup——>子ViewGroup——>...内部子ViewGroup——>Target View
响应就是反过来:Target View——>子ViewGroup——>...外部子ViewGroup——>ViewGroup——>Activity
如果我们嵌套的ViewGroup比较多,那么分发的时候ViewGroup会向子ViewGroup传递,同样的,响应反过来理解。

    3.在ViewGroup中如果onInterceptTouchEvent 拦截了该事件,那么该事件就不会往下传递。

    4.如果当前TargetView 处理了该次事件,那么该次事件不会再回传给ViewGroup。


----------------欢迎爱学习的小伙伴加群
--------------------android交流群:230274309
------------------------一起分享,一起进步!需要你们
------------------------------期待各位爱学习的小伙伴们的到来

能看到这里的,都是比较认真的同学咯、灰常感谢、用的Demo 这里没有就不上传了。需要的可以等我回头再上传,或者加群,去群里问一下。


参考一下资料:

http://www.mobdevgroup.com/platform/android/article/

 http://blog.csdn.net/lvxiangan/article/details/9309927

http://blog.csdn.net/xyz_lmn/article/details/12517911

http://blog.csdn.net/qiushuiqifei/article/details/9918527

http://blog.csdn.net/guolin_blog/article/details/9097463

灰常感谢以上!!



版权声明:本文为博主原创文章,未经博主允许不得转载。

3楼u010786678昨天 21:29
谢谢小伙伴的分享,学习了
Re: u0117330203小时前
回复u010786678n嘿,向你们这些大牛学习~
2楼u010786678昨天 21:29
谢谢小伙伴的分享,学习了
1楼yulianlin昨天 13:06
收藏了
  相关解决方案