Android事件分发机制

作者: ClericYi | 来源:发表于2020-01-28 13:23 被阅读0次

    CVTE一面答的很不好的题目之一,特别写一篇博客反思自己。我记得我当时答的糊里糊涂的,只说了连事件从什么时候开始响应的也没说,只说了从子控件开始接受到,如果不消费,或者没有子控件能够消费时,就向上传递,一直传到根布局后自动消费。

    所谓的安卓事件是什么?具体来说的就是点击和滑动两个操作;抽象着来说就是下面的表格。

    MotionEvent/事件类型 具体操作
    ACTION_DOWN 点下View
    ACTION_UP 抬起View
    ACTION_MOVE 滑动View
    ACTION_CANCEL 非人为因素取消

    事件序列一般组成:

    点击的事件组成就是:Down --> Up

    滑动的事件组成就是:Down --> Move --> Move .... --> Up


    事件分发

    1. 使用到的函数
      • dispatchTouchEvent():用于事件分发
      • onTouchEvent():消费事件
      • onInterceptTouchEvent():判断是否拦截事件,仅存在于ViewGroup
    2. 分发对象
      • Activity
      • ViewGroup
      • View

    Activity的事件分发

    public boolean dispatchTouchEvent(MotionEvent ev) {
        // 从判断语句中可以得出所有事件的起点就是Down
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            // 实现屏保功能
            onUserInteraction();
        }
        // 向上传递至ViewGroup,调用其dispatchTouchEvent
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }
    

    文章中我已经添加了注释内容,其中getWindow()获得就是一个Window抽象类,根据其子类PhoneWindow我们可以很容易得知最后调用的其实就是ViewGroupdispatchTouchEvent()方法

    /**
     * 实际上就是判断事件是否是DOWN事件,event的坐标是否在边界内等
     */
    public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }
    

    最后就是Activity中的onTouchEvent()方法了,这个模块干的事情在注释中也就很清晰明了了。


    ViewGroup的事件分发

    public boolean dispatchTouchEvent(MotionEvent ev) {
        ········
        // 初始化Down事件
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 丢弃之前手头上干的事情,重新开始响应Down事件
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }
        // 检查是否需要拦截
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            // 这个与运算是用于影响除Down以外的事件的
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            // 当前按压的位置没有控件,或者当前控件并不可被点击,直接被ViewGroup拦截
            intercepted = true;
        }
        ········
        /**
         *这个判断里面同样的还是判断响应的事件,然后就是通过一个for循环判断位置来判断当前的子控件是否在对应的位置内
         * 还有非常重要的一点就是这个循环的判断还是倒叙的
         */
        if (actionMasked == MotionEvent.ACTION_DOWN
            || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
            || actionMasked ==MotionEvent.ACTION_HOVER_MOVE) {
            ········
            if (newTouchTarget == null && childrenCount != 0) {
                ········
                for (int i = childrenCount - 1; i >= 0; i--) {
                    final int childIndex = getAndVerifyPreorderedIndex(childrenCount, i, customOrder);
                    final View child = getAndVerifyPreorderedView(preorderedList, children, childIndex);
                    // 后面的篇幅主要用于判断当前的控件的各种属性是否是满足需要的。比如说位置、是否可以点击、是否隐藏等一系列信息
                    ········ 
                }
       ········
    }
    
    public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;
    }
    

    onInterceptTouchEvent(MotionEvent ev)函数可知,默认其实并不会去拦截。所以就一般情况而言,dispatchTouchEvent()方法是需要去循环遍历子控件集合去寻找对应的控件的。

    使用一个伪代码解释以上的逻辑

    public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean consume = false;
        if(onInterceptTouchEvent(ev)){
            // 自己拦截,自己消费
            onTouchEvent(ev);
        }else{
            // 不拦截,分发给子View进行消费
            consume = child.dispatchTouchEvent(ev);
        }
        return consume;
    }
    

    View事件分发

        public boolean dispatchTouchEvent(MotionEvent event) {
            ·····
            boolean result = false;
    
            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)) {
                    result = true;
                }
                
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
            }
            if (!result && mInputEventConsistencyVerifier != null) {
                mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
            }
            return result;
        }
    

    通过以往的实践,我们知道只有通过设置了监听器的View才能够去监听事件,那么在dispatchTouchEvent()方法中也是一样的,如果View并没有被设置监听器,变量result也不会被赋值成为true。

    从代码中很容易看出onTouch()方法的优先级大于onTouchEvent()方法。

        public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
    
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
            ·····
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        ·····
                        boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
    
                            boolean focusTaken = false;
                            if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                                focusTaken = requestFocus();
                            }
    
                            if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                removeLongPressCallback();
    
                                if (!focusTaken) {
                                    if (mPerformClick == null) {
                                        mPerformClick = new PerformClick();
                                    }
                                    if (!post(mPerformClick)) {
                                        performClickInternal();
                                    }
                                }
                            }
                            ·····
                        }
                        ·····
                        mIgnoreNextUpEvent = false;
                        break;
                    ·····
                }
                return true;
            }
            return false;
        }
    

    onTouchEvent()方法中其实具体干了一件事情,那就是区别到底是长按事件还是点击事件。

    那么先行判断的是长按事件还是点击事件呢?答案很明显,在代码行中removeLongPressCallback();有一个这样的函数,这就是去除长按事件回调的函数,所以答案就是长按事件是第一个被判断的事件,然后才是点击事件。

    判断这个方法的事件的方法就是通过做出Up动作时的时间和做出Down动作时的时间间隔。如果Down和Up两个动作之间的时间间隔小于500ms,就是点击事件。


    总结

    Android事件分发机制.png

    以上就是我的学习成果,如果有什么我没有思考到的地方或是文章内存在错误,欢迎与我分享。

    相关文章

      网友评论

        本文标题:Android事件分发机制

        本文链接:https://www.haomeiwen.com/subject/zenpthtx.html