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--------------dispatchTouchEvent
10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent
10-29 11:40:24.519: E/TouchRelativeLayout(29973): onInterceptTouchEvent:   false
10-29 11:40:24.519: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_DOWN--------------onTouch
10-29 11:40:24.519: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_DOWN--------------onTouchEvent
10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.onTouchEvent(event)   :true
10-29 11:40:24.519: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true
10-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--------------dispatchTouchEvent
10-29 11:40:30.649: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_UP--------------onTouch
10-29 11:40:30.649: E/TouchRelativeLayout(29973):  MotionEvent.ACTION_UP--------------onTouchEvent
10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.onTouchEvent(event)   :true
10-29 11:40:30.649: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true
10-29 11:40:30.649: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity  :true
10-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--------------dispatchTouchEvent
10-29 16:16:50.092: E/TouchRelativeLayout(9735): onInterceptTouchEvent
10-29 16:16:50.092: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_DOWN--------------onTouch
10-29 16:16:50.092: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event)   :true
10-29 16:16:50.092: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity  :true
10-29 16:16:52.692: E/MainActivity(9735): dispatchTouchEvent:--------------MainActivity 
10-29 16:16:52.692: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_UP--------------dispatchTouchEvent
10-29 16:16:52.692: E/TouchRelativeLayout(9735):  MotionEvent.ACTION_UP--------------onTouch
10-29 16:16:52.692: E/TouchRelativeLayout(9735): super.dispatchTouchEvent(event)   :true
10-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方法:

@Override
public 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--------------dispatchTouchEvent
10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent
10-29 11:41:05.839: E/TouchRelativeLayout(29973): onInterceptTouchEvent:   false
10-29 11:41:05.839: E/TouchImageView(29973):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent
10-29 11:41:05.839: E/TouchImageView(29973):  MotionEvent.ACTION_DOWN--------------onTouch
10-29 11:41:05.839: E/TouchImageView(29973):  MotionEvent.ACTION_DOWN--------------onTouchEvent
10-29 11:41:05.839: E/TouchImageView(29973): super.onTouchEvent(event)   :true
10-29 11:41:05.839: E/TouchImageView(29973): super.dispatchTouchEvent(event)   :true
10-29 11:41:05.839: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true
10-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--------------dispatchTouchEvent
10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent
10-29 11:41:09.099: E/TouchRelativeLayout(29973): onInterceptTouchEvent:   false
10-29 11:41:09.099: E/TouchImageView(29973):  MotionEvent.ACTION_UP--------------dispatchTouchEvent
10-29 11:41:09.099: E/TouchImageView(29973):  MotionEvent.ACTION_UP--------------onTouch
10-29 11:41:09.099: E/TouchImageView(29973):  MotionEvent.ACTION_UP--------------onTouchEvent
10-29 11:41:09.099: E/TouchImageView(29973): super.onTouchEvent(event)   :true
10-29 11:41:09.099: E/TouchImageView(29973): super.dispatchTouchEvent(event)   :true
10-29 11:41:09.099: E/TouchRelativeLayout(29973): super.dispatchTouchEvent(event)   :true
10-29 11:41:09.099: E/MainActivity(29973): dispatchTouchEvent:--------------MainActivity  :true
10-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

拦截点击ImageView

down
10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity 
10-29 18:43:17.004: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_DOWN--------------dispatchTouchEvent
10-29 18:43:17.004: E/TouchRelativeLayout(30724): onInterceptTouchEvent  true
10-29 18:43:17.004: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_DOWN--------------onTouch
10-29 18:43:17.004: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_DOWN--------------onTouchEvent
10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.onTouchEvent(event)   :true
10-29 18:43:17.004: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event)   :true
10-29 18:43:17.004: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity  :true
up
10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity 
10-29 18:43:18.734: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_UP--------------dispatchTouchEvent
10-29 18:43:18.734: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_UP--------------onTouch
10-29 18:43:18.734: E/TouchRelativeLayout(30724):  MotionEvent.ACTION_UP--------------onTouchEvent
10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.onTouchEvent(event)   :true
10-29 18:43:18.734: E/TouchRelativeLayout(30724): super.dispatchTouchEvent(event)   :true
10-29 18:43:18.734: E/MainActivity(30724): dispatchTouchEvent:--------------MainActivity  :true
10-29 18:43:18.734: E/TouchRelativeLayout(30724):  OnClickListener--------------

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

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

通过本文,我们可以知道以下几点:

   (1)Touch事件,主要由 dispatchTouchEvent()函数(传递消息)、onInterceptTouchEvent()函数(拦截消息)、onTouchEvent()函数和OnTouchListener(消费事件)组成。

   (2)事件从Activity.dispatchTouchEvent()开始传递,只要没有被停止或拦截,从最上层的View(ViewGroup)开始一直往下(子View)传递。子View可以通过onTouchEvent()对事件进行处理。

   (3)事件由父View(ViewGroup)传递给子View,ViewGroup可以通过onInterceptTouchEvent()对事件做拦截,停止其往下传递。

   (4)如果事件从上往下传递过程中一直没有被停止,且最底层子View没有消费事件,事件会反向往上传递,这时父View(ViewGroup)可以进行消费,如果还是没有被消费的话,最后会到Activity的onTouchEvent()函数。

   (5) OnTouchListener优先于onTouchEvent()对事件进行消费,消费就是相应的函数返回true。

----------------欢迎爱学习的小伙伴加群

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

灰常感谢以上!!

接下来是分析Android Handler 的工作机制,感兴趣的小伙伴可以戳进去:看完这篇文章,你就了解了Android Handler的一切


  • 3
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值