Android事件分发学习

需要搞懂的疑问

  • 当按住一个在Linearlayout里的button后滑动出这个button,为什么这个button还能继续接收处理触摸事件
  • 触摸事件是如何传递给Activity,才继续进行Activity->Window->View的分发的
  • 事件分发递归调用的流程整理
  • 上层ViewGroup设置onTouchListener并且在onTouch方法返回true,为什么它的子view还是可以接收到触摸事件
  • 了解为什么设置setEnable(false)后,onTouchEvent()事件还可以发生,onclick事件为什么没有
  • 当touch事件继续传递给一个坐标不在该view内的view对象时,它是怎么处理的
  • 当一个viewGroup有并列的子view时,是先把事件传递给哪个子view的
  • MotionEvent里的x,y坐标都是相对于谁的坐标值

事件源码分析

简化ViewGroup的dispatchTouchEvent()方法如下:

//子view可设置该变量为true禁止拦截touch事件
private boolean isDisallowIntercept;

public boolean dispatchTouchEvent(MotionEvent ev) {
    //********预先定义处理结果变量********
    boolean handled = false;
    //重置标记位和变量
    if(actionMasked == MotionEvent.ACTION_DOWN){
        mFirstTouchTarget = null; //mFirstTouchTarget设置为null,并且清空单链表
        isDisallowIntercept = false; //重置失效拦截标志位,所以ACTION_DOWN的时,父view想拦截是一定可以拦截的
    }

    //********检查是否拦截********
    final boolean intercepted;
    //如果ACTION_DOWN的时候拦截了事件,则mFirstTouchTarget=null,后续的ACTION_MOVE和ACTION_UP事件
    // 则不会进入这个判断,onInterceptTouchEvent也就没有再执行的机会和必要了
    if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
        if (!isDisallowIntercept) {
            intercepted = onInterceptTouchEvent(ev);
        } else {
            intercepted = false;
        }
    } else {
        intercepted = true;
    }

    //********判断分发********
    TouchTarget newTouchTarget = null;
    boolean alreadyDispatchedToNewTouchTarget = false;
    if (!canceled && !intercepted) {
        if (actionMasked == MotionEvent.ACTION_DOWN
                || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
            final int childrenCount = mChildrenCount;
            if (newTouchTarget == null && childrenCount != 0) {
                final View[] children = mChildren;
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final View child = (preorderedList == null)
                            ? children[childIndex] : preorderedList.get(childIndex);
                    //如果之前该child已经处理过该序列事件中的事件,找到后直接返回
                    newTouchTarget = getTouchTarget(child);
                    if (newTouchTarget != null) {
                        break;
                    }

                    //调用child的dispatchTouchEvent()返回,如果返回true,则创建新的TouchTarget并加在链表头
                    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign
                        newTouchTarget = addTouchTarget(child, idBitsToAssign);
                        alreadyDispatchedToNewTouchTarget = true;
                        break;
                    }
                }
            }

            //如果遍历中没找到处理的子view,并且单链表不为null,则newTouchTarget指向链表尾部元素
            if (newTouchTarget == null && mFirstTouchTarget != null) {
                newTouchTarget = mFirstTouchTarget;
                while (newTouchTarget.next != null) {
                    newTouchTarget = newTouchTarget.next;
                }
            }
        }
    }

    //********处理分发结果********
    if (mFirstTouchTarget == null) {
        // 没有子view处理,则自己调用自己的处理方法,看自己是否处理
        handled = dispatchTransformedTouchEvent(ev, canceled, null,
                TouchTarget.ALL_POINTER_IDS);
    } else {
        TouchTarget predecessor = null;
        TouchTarget target = mFirstTouchTarget;
        while (target != null) {
            //如果是上面处理ACTION_DOWN时新到的处理子view,则直接返回true
            if (alreadyDispatchedToNewTouchTarget && newTouchTarget == mFirstTouchTarg
                handled = true;
            } else {
                //可以看到intercepted到后面处理时还是有用的,如果一系列事件开始已经交给某个子view处理
                //当处理到中间某一个事件的时候突然拦截
                final boolean cancelChild = resetCancelNextUpFlag(target.child)
                        || intercepted;
                //如果拦截了某个中间事件,则会给之前处理的子view发送CANCEL事件
                if (dispatchTransformedTouchEvent(ev, cancelChild,
                        target.child, target.pointerIdBits)) {
                    handled = true;
                }
                if (cancelChild) {
                    if (predecessor == null) {
                        //如果是第一次循环,且链表只有一个头元素,则next=null,相当于清空了mFirstTouchTarget
                        //则下一次事件来的时候intercepted直接为true,不会再传递给子view处理
                        mFirstTouchTarget = next;
                    } else {
                        predecessor.next = next;
                    }
                    target.recycle();
                    target = next;
                    continue;
                }
            }
            predecessor = target;
            target = next;
        }
    }
    return handled;
}

简化View的dispatchTouchEvent()方法如下:

public boolean dispatchTouchEvent(MotionEvent event) {
    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        //判断OnTouchListener是否不为空,并且viw是否是ENABLED,如果都是则会执行OnTouchListener
        //的onTouch()事件。如果的onTouch事件返回true,则直接返回,不会执行view本身的onTouchEvent方法
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }
        //当上面的判断返回false时,才有机会执行view本身的onTouchEvent方法
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    if (!result && mInputEventConsistencyVerifier != null) {
        mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
    }
    //OnTouchListener的onTouch()和view本身的onTouchEvent只要有一个返回true,
    // 则表示该view消费事件
    return result;
}

简化View的onTouchEvent()方法如下:

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        //如果view的ENABLED为false,则当view的clickable或者longClickable有一个是true的话,则
        //消费事件,但是什么都不做,所以onClick()方法也不会触发。否则则直接返回不消费事件
        return (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
    }
    //如果设置了TouchDelegate类,则调用TouchDelegate的onTouchEvent()方法处理,
    //如果TouchDelegate的onTouchEvent()返回true则直接返回消费事件。
    //从TouchDelegate类文档看该类用于帮助扩大touch区域
    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;
                //当在PREPRESSED或者PRESSED状态的时候才进入
                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) {
                        setPressed(true, x, y);
                    }
                    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) {
                            if (mPerformClick == null) {
                                mPerformClick = new PerformClick();
                            }
                            //触发OnClickListener的onClick()的方法,用post方式
                            if (!post(mPerformClick)) {
                                performClick();
                            }
                        }
                    }
                    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) {
                    //设置为PREPRESSED状态
                    mPrivateFlags |= PFLAG_PREPRESSED;
                    if (mPendingCheckForTap == null) {
                        mPendingCheckForTap = new CheckForTap();
                    }
                    mPendingCheckForTap.x = event.getX();
                    mPendingCheckForTap.y = event.getY();
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                } else {
                    // Not inside a scrolling container, so show the feedback right away
                    setPressed(true, x, y);
                    //检查是否为长按事件
                    checkForLongClick(0);
                }
                break;
            case MotionEvent.ACTION_CANCEL:
                setPressed(false);
                removeTapCallback();
                removeLongPressCallback();
                break;
            case MotionEvent.ACTION_MOVE:
                drawableHotspotChanged(x, y);
                //移动出view所在的区域
                if (!pointInView(x, y, mTouchSlop)) {
                    // Outside button
                    removeTapCallback();
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                        removeLongPressCallback();
                        //当移出view区域时,如果当前为PRESSED状态,则会清除PRESSED状态,并且刷新view
                        //比如view背景设置为一个selector,则会恢复显示normal状态下的背景
                        setPressed(false);
                    }
                }
                break;
        }
        return true;
    }
    return false;
}

小结

由于ViewGroup的onInterceptTouchEvent()方法默认实现为直接返回false和当ViewGroup没找到子view处理touch事件的时候,会调用super.dispatchTouchEvent(event),即view的dispatchTouchEvent()方法,所以从上面的源码可以得出下面结论:

  • 当某一个ViewGroup或者View的dispatchTouchEven()方法返回true的时,则相对于它们的父view表示该ViewGroup或者View消费了这次touch事件。对于ViewGroup有可能是自身的OnTouchListener或onTouchEvent方法消费了,也有可能是子view消费了,而对于View而言,则只能是前者。但对于它们父view来说是没有区别的,父view的mFirstTouchTarget中保存的是该ViewGroup或者View。

  • 整个调用过程有点类递归的感觉,递归的深度则是整个布局中view层次的深度,所以会有往下深入和网上冒出的过程,俗称的往下隧道方式,往上冒泡方式的传递过程。而且这样的过程最多只有ACTION_DOWN的时候能够完整走一遍,当ACTION_MOVE和ACTION_UP的时候,DecorView的mFirstTouchTarget已经记录了消费的直接子view,如果这个直接子view是ViewGroup的话,则它的mFirstTouchTarget又记录了它里面消费事件的子view,如此循环,最后交给最深的处的消费子view,没有了ACTION_DOWN时遍历寻找消费子view的过程。

  • 每一个事件ViewGroup都会遍历mFirstTouchTarget单链表里所有记录的view,如果不是cancelChild则会给他们发送正常事件,如果是则会给该child发送ACTION_CANCEL事件,并且从链表中移除该child。判断一个view是不是cancelChild则通过判断该child的标记位和ViewGroup是否拦截来决定,只有有一个成立,则是cancelChild。不管是发送正常事件还是ACTION_CANCEL事件,都会调用子view的dispatchTouchEven()方法,只要有一个返回true,则ViewGroup会向上返回true表示消费该事件。

  • 当动态改变ViewGroup的onInterceptTouchEvent()方法的返回值时,比如在ACTION_DOWN时返回false不拦截,ACTION_MOVE的时候返回true,则ViewGroup在第一次ACTION_MOVE事件到来时把事件转变为ACTION_CANCEL发送给之前处理事件的子view,并且会把mFirstTouchTarget置为null,下次ACTION_MOVE或ACTION_UP时来到时,intercepted则为true表示拦截,ViewGroup会调用自己的OnTouchListener或onTouchEvent方法。

  • 当动态改变子view的dispatchTouchEven()方法返回值时,比如在ACTION_DOWN时返回true消费事件,ACTION_MOVE的时候返回false,由于父ViewGroup的mFirstTouchTarget单链表里保存的该子view,只有在它是cancelChild的时候才会移除,就算ACTION_MOVE的时候返回false导致子view的dispatchTouchEvent()方法返回false,父ViewGroup的mFirstTouchTarget单链表还是会保存该子view,所有后续的ACTION_MOVE和ACTION_UP事件还是会继续发送给该子view。可以看出子view的dispatchTouchEvent()方法返回值只有在ACTION_DOWN事件时才能起到决定事件走向的作用。但是由于子view的dispatchTouchEvent()方法返回false,而且也不会走父ViewGroup的OnTouchListener或onTouchEvent方法,所以最终到Activity的时,DecorView的dispatchTouchEvent()方法也会返回false,这时候会执行Activity的onTouchEvent()方法。

回答疑问

  • 当按住一个在Linearlayout里的button后滑动出这个button,为什么这个button还能继续接收处理触摸事件
    因为Linearlayout的mFirstTouchTarget里面保存有该button,并且判断它不是cancelChild,所以会一直转发事件发给它,button内部的onTouchEvent()方法,ACTION_MOVE时会判断是否滑出button边界,如果是则会清除PRESSED状态,并且刷新背景,当ACTION_UP的时候判断不是PRESSED状态,所以OnClickListener的onclick()方法也不会触发。

  • 触摸事件是如何传递给Activity,才继续进行Activity->Window->View的分发的

  • 事件分发递归调用的流程整理

  • 上层ViewGroup设置onTouchListener并且在onTouch方法返回true,为什么它的子view还是可以接收到触摸事件
    因为如果子view的dispatchTouchEven()方法返回true的话,则会先发送给子view消费,不会走到ViewGroup的onTouchListener和onTouchEvent()方法。只有当点击ViewGroup中未包含子view的地方才会不经过子view直接自己消费。

  • 了解为什么设置setEnable(false)后,onTouchEvent()事件还可以发生,onclick事件为什么没有
    因为setEnable(false)后,view的CLICKABLE或者LONG_CLICKABLE还是true的,所以还可以继续消费事件,只是接收后,什么都不做直接返回true,所以onclick事件不会触发

  • 当touch事件继续传递给一个坐标不在该view内的view对象时,它是怎么处理的
    同第一个问题,ACTION_MOVE时会判断是否滑出button边界,如果是则会清除PRESSED状态,并且刷新背景,当ACTION_UP的时候判断不是PRESSED状态,所以OnClickListener的onclick()方法也不会触发。

  • 当一个viewGroup有并列的子view时,是先把事件传递给哪个子view的
    preorderedList中的顺序是按照addView或者XML布局文件中的顺序来的,后addView添加的子View,会因为Android的UI后刷新机制显示在上层,所以有覆盖的并列子view时,会先传递给上层view(已验证源码待分析)

  • MotionEvent里的x,y坐标都是相对于谁的坐标值
    MotionEvent有四个方法getRawX(), event.getRawY(), getX(),getY()。awX和rawY是相对于屏幕的坐标,x和y是相对于当前控件的坐标。rawX和X 向右移动都是增大,向左移动都是减小,rawY和 Y 向下移动都是增大,向上移动都是减小。

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值