美文网首页
Android事件分发机制

Android事件分发机制

作者: 椰子 | 来源:发表于2019-10-16 01:15 被阅读0次

    主要方法:

    1.dispatchTouchEvent(MotionEvent ev):用来进行事件分发
    2.onInterceptTouchEvent(MotionEvent ev):判断是否拦截事件(只存在于ViewGroup中)
    3.onTouchEvent(MotionEvent ev):处理点击事件

    一、事件分发:Activity

    Activity.java
    /**
         * 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();
            }
            //如果返回false,就走下边的onTouchEvent()
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
    
    /**
         * Called when a touch screen event was not handled by any of the views
         * under it.  This is most useful to process touch events that happen
         * outside of your window bounds, where there is no view to receive it.
         *
         * @param event The touch screen event being processed.
         *
         * @return Return true if you have consumed the event, false if you haven't.
         * The default implementation always returns false.
         */
        public boolean onTouchEvent(MotionEvent event) {
            //超出可触摸边界就直接finish掉
            if (mWindow.shouldCloseOnTouch(this, event)) {
                finish();
                return true;
            }
    
            return false;
        }
    

    传递流程:
    Activity:dispatchTouchEvent--->PhoneWindow:superDispatchTouchEvent--->DecorView:superDispatchTouchEvent--->ViewGroup:dispatchTouchEvent
    如果子view没有消费触摸事件,最终就是走了Activity的onTouchEvent()的事件分发

    二、事件分发:ViewGroup:dispatchTouchEvent()

    step1: 在DOWN的时候,先调用了cancelAndClearTouchTargets(ev);和resetTouchState();清楚掉之前的点击状态并重置标记位;

    ViewGroup--line:2559
    // Handle an initial down.
                if (actionMasked == MotionEvent.ACTION_DOWN) {
                    // Throw away all previous state when starting a new touch gesture.
                    // The framework may have dropped the up or cancel event for the previous gesture
                    // due to an app switch, ANR, or some other state change.
                    cancelAndClearTouchTargets(ev);
                    resetTouchState();
                }
    

    step2:检测是否需要拦截。这里定义了一个boolean intercepted来记录是否需要拦截

    line:2567
    // Check for interception.
                final boolean intercepted;
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                  //用来禁止viewGroup拦截除了down以外的事件,
                  //通过子view调用requestDisallowInterceptTouchEvent()方法来控制
                    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 {
                    // There are no touch targets and this action is not an initial down
                    // so this view group continues to intercept touches.
                    intercepted = true;
                }
    

    step3:判断不是DOWN事件后,通过一个for循环,拿到子view,又进行了一个判断canViewReceivePointerEvents(),判断能否接收到点击事件。

    line:2647
    //如果不可见或在执行动画或不在可触摸区域范伟之内,就不可响应触摸事件,continue
    if (!canViewReceivePointerEvents(child) || 
    !isTransformedTouchPointInView(x, y, child, null)) {
          ev.setTargetAccessibilityFocus(false);
          continue;
     }
    
    line:2940
    /**
         * Returns true if a child view can receive pointer events.
         * 如果是可见的且没有在执行动画,就可以接收点击事件
         * @hide
         */
        private static boolean canViewReceivePointerEvents(@NonNull View child) {
            return (child.mViewFlags & VISIBILITY_MASK) == VISIBLE
                    || child.getAnimation() != null;
        }
    

    当可以接收点击事件,调用该方法,传入子view,当child不为空的时候,调用子view的dispatchTouchEvent(),交给子view处理。

    line:2662
    if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {...}
    
    line:2988
    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 (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
                event.setAction(oldAction);
                return handled;
            }
            ...
    }
    

    走完step1—step3,就完成了viewGroup到view的事件传递,并返回了一个handled的布尔值,表示子view消耗了点击事件。
    小结:dispatchTouchEvent()是用来分发的,最终的处理都是在onTouchEvrent()

    ViewGroup_1.png ViewGroup_2.png ViewGroup_3.png ViewGroup_4.png

    三 、View:dispatchTouchEvent()

    View--line:12509
    /**
         * 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) {
            ...
                //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;
                }
            }
          ...
            return result;
        }
    

    由上面源码可以得出,onTouchEvent()和onTouch()方法优先级及控制关系:
    ①如果onTouch()方法返回值是true(事件被消费)时,则onTouchEvent()方法将不会被执行;
    ②只有当onTouch()方法返回值是false(事件未被消费,向下传递)时,onTouchEvent方法才被执行。

    View---line:13718
    public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
    
            //clickable标记view是否可以被消费
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
            if ((viewFlags & ENABLED_MASK) == DISABLED) {
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                }
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                // A disabled view that is clickable still consumes the touch
                // events, it just doesn't respond to them.
                return clickable;
            }
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
          ...
      }
    

    clickable标记view可否消费事件,哪怕view是disable得,但只要它可点击或长按,就仍然消费事件,只是说没有消费他们。

    View--onTouchEvent--ACTION_UP line:13775
    //如果设置了长按事件,且长按事件返回true,则不会执行onClick事件
    if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                                // 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)) {
                                        performClickInternal();
                                    }
                                }
                            }
    

    View的长按事件在ACTION_DOWN中处理,UP中处理点击事件,如果500ms内没有触发UP事件,则长按事件会执行(设置了长按监听器)。如果设置了长按事件,且长按事件返回true,则不会执行onClick事件,返回的false,则长按点击事件都会执行。

    由此可见,给View设置监听OnTouchListener时,重写的onTouch()方法,其优先级比onTouchEvent()要高,假如onTouch方法返回false,会接着触发onTouchEvent,反之onTouchEvent方法不会被调用。内置诸如click事件的实现等等都基于onTouchEvent,假如onTouch返回true,这些事件将不会被触发。OnClickListener,其优先级最低,即处于事件传递的尾端。
    _
    _
    _
    _

    总结

    类似领导向下属分配任务,逐级下传,中途遇到处理不了就报告上级。


    事件分发流程 .png
    1. 一个事件序列从手指接触屏幕到手指离开屏幕,在这个过程中产生一系列事件,以DOWN事件开始,中间含有n个MOVE事件,以UP事件结束;
    2. 正常情况下,一个事件序列只能被一个View拦截并消耗;
    3. 某个View一旦决定拦截,那么这个事件序列都将由它的onTouchEvent处理,并且它的onInterceptTouchEvent不会再调用;
    4. 某个View一旦开始处理事件,如果它不消耗ACTION_DOWN事件(onTouchEvent返回false),那么同一事件序列中其他事件都不会再交给它处理。并且重新交由它的父元素处理(父元素onTouchEvent被调用);
    5. 事件的传递过程是由外向内的,即事件总是先传递给父元素,然后再由父元素分发给子View,通过requestDisallowInterceptTouchEvent反法可以在子View中干预父元素的事件分发过程,但ACTION_DOWN除外
    6. ViewGroup默认不拦截任何事件,即onInterceptTouchEvent默认返回false。View没有onInterceptTouchEvent方法,一旦有点击事件传递给它,那么它的onTouchEvent方法就被调用;
    7. VIew的onTouchEvent默认会消耗事件(返回true),除非它是不可点击的(clickable和longClickable同时为false)。View的longClickable默认都为false,clickable要分情况,比如Button的clickable默认为true,TextView的clickable默认为false;
    8. View的enable属性不影响onTouchEvent的默认返回值。哪怕一个View是disable状态,只要它的clickable或者longClickable有一个为true,那么它的onTouchEvent就返回true;
    9. onClick会响应的前提是当前View是可点击的,并且收到了ACTION_DOWN和ACTION_UP事件,并且受长按事件影响,当长按事件返回true,onClick不会响应;
    10. onLongClick在ACTION_DOWN里判断是否进行响应,要想执行长按事件该View必须是longClickable的并且设置了OnLongClickListener;

    onInterceptTouchEvent()方法返回值

    一般重载这个函数地方就是你自定义了一个布局,extends LinearLayout等等布局,一般除了自己的业务处理外,返回值只有两种。
    第一种:返回值为false
    作用:让自定义布局上面的所有子view 例如button imageview 等可以被点击

    第二种:直接返回true
    作用:让自定义布局上面的所有子view不可以被点击

     @Override  
        public boolean onInterceptTouchEvent(MotionEvent ev) {  
            return true;  
        }
    

    分分钟明白onTouchEvent事件分发

    首先确定有三种,由内向外依次为:
    1、View自己的onTouchEvent
    2、ViewGroup的onTouchEvent,由于要管理它的子View的onTouchEvent,所以多了个onInterceptTouchEvent(鼓励重载这个而不是dispatchTouchEvent,因为后者是对ViewGroup共性的提取,前者才是针对个例)
    3、Activity的onTouchEvent
    三者都是先处理setOnTouchEvent的onTouch事件,返回true表示不想下传递,就不进入到onTouchEvent中了

    先说ViewGroup的onInterceptTouchEvent,两种情况:
    1.返回值为True,代表拦截这次事件,直接进入到ViewGroup的onTouchEvent中,就不会进入到View的onTouchEvent了
    2.返回值为False,代表不拦截这次事件,不进入到ViewGroup的onTouchEvent中,直接进入到View的onTouchEvent中

    再说三者的onTouchEvent
    1.View的onTouchEvent返回为false表示view处理完onTouchEvent后不消费这次事件,那么这个事件就会继续传递到他的上一层ViewGroup的onTouchEvent事件中,返回true的话就传递完毕,进入不到ViewGroup的onTouchEvent中了

    2.ViewGrop的onTouchEvent返回为false表示这个ViewGroup处理完onTouchEvent后不消费这次事件,这个事件就会继续传递到activity的onTouchEvent中,返回为true的话就传递完毕,进入不到activity的onTouchEvent中了

    3.activity的onTouchEvent,就这样吧 `(∩_∩)′

    总之,如果最里层的返回false就会交给他的上一层处理,否则就会消费这次事件,停止传递,over!

    相关文章

      网友评论

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

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