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

Android 事件分发机制

作者: 打杂的_e2c9 | 来源:发表于2020-04-09 17:46 被阅读0次

    转自:https://lrh1993.gitbooks.io/android_interview_guide/android/basis/Event-Dispatch.html

    事件分发涉及到的类 及 方法
    事件分发都分发了哪些事件
    事件分发流程图
    事件分发源码解析
    面试题


    事件分发涉及到的类 及 方法

    dispatchTouchEvent onInterceptTouchEvent onTouchEvent
    Activity
    ViewGroup
    View

    dispatchTouchEvent:事件分发
    onInterceptTouchEvent:事件拦截
    onTouchEvent :处理点击事件

    事件分发都分发了哪些事件

    • MotionEvent.ACTION_DOWN:按下View(所有事件的开始)
    • MotionEvent.ACTION_MOVE:滑动View
    • MotionEvent.ACTION_CANCEL:非人为原因结束本次事件
    • MotionEvent.ACTION_UP:抬起View(与DOWN对应)


      image.png

    事件分发流程图

    事件分发流程图.png

    此图只介绍了down 事件的传递流程,后续的move 和 up 事件还会随着down 传递流程的改变发生相应的变化:
    dispatchTouchEvent:

    • activity 中,当 Down 返回true,其他情况返回super,则其他事件会传递到activity 的onTouchEvent
    • viewGroup中 返回false,则其他事件不会传到此viewgroup,交友父控件的onTouchEvent处理,后续事件不会分发到该view

    onTnerceptTouchEvent

    • 当返回true 时,会由此viewgroup的onTouchevent处理,后续也不会分发到子view,且不会再处理该控件的onTnerceptTouchEvent(即该方法只要有一次返回true,即不再执行)
    • 当Down 返回super/false,其余的返回true,则down 会分发到子view,后续的一个事件会分发到子view中执行,但是此时子view中执行的是cancel事件,随后的事件不会分发到子view,而是由当前view处理

    onTouchEvent

    • 返回false ,之后的事件不会传到此view进行处理

    事件分发源码解析

    我们先了解下事件分发三个方法的伪代码:

    // 点击事件产生后,会直接调用dispatchTouchEvent()方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
    
        //代表是否消耗事件
        boolean consume = false;
    
    
        if (onInterceptTouchEvent(ev)) {
        //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
        //则该点击事件则会交给当前View进行处理
        //即调用onTouchEvent ()方法去处理点击事件
          consume = onTouchEvent (ev) ;
    
        } else {
          //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
          //则该点击事件则会继续传递给它的子元素
          //子元素的dispatchTouchEvent()就会被调用,重复上述过程
          //直到点击事件被最终处理为止
          consume = child.dispatchTouchEvent (ev) ;
        }
    
        return consume;
       }
    

    接下来我们看下具体的事件传播源码

    首先事件由activity 的dispatchTouchEvent 开始

    public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                // 实现屏保功能:
                //    a. 该方法为空方法
                //    b. 当此activity在栈顶时,触屏点击按home,back,menu键等都会触发此方法
                onUserInteraction();
            }
            // window 是一个抽象类,只有一个继承类PhoneWindow
            // mWindow 初始化 mWindow = new PhoneWindow(this, window, activityConfigCallback);
            if (getWindow().superDispatchTouchEvent(ev)) {
                return true;
            }
            return onTouchEvent(ev);
        }
    

    接下来我们看下PhoneWindow 的superDispatchTouchEvent

    public boolean superDispatchTouchEvent(MotionEvent event) {
            return 
    // mDecor 是DecorView一个对象,它的父类是FrameLayout
    mDecor.superDispatchTouchEvent(event);
        }
    

    DecorView 的 superDispatchTouchEvent

    public boolean superDispatchTouchEvent(MotionEvent event) {
            // DecorView 的父类是FrameLayout
            return super.dispatchTouchEvent(event);
        }
    

    FrameLayout 中没有实现dispatchTouchEvent ,直接使用的ViewGroup 的dispatchTouchEvent。
    下面我们来看下dispatchTouchEvent 的流程图:


    image.png

    所以接下来我们看下viewgroup的分发方法,在这里我们主要介绍其中两个主要的片段:

    /**
                 * 这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,
                 * 调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,
                 * 即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,
                 * 那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。
                 * 如果不是以上两种情况,即已经是MOVE或UP事件了,并且之前的事件没有对象进行处理,则设置成true,
                 * 开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,
                 * 那么接下来的一些列事件都不会交给他处理。如果VieGroup的onInterceptTouchEvent()第一次执行为true,
                 * 则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。
                 */
                if (actionMasked == MotionEvent.ACTION_DOWN
                        || mFirstTouchTarget != null) {
                    //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
                    //可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
                    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 {
                    // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
                    intercepted = true;
                }
    
    ...
    
    // 该片段作用:遍历了当前ViewGroup下的所有子View 赋值mFirstTouchTarget
      for (int i = childrenCount - 1; i >= 0; i--) {
             final int childIndex = getAndVerifyPreorderedIndex(
                       childrenCount, i, customOrder);
                  final View child = getAndVerifyPreorderedView(
                   preorderedList, children, childIndex);
    
                   // 如果当前视图无法获取用户焦点,则跳过本次循环
                   if (childWithAccessibilityFocus != null) {
                         if (childWithAccessibilityFocus != child) {
                                   continue;
                          }
                          childWithAccessibilityFocus = null;
                               i = childrenCount - 1;
                     }
     // 如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
                   if (!canViewReceivePointerEvents(child)
                                        || !isTransformedTouchPointInView(x, y, child, null)) {
                        ev.setTargetAccessibilityFocus(false);
                         continue;
                   }
    
                  newTouchTarget = getTouchTarget(child);
                  //孩子在其范围内已经收到了触摸,并退出整个循环。
                  if (newTouchTarget != null) {
                       newTouchTarget.pointerIdBits |= idBitsToAssign;
                        break;
                  }
    
                  resetCancelNextUpFlag(child);
                //如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
                // 际就是调用子元素的dispatchTouchEvent()方法
                /**
                 * if (child == null) {
                 *                 handled = super.dispatchTouchEvent(event);
                 *             } else {
                 *                 handled = child.dispatchTouchEvent(event);
                *             }
               */
              if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                   // Child wants to receive touch within its bounds.
                   mLastTouchDownTime = ev.getDownTime();
                   if (preorderedList != null) {
                   // childIndex points into presorted list, find original index
                    for (int j = 0; j < childrenCount; j++) {
                            if (children[childIndex] == mChildren[j]) {
                                   mLastTouchDownIndex = j;
                                   break;
                              }
                     }
             } else {
                  mLastTouchDownIndex = childIndex;
             }
                //获取TouchDown的x,y坐标
                mLastTouchDownX = ev.getX();
                mLastTouchDownY = ev.getY();
                //添加TouchTarget,则mFirstTouchTarget != null。
                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                 //表示以及分发给NewTouchTarget
                alreadyDispatchedToNewTouchTarget = true;
                 break;
     }
    

    接下来我们看下view 的 dispatchTouchEvent片段

    // 有mOnTouchListener 控件是可点击的,若onTouch返回true,则直接返回,不会执行onTouchEvent
    if (li != null && li.mOnTouchListener != null
                        && (mViewFlags & ENABLED_MASK) == ENABLED
                        && li.mOnTouchListener.onTouch(this, event)) {
                    result = true;
                }
    
                if (!result && onTouchEvent(event)) {
                    result = true;
                }
    

    view 中的onTouch 方法

    public boolean onTouchEvent(MotionEvent event) {  
        final int viewFlags = mViewFlags;  
        if ((viewFlags & ENABLED_MASK) == DISABLED) {  
            // 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;  
            }  
        }  
         //如果该控件是可以点击的就会进入到下两行的switch判断中去;
    
        if (((viewFlags & CLICKABLE) == CLICKABLE ||  
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  
        //如果当前的事件是抬起手指,则会进入到MotionEvent.ACTION_UP这个case当中。
    
            switch (event.getAction()) {  
                case MotionEvent.ACTION_UP:  
                    boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  
                   // 在经过种种判断之后,会执行到关注点1的performClick()方法。
                   //请往下看关注点1
                    if ((mPrivateFlags & 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 (!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)) {  
                //关注点1
                //请往下看performClick()的源码分析
                                    performClick();  
                                }  
                            }  
                        }  
                        if (mUnsetPressedState == null) {  
                            mUnsetPressedState = new UnsetPressedState();  
                        }  
                        if (prepressed) {  
                            mPrivateFlags |= PRESSED;  
                            refreshDrawableState();  
                            postDelayed(mUnsetPressedState,  
                                    ViewConfiguration.getPressedStateDuration());  
                        } else if (!post(mUnsetPressedState)) {  
                            // If the post failed, unpress right now  
                            mUnsetPressedState.run();  
                        }  
                        removeTapCallback();  
                    }  
                    break;  
                case MotionEvent.ACTION_DOWN:  
                    if (mPendingCheckForTap == null) {  
                        mPendingCheckForTap = new CheckForTap();  
                    }  
                    mPrivateFlags |= PREPRESSED;  
                    mHasPerformedLongPress = false;  
                    postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());  
                    break;  
                case MotionEvent.ACTION_CANCEL:  
                    mPrivateFlags &= ~PRESSED;  
                    refreshDrawableState();  
                    removeTapCallback();  
                    break;  
                case MotionEvent.ACTION_MOVE:  
                    final int x = (int) event.getX();  
                    final int y = (int) event.getY();  
                    // Be lenient about moving outside of buttons  
                    int slop = mTouchSlop;  
                    if ((x < 0 - slop) || (x >= getWidth() + slop) ||  
                            (y < 0 - slop) || (y >= getHeight() + slop)) {  
                        // Outside button  
                        removeTapCallback();  
                        if ((mPrivateFlags & PRESSED) != 0) {  
                            // Remove any future long press/tap checks  
                            removeLongPressCallback();  
                            // Need to switch from pressed to not pressed  
                            mPrivateFlags &= ~PRESSED;  
                            refreshDrawableState();  
                        }  
                    }  
                    break;  
            }  
    //如果该控件是可以点击的,就一定会返回true
            return true;  
        }  
    //如果该控件是不可以点击的,就一定会返回false
        return false;  
    }
    

    performClick :

    public boolean performClick() {
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
    
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
    
            notifyEnterOrExitForAutoFillIfNeeded(true);
    
            return result;
        }
    

    可以看到其中执行了OnClickListener.onClick,结合上边的代码可以知道onClick 是在ACTION_UP 时执行的,则处于时间传递的最后一个环节

    面试题

    • 事件传递涉及的方法和在不同组件中的表现
    • onTouchListener onTouchEvent onClickListener 执行关系
    • 如何拦截事件

    相关文章

      网友评论

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

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