美文网首页
Android 事件机制详情(事件的分发、拦截、处理)

Android 事件机制详情(事件的分发、拦截、处理)

作者: 我要离开浪浪山 | 来源:发表于2023-04-29 17:46 被阅读0次

1、事件在Android中的传递顺序

Activity--> Window-->DecorView --> 布局View

916045a8d8a1e83c80bc1668d7ed63b.png

或者说以上顺序是事件在应用层的传递顺序。如果要说整个事件的传递机制,是Android底层收到触摸屏的事件后,使用socket跨进程通信,用InputDispatcher将事件发送给APP进程,由主线程的Looper去取出消息进行处理。

2、事件的传递规则

一个点击事件,或者说触摸事件,被封装为了一个MotionEvent。事件的分发主要由三个重要的方法来完成:

1、分发:dispatchTouchEvent;
2、拦截:onInterceptTouchEvent;
3、处理:onTouchEvent;

如果是ViewGroup容器类view,则以上三个方法都会用到。但是如果是View类型的,不能包含子view,那就没有第二个拦截方法,因为没有子view的话,拦截方法的就是多余的,只有ViewGroup才会有拦截。

MotionEvent的类型

MotionEvent的类型.png d457e71eb582153cdc5266cb8ccb8df.png

1、public boolean dispatchTouchEvent(MotionEvent ev)

此方法用来处理事件的分发,当事件传递给当前view时,首先就是通过调用此方法来进行传递的,如果当前view锁包含的子view的dispatchTouchEvent方法或者当前view的onTouchEvent处理了事件, 通常返回true, 表示事件已消费。如果没有处理则返回false。

2、public boolean onInterceptTouchEvent(MotionEvent ev)

此方法用来判断某个事件是否需要拦截,如果某个view拦截了此事件,那么同时在这个事件序列中,此方法不会被再次调用,因为会把当前view赋值给mFirstTouchTarget对象(原本为null),后续父必问判断mFirstTouchTarget != null时,就会去调用它的onTouchEvent方法,交给mFirstTouchTarget处理事件。

3、public boolean onTouchEvent(MotionEvent ev)

用来处理事件,如果事件被消耗了,通常就返回true, 如果不做处理,否则返回false,并且在同一个时间序列中,当前view不会再接受到事件。

以上三个方法的关系可以用以下伪代码来表示:

1c6e670d999ea47590d74d1561720cd.png

根据以上伪代码和图片展示的流程图,我们梳理一下从根ViewGroup(也就是DecorView)往下传递事件的过程:

  • 首先,事件产生后,通过调用根ViewGroup的dispatchTouchEvent方法,
  • 然后,如果这个ViewGroup要拦截事件, 则它的onInterceptTouchEvent返回true,然后事件交给它的onTouchEvent处理,不再进行传递。如果不拦截,则继续调用子view的dispatchTouchEvent方法,继续往下传递,往下递归,直到最终被处理。
  • 如果没有任何一个view处理事件, 则最终又会回调给Activity的onTouchEvent方法,如果Activity也不处理,则此事件结束,且没做任何处理。

3、事件传递的源码分析

1、Activity对事件的传递

前面已经讲到,APP层的事件传递是从Activity看的,首先就是调用Activity的dispatchTouchEvent, 源码如下:

public boolean dispatchTouchEvent(MotionEvent ev) {
   if (ev.getAction() == MotionEvent.ACTION_DOWN) {
       onUserInteraction();   //注释1
    }
if (getWindow().superDispatchTouchEvent(ev)) {
     return true;    //注释2
    }
   return onTouchEvent(ev);//注释3
}

注释1: 是Activity的方法,如果当事件开始传递前,我们需要额外处理一些操作,可以用onUserInteraction()中进行处理,这本身是一个空方法;

注释2:getWindow返回的就是DecorView对象,相当于最顶层的ViewGroup,然后就开始往下层的view传递, 如果事件在view的传递中被处理,则返回true,否则就调用注释3的代码;

注释3:如果事件在view的传递中未被处理,则调用Activity自己的onTouchEvent方法。

2、View对事件的传递

在上面3.1小节讲到, 通过getWindow().superDispatchTouchEvent(ev),把事件传递给了DecorView。其实在我们做APP开发时,有时就会用到

(ViewGroup)getWindow().getDecorView().findViewById(android.R.id.content).getChildAt(0) 

这种方式来获取我们给Activity的布局view。

DecorView以前是PhoneWindow的一个内部类,不过现在已经独立成单独的一个类了,查看它的superDispatchTouchEvent方法代码

public boolean superDispatchTouchEvent(MotionEvent event) {
      return super.dispatchTouchEvent(event);
}

发现直接使用的父类,也就是ViewGroup的dispatchTouchEvent方法,这和我们前面的分析就一一对上了,我们进入ViewGroup的dispatchTouchEvent方法,
代码很长,先看这一段代码:

final boolean intercepted;
if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
     final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
     if (!disallowIntercept) {
         // restore action in case it was changed
           intercepted = onInterceptTouchEvent(ev);ev.setAction(action); 
      } else {
         intercepted = false;
      }
} else {
     intercepted = true;
}

这一段代码是ViewGroup用来判断是否需要拦截事件,如果为ACTION_DOWN或者mFirstTouchTarget != null,则进入判断。mFirstTouchTarget是个什么东东呢? 从后面代码可以看出,它是ViewGroup的子view,即当事件序列已经由子view进行处理的话,则父view不会再进行拦截。

另外就是FLAG_DISALLOW_INTERCEPT这个标记位,一般用于子view中,如果子view拦截了事件,则这个标记位就会设置为1,以便当前时间序列所有事件都将交给子view,当时当再次传来ACTION_DOWN事件时(也就是一个新的时间序列),这个标记位就会被重置为0。

也就是在上一段代码的前一个位置,有这么一段代码:

if (actionMasked == MotionEvent.ACTION_DOWN){
     //如果为ACTION_DOWN,则重置标记位
     cancelAndClearTouchTargets(ev);resetTouchState();
}

所以以上两段代码可以得出:

  • 1、在一个完整的时间序列中,当某个子View拦截ACTION_DOWN后,后续所有事件都会交给它处理,而父view也不会再去调用onInterceptTouchEvent方法;
  • 2、当新来到一个事件序列时(也就是以ACTION_DOWN开头),会重置mFirstTouchTarget以及标记位FLAG_DISALLOW_INTERCEPT等;

接着往下看dispatchTouchEvent的其他代码段,当ViewGroup不拦截事件时的处理,那么事件就会分发给它的子view:

 final View[] children = mChildren;
        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;
            }
            if (!canViewReceivePointerEvents(child) //注释3.1|| !isTransformedTouchPointInView(x, y, child, null)) {
            //注释3.2ev.setTargetAccessibilityFocus(false);
            continue;
        }

        newTouchTarget = getTouchTarget(child);
        if (newTouchTarget != null) {
            newTouchTarget.pointerIdBits |= idBitsToAssign;
            break;
        }

        resetCancelNextUpFlag(child);
        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
            //注释3.3// Child wants to receive touch within its bounds.
            mLastTouchDownTime = ev.getDownTime();
            if (preorderedList != null) {
                childIndex points into presorted list, find original indexfor ( int j = 0;
                j<childrenCount ;
                j++){
                    if (children[childIndex] == mChildren[j]) {
                        mLastTouchDownIndex = j;
                        break;
                    }
                }
            } else {
                mLastTouchDownIndex = childIndex;
            } mLastTouchDownX = ev.getX();
            mLastTouchDownY = ev.getY();
            newTouchTarget = addTouchTarget(child, idBitsToAssign);
            //注释3.4
            alreadyDispatchedToNewTouchTarget = true;
            break;
        }

上述代码中:

注释3.1: 用来判断view是否可见,是否在播放动画, 因为不可见或播动画都无法获得焦点;
注释3.2: 用来判断子view是否在点击区域中,没在点击区域,自然不用接收事件;
注释3.3: 实际上dispatchTransformedTouchEvent方法就是调用子view的dispatchTouchEvent方法,继续让子view分发事件;
注释3.4: 完成对mFirstTouchTarget的赋值;

这样,就完成了整个View树对事件的传递。

再往后看:

  if (mFirstTouchTarget == null) {
            handled = dispatchTransformedTouchEvent(ev, canceled, null, TouchTarget.ALL_POINTER_IDS);//注释3.5
        } else {
            TouchTarget predecessor = null;
            TouchTarget target = mFirstTouchTarget;
            while (target != null) {....}
        }

如果mFirstTouchTarget == null, 则没有子view处理事件,则交给父view处理, 否则就去子view处理事件。

到此,应用层的整个事件传递机制就分析完了。

相关文章

网友评论

      本文标题:Android 事件机制详情(事件的分发、拦截、处理)

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