Android 事件分发机制

2020-04-09  本文已影响0人  打杂的_e2c9

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

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


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

dispatchTouchEvent onInterceptTouchEvent onTouchEvent
Activity
ViewGroup
View

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

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

事件分发流程图

事件分发流程图.png

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

onTnerceptTouchEvent

onTouchEvent

事件分发源码解析

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

// 点击事件产生后,会直接调用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 时执行的,则处于时间传递的最后一个环节

面试题

上一篇下一篇

猜你喜欢

热点阅读