美文网首页Android自定义View
Android事件分发机制

Android事件分发机制

作者: leilifengxingmw | 来源:发表于2019-07-12 07:57 被阅读0次

1.点击事件发生后,事件先传到Activity、再传到ViewGroup、最终再传到 View。要想充分理解Android分发机制,本质上是要理解:

  • Activity对点击事件的分发机制
  • ViewGroup对点击事件的分发机制
  • View对点击事件的分发机制

Activity的事件分发机制

当一个点击事件发生时,事件最先传到 ActivitydispatchTouchEvent() 进行事件分发

Activity的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent ev) {
    //注释1 ,先调用Window的superDispatchTouchEvent方法
    if (getWindow().superDispatchTouchEvent(ev)) {
        return true;
    }
    //注释2,如果Window没有处理事件,则调用onTouchEvent
    return onTouchEvent(ev);
}

Activity首先将事件交给Window进行分发,如果返回true,那么这次事件分发结束。返回false,说明没有View对事件进行处理,那么就调用Activity的onTouchEvent方法。

注释1处,获取当前Activity的window对象,Window类是抽象类,其唯一实现类是PhoneWindow类;

PhoneWindow类的superDispatchTouchEvent()方法

@Override
public boolean superDispatchTouchEvent(MotionEvent event) {
    //调用DecorView的superDispatchTouchEvent方法
    return mDecor.superDispatchTouchEvent(event);
}

DecorView的superDispatchTouchEvent方法

/**
 * 
 * a. DecorView类是PhoneWindow类的一个内部类
 * b. DecorView继承自FrameLayout,是所有界面的父类
 * c. FrameLayout是ViewGroup的子类,故DecorView的间接父类 = ViewGroup
 */
public boolean superDispatchTouchEvent(MotionEvent event) {
   //调用父类的方法FrameLayout的dispatchTouchEvent()
   return super.dispatchTouchEvent(event);
}

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
    
}

FrameLayout并没有覆盖ViewGroup的dispatchTouchEvent()方法,所以这个时候事件已经到了ViewGroup了。

ViewGroup的事件分发机制

点击事件到达ViewGroup以后,会调用ViewGroup的dispatchTouchEvent方法,然后逻辑是这样的:

  1. 如果顶级ViewGroup拦截事件,即onInterceptTouchEvent方法返回true,那么事件就由ViewGroup处理。注意,ViewGroup的onInterceptTouchEvent方法默认是返回false的。

  2. 如果ViewGroup设置了OnTouchListener,则OnTouchListener的onTouch回调方法会被调用。如果OnTouchListener的onTouch回调方法返回true,事件分发结束。如果OnTouchListener的onTouch回调方法返回false,那么ViewGroup的onTouchEvent方法会被调用。

  3. 如果ViewGroup设置了OnClickListener,在ViewGroup的onTouchEvent方法中,OnClickListener的onClick回调会被调用。

  4. 如果ViewGroup不拦截事件,则事件会传递给它的子View,这时子View
    的dispatchTouchEvent方法会被调用。如果子View是一个View的时候,这个时候事件分发就到View了。

下面我们看一下ViewGroup的dispatchTouchEvent方法的部分代码逻辑

@Override
public boolean dispatchTouchEvent(MotionEvent ev) {
       
   //...
    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // 处理初始化的down事件
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // 当触摸手势开始的时候丢弃所有先前的状态 
           // 由于app切换,ANR,或者一些其他的状态改变,系统框架可能已经丢弃了先前触摸手势的up和cancel事件
          //注释1处,取消和清除所有的触摸状态
            cancelAndClearTouchTargets(ev);
          //注释2处,
            resetTouchState();
        }

        // 注释3处,检查是否拦截事件
        final boolean intercepted;
        if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
            //检查是否不允许拦截事件
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
            if (!disallowIntercept) {
                //允许拦截,调用onInterceptTouchEvent方法
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {//不允许拦截
                intercepted = false;
            }
        } else {
            // 如果没有触摸事件处理者,并且不是一个down事件,当前ViewGroup会继续拦截
            // 所以事件会继续由当前viewGroup处理。
            intercepted = true;
        }

        // ...
        // Check for cancelation.
        final boolean canceled = resetCancelNextUpFlag(this)
                    || actionMasked == MotionEvent.ACTION_CANCEL;

        // Update list of touch targets for pointer down, if needed.
        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        //声明事件处理者newTouchTarget
        TouchTarget newTouchTarget = null;
        //是否已经将事件交给子View进行处理了
        boolean alreadyDispatchedToNewTouchTarget = false;
        //注释4处,如果事件没有取消,并且当前ViewGroup没有拦截事件
        if (!canceled && !intercepted) {
            // ...
           // 注释5处,这里可以认为只有ACTION_DOWN事件,才会进入这个if判断,
            if (actionMasked == MotionEvent.ACTION_DOWN
                        || (split && actionMasked == MotionEvent.ACTION_POINTER_DOWN)
                        || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                final int actionIndex = ev.getActionIndex(); // always 0 for down
                final int idBitsToAssign = split ? 1 << ev.getPointerId(actionIndex)
                            : TouchTarget.ALL_POINTER_IDS;

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    // 找到一个可以接受事件的子View
                    // 从前到后遍历Child
                    final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                    final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                    final View[] children = mChildren;
                    //注释6处
                    for (int i = childrenCount - 1; i >= 0; i--) {
                        final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                        //子View
                        final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);
                      //注释7处,如果不能接收事件,就continue。
                       if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                            ev.setTargetAccessibilityFocus(false);
                            continue;
                        }
                        //...
                        //注释8处,获取事件处理者
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            //如果newTouchTarget不为null,跳出循环。
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }

                        //注释9处,将事件交给子View处理,传入的第三个参数child不为null
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            //...
                            //注释10处,为newTouchTarget和mFirstTouchTarget赋值
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            //已经将事件交给newTouchTarget处理了
                            alreadyDispatchedToNewTouchTarget = true;
                            //跳出循环
                            break;
                        }
                    }
                }
                //...
            }
        }

        // 注释11处,mFirstTouchTarget == null表示没有子View处理事件
        if (mFirstTouchTarget == null) {
            // 没有子View处理事件。有两种情况,一种是当前ViewGroup拦截了事件,另一种情况是子View都没有处理事件。
           //那么就交给当前ViewGroup处理事件,注意我们调用下面的方法,传入的第三参数是null。
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
        } 
    }
    //... 
    return handled;
}


注释2处,调用了resetTouchState方法

private void resetTouchState() {
    clearTouchTargets();
    resetCancelNextUpFlag(this);
    //这里重置了是否允许ViewGroup拦截事件的标志FLAG_DISALLOW_INTERCEPT
    mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    mNestedScrollAxes = SCROLL_AXIS_NONE;
}

在每次down事件到来的时候,都会调用该方法,重置所有的状态准备新一轮的触摸事件分发。ViewGroup的是否允许拦截的标志也会被重新置为允许拦截。也就是说如果是down事件,ViewGroup总是会调用onInterceptTouchEvent方法。但是ViewGroup的onInterceptTouchEvent方法默认是返回false的。

注释3处,ViewGroup会在actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null的情况下判断是否要拦截事件。mFirstTouchTarget != null是什么意思呢?当事件由ViewGroup的子元素成功处理时,mFirstTouchTarget会被赋值并指向该子元素。

我们看一下这段代码

 if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
    //检查是否不允许拦截事件
    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;
      }

如果子View通过调用ViewGroup的requestDisallowInterceptTouchEvent方法,将是否允许ViewGroup拦截事件的标记置为FLAG_DISALLOW_INTERCEPT,那么说明此时子View想要处理事件,ViewGroup将不会拦截事件(down事件也不会拦截),而是将事件交给子View处理。

@Override
public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {

    if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
        // We're already in this state, assume our ancestors are too
        return;
    }

    if (disallowIntercept) {
        mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
    } else {
        mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
    }

    // Pass it up to our parent
    if (mParent != null) {
        mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
    }
}

如果mFirstTouchTarget != null条件满足,move,up,事件到来,如果没有将ViewGroup拦截事件的标记置为FLAG_DISALLOW_INTERCEPT,那么还是允许ViewGroup拦截事件的,即还会走到onInterceptTouchEvent方法里。我们看看这个方法做了什么。

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())) {
        //默认是不会返回true的。
        return true;
    }
    return false;
}

这里我们注意:

  • onInterceptTouchEvent方法并没有处理move和up事件的逻辑,这里也说明ViewGroup默认是不拦截move和up事件的。
  • 默认情况下,即使是down事件,这个方法返回的也是false,这点要格外注意。
  • 如果ViewGroup想要拦截事件的话需要重写这方法,比如RecyclerView

注释6 处开始遍历子View,查找是否有子View可以接收事件。
注释7处,如果子View不能接收事件,就continue。
注释8处,获取处理者,如果已经存在处理者,就跳出循环。
注释9处,调用dispatchTransformedTouchEvent方法,将事件交给子View处理,传入的第三个参数child不为null。

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    //...
    if (child == null) {
            //如果child为null,就由当前ViewGrouop处理事件,
           //直接调用父类View的dispatchTouchEvent方法
            handled = super.dispatchTouchEvent(transformedEvent);
    } else {
        final float offsetX = mScrollX - child.mLeft;
        final float offsetY = mScrollY - child.mTop;
        transformedEvent.offsetLocation(offsetX, offsetY);
        if (! child.hasIdentityMatrix()) {
            transformedEvent.transform(child.getInverseMatrix());
        }
        //child不为null,由child处理事件
        handled = child.dispatchTouchEvent(transformedEvent);
    }
    return handled;
}

注释10处,在addTouchTarget方法里,为newTouchTarget和mFirstTouchTarget赋值,并将alreadyDispatchedToNewTouchTarget置为true;然后跳出循环。

private TouchTarget addTouchTarget(View child, int pointerIdBits) {
    final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
    target.next = mFirstTouchTarget;
    mFirstTouchTarget = target;
    return target;
}

注释11处,mFirstTouchTarget == null表示没有子View处理事件,那么由当前ViewGroup处理事件,在上面的dispatchTransformedTouchEvent方法中已经看过了。

当由子View 处理事件的时候,如果子View是一个View,那么就事件分发就到了View的dispatchTouchEvent方法中。

View的事件分发机制

public boolean dispatchTouchEvent(MotionEvent event) {
        
        boolean result = false;
        //...

        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            //注释1处,调用mOnTouchListener的onTouch
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }
            //注释2处
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }
        //...
        return result;
    }

注释1处,如果View设置了OnTouchListener,则OnTouchListener的onTouch回调方法会被调用。如果OnTouchListener的onTouch回调方法返回true,事件分发结束。如果OnTouchListener的onTouch回调方法返回false,那么View的onTouchEvent方法会被调用。

注释2处,调用onTouchEvent方法。
如果View设置了OnClickListener,在View的onTouchEvent方法中,OnClickListener的onClick回调会被调用。

相关文章

网友评论

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

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