事件分发源码分析

简介: 事件分发源码分析

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了,也将完成了一次循环。


目录
相关文章
|
Android开发 UED 容器
再谈事件分发
再谈事件分发
116 0
|
Android开发 容器 Python
DialogFragment源码分析
目录介绍 1.最简单的使用方法 1.1 官方建议 1.2 最简单的使用方法 1.3 DialogFragment做屏幕适配 2.源码分析 2.1 DialogFragment继承Fragment 2.
1184 0
|
Android开发 容器
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(三)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(三)
145 0
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(三)
|
Android开发
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(二)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(二)
184 0
|
Android开发
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )(一)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )(一)
234 0
|
Android开发 开发者
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(一)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 二 )(一)
238 0
|
Android开发
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )(三)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )(三)
111 0
|
Android开发
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )(二·)
【Android 事件分发】ItemTouchHelper 源码分析 ( OnItemTouchListener 事件监听器源码分析 )(二)
213 0
|
前端开发 Android开发
【Android 事件分发】ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )(二)
【Android 事件分发】ItemTouchHelper 事件分发源码分析 ( 绑定 RecyclerView )(二)
145 0