View的事件处理机制源码分析

View的事件分发主要是针对MotionEvent事件的分发,下面通过Android源码一步步分析MotionEvent事件的分发过程。

当一个点击事件发生时,事件首先传递给当前activity,由当前activity的dispatchTouchEvent方法进行分发,下面看下该函数的源码

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

通过代码可以看出,事件的分发工作主要交给当前activity内部的Window进行分发,继续看Window的superDispatchTouchEvent方法,由于Window类的唯一实现是PhoneWindow类,所以下一步继续查看PhoneWindow的superDispatchTouchEvent方法,代码如下:

    @Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        return mDecor.superDispatchTouchEvent(event);
    }

可以看出PhoneWindow类什么也没做,只是将事件转交给mDecor处理,mDecor是DecorView类型的一个对象,DecorView继承自FrameLayout, FrameLayout又继承自ViewGroup,下面看下DecorView的superDispatchTouchEvent方法

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

可以看到,只是调用了父类的dispatchTouchEvent方法,由于FrameLayout没有重写该方法,所以最终还是调用ViewGroup中的方法。
下面重点分析一下ViewGroup中对事件分发处理的过程,主要看一下dispatchTouchEvent方法中的代码片段

            // Check for interception.
            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要不要对当前事件调用onInterceptTouchEvent,当事件为ACTION_DOWN时,一定会调用onInterceptTouchEvent,mFirstTouchTarget != null不为空的情况在于,当ViewGroup将ACTION_DOWN交给它的任一子View处理时,且该View的DispatchTouchEvent返回true时(即该View处理此ACTION_DOWN事件),则mFirstTouchTarget 会被指向该View。所以当同一事件序列的后续事件中的ACTION_MOVE,...,ACTION_UP事件到来的时候,onInterceptTouchEvent不会再被调用。
当ViewGroup不拦截事件时,事件会交由ViewGroup的某个子View处理,具体代码如下:

                      final View[] children = mChildren;
                      for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = customOrder
                                    ? getChildDrawingOrder(childrenCount, i) : i;
                            final View child = (preorderedList == null)
                                    ? children[childIndex] : preorderedList.get(childIndex);

                            // If there is a view that has accessibility focus we want it
                            // to get the event first and if not handled we will perform a
                            // normal dispatch. We may do a double iteration but this is
                            // safer given the timeframe.
                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                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();
                    }

是否将事件交由某个子View来处理,由以下两个函数做判断,canViewReceivePointerEvents和isTransformedTouchPointInView,前者判断子View是否可见以及是否在播放动画,后者判断点击事件的坐标是否落在子元素的区域内,如果这两点都满足,那么事件就交由该子元素处理,从而完成一轮事件的分发。此时会在dispatchTransformedTouchEvent里调用子元素的dispatchTouchEvent方法

        final int oldAction = event.getAction();
        if (cancel || oldAction == MotionEvent.ACTION_CANCEL) {
            event.setAction(MotionEvent.ACTION_CANCEL);
            if (child == null) {
                handled = super.dispatchTouchEvent(event);
            } else {
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

如果dispatchTransformedTouchEvent返回true,也即子View处理此ACTION_DOWN事件,那么执行下面if分支里的代码。

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;
                            }

如果子View处理了ACTION_DOWN事件,则在addTouchTarget方法里mFirstTouchTarget 会被赋值指向此子View, 代码如下

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

你可能感兴趣的:(View的事件处理机制源码分析)