事件分发源码分析

1.Activity对事件的分发过程

public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();
        }
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

点击事件用MotionEvent表示,事件先传递给Activity,由它的dispatchTouchEvent进行事件分发,具体由Window来完成,Window会将事件传递给Decor view(当前界面底层容器,即setContextView所设置的View的父容器)。

先分析dispatchTouchEvent:

可以看出,事件首先交给Activity附属的Window进行分发,返回true,整个循环结束,false表示没人处理该事件。所有View的onTouchEvent都返回false,那么Activity的onTouchEvent就会被调用。

2.接下来看事件是怎么传递给Group View的

Window的实现类是PhoneWindow类,所以再看PhoneWindow如何处理事件:

/frameworks/base/core/java/android/view/Window.java

/**
     * Used by custom windows, such as Dialog, to pass the touch screen event
     * further down the view hierarchy. Application developers should
     * not need to implement or call this.
     *
     */
    public abstract boolean superDispatchTouchEvent(MotionEvent event);

/frameworks/base/core/java/com/android/internal/policy/PhoneWindow.java

public class PhoneWindow extends Window implements MenuBuilder.Callback {
mDecor = (DecorView) preservedWindow.getDecorView();
@Override
public boolean superDispatchTouchEvent(MotionEvent event) { return mDecor.superDispatchTouchEvent(event); }
    @Override
    public final @NonNull View getDecorView() {
        if (mDecor == null || mForceDecorInstall) {
            installDecor();
        }
        return mDecor;
    }

通过(ViewGroup)getWindow().getDecorview.findViewById(android.R.id.context)).getChildAt(0)获取Activity设置的View,mDecor为getWindow().getDecorView()返回的View。通过setContentView设置View是它的一个子View。所以事件首先会传递给DecroView,由于DecorView继承自FrameLayout,且为父View,所以事件最终会传递给View。
\frameworks\base\core\java\com\android\internal\policy\DecorView.java

public class DecorView extends FrameLayout implements RootViewSurfaceTaker, WindowCallbacks {
public
boolean superDispatchTouchEvent(MotionEvent event) { return super.dispatchTouchEvent(event); }

进入 FrameLayout   /frameworks/base/core/java/android/widget/FrameLayout.java

3.顶级View对点击事件的分发过程

分发过程:首先一个事件到顶级的View(ViewGroup),会调用ViewGroup的dispatchTouchEvent,如果拦截,ViewGroup的onInterceptTouchEvent返回true,则事件由ViewGroup处理,具体会根据事件处理的优先级,消耗事件。返回false,则事件将传递给下一级View,依次类推

3.1.ViewGroup对事件的分发

首先看ViewGroup的dispatchTouchEvent方法


// 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();
}


final
boolean intercepted; 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; } } else { // There are no touch targets and this action is not an initial down // so this view group continues to intercept touches. intercepted = true; }

可见ViewGroup在ACTION_DOWN或者mFirstTouchTarget != null,其中mFirstTouchTarget != null,即,当事件由VIewGroup子控件处理时,mFirstTouchTarget会被指向子元素。
当然还有一种特殊情况:通过requestDisallowInterceptTouchEvent来设置的FLAG_DISALLOW_INTERCEPT标记位,它设置后ViewGroup无法拦截除了ACTIOND_DOWN以外的点击事件。为什么是ACTION_DOWN以外?是因为ViewGroup接收到ACTION_DOWN时,会重置一次标记位,导致子View设置无效。ViewGroup对待ACTION_DOWN事件,会调用自己的onInterceptTouchEvent方法来拦截

上面代码可以看出,当ViewGroup决定拦截事件后,后续的事件都将默认交给它处理,并且不会再调用onInterceptTouchEvent…
参考价值有两点:
第一、onInterceptTouchEvent 不是每次都会调用,如果我们需要在前一节点处理事务,要选择dispatchTouchEvent方法,但必须续保事件可以传到ViewGroup中。
第二、FLAG_DISALLOW_INTERCEPT标记位,可以帮我们解决滑动冲突的问题。

3.2ViewGroup对事件不拦截,事件如何向下分发

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x =
                                isMouseEvent ? ev.getXCursorPosition() : ev.getX(actionIndex);
                        final float y =
                                isMouseEvent ? ev.getYCursorPosition() : ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        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 (!child.canReceivePointerEvents()
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                continue;
                            }

                            newTouchTarget = getTouchTarget(child);
                            if (newTouchTarget != null) {
                                // Child is already receiving touch within its bounds.
                                // Give it the new pointer in addition to the ones it is handling.
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }

                            resetCancelNextUpFlag(child);
                            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;
                                }
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }

                            // The accessibility focus didn't handle the event, so clear
                            // the flag and do a normal dispatch to all children.
                            ev.setTargetAccessibilityFocus(false);
                        }
                        if (preorderedList != null) preorderedList.clear();
                    }

上述代码可见,首先遍历ViewGroup的每个子View,判断子view是否得到了点击事件。判断标准:1.点击事件的坐标是否落到了子元素的区域内;2.子元素是否在播动画如果满足这两个条件,则交由子元素处理。而dispatchTransformedTouchEvent实际调用了子元素的dispatchTouchEvent,这样就把事件传递给子View了,也将完成了一次循环。

 

posted @ 2023-03-01 17:23  xiaowang_lj  阅读(14)  评论(0编辑  收藏  举报