View事件分发机制

1、View和ViewGroup之间的关系

在android中,ViewGroup 继承了View,也就是说android中控件全部是View,不管是TextView,Button,ImageView还是五大布局等,只不过ViewGroup可以放置其他的View和ViewGroup,而View是不可以的

2、View中的事件序列

在android中,一次操作(点击,触摸等)会产生一个事件序列,这个事件序列组成部分为:一个MotionEvent.ACTION_DOWN事件,0到多个MotionEvent.ACTION_MOVE事件,一个MotionEvent.ACTION_UP或是MotionEvent.ACTION_CANCEL事件

事件分发机制涉及的主要方法

dispatchTouchEvent(MotionEvent ev): 对事件进行分发,返回值表示是否消费当前事件,返回值受该View的OnTouchEvent()和子View的dispatchTouchEvent()方法返回值影响

onInterceptTouchEvent(MotionEvent ev): 该方法只存在于ViewGroup中,返回值表示是否对当前事件进行拦截

onTouchEvent(MotionEvent ev): 该方法返回值表示是否消费当前事件

3、详细测试

两个简单的view分别为MRelativeLayout extends RelativeLayout和MTextView extends TextView

3.1 测试一:父View对事件拦截,并在onTouchEvent()对事件处理,这个时候父view的dispatchTouchEvent返回值是为true的
View事件分发机制_第1张图片
QQ截图20160727152331.png
3.2 测试二:父View对事件拦截,但在onTouchEvent()对事件没有处理
View事件分发机制_第2张图片
QQ截图20160727154144.png
3.3 测试三:父View对事件不拦截,子View对事件进行处理
View事件分发机制_第3张图片
QQ截图20160727164011.png
3.4 测试四:父view对事件不拦截,子view对事件也不处理
View事件分发机制_第4张图片
QQ截图20160727164637.png
3.5 简要总结
public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean isConsume = false;
        if(onInterceptTouchEvent(ev)){
            isConsume = onTouchEvent(ev);
        }else{
            isConsume = childView.dispatchTouchEvent(ev);
        }
        return isConsume;
    }

一个事件产生后,先由viewgroup的dispatchTouchevent对事件进行分发,dispatchTouchevent先调用onInterceptTouchEvent查看自己是否拦截了事件, 1): 如果拦截了,则再看自己的onTouchEvent对事件是否进行了消费处理,如果自己处理了,则返回true,事件到这里就结束了;如果没有处理,那么该事件则会往父view抛(onTouchEvent),如果父view都没有处理,则抛向window直到activity的onTouchEvent;2) 如果ViewGroup没有拦截事件,则交给子View的dispatchTouchEvent()来进行分发,如果子View也是ViewGroup,分发逻辑和上面一样,如果子View是一个View,那么则直接进入View的onTouchEvent()来确定事件是否被消耗掉,如果消耗了,返回true,如果没有消耗,则依次往父view抛,直到交给activity处理。

View事件分发机制_第5张图片
image.png
4、View源码分析
4.1 在android中,当用户触摸设备产生事件(MotionEvent)的时候,事件会先传递到activity,然后activity传给其内部的window,window再传给decorview,decorview传给布局中的相关view。
4.2 Activity.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            onUserInteraction();// 空实现
        }
        // window初始化在attach()方法中
        if (getWindow().superDispatchTouchEvent(ev)) {
          // 事件被Activity的下级给消费掉了
            return true;
        }
        // 事件在Activity的下级中没有被消费掉,交由Activity自己来处理
        return onTouchEvent(ev);
    }
4.3 Window.superDispatchTouchEvent()

Window是一个抽象类,根据Window类的注释能够知道,在整个android系统中,window只有一个子类:PhoneWindow,所以这里主要是看PhoneWindow对这个方法的实现。

@Override
    public boolean superDispatchTouchEvent(MotionEvent event) {
        // mDecor是一个DecorView
        return mDecor.superDispatchTouchEvent(event);
    }

可以看到,PhoneWindow把对事件的处理权限交给了DecorView,那么DecorView是什么呢?又是怎么初始化的呢?

4.4 DecorView

private final class DecorView extends FrameLayout implements RootViewSurfaceTaker
从这个定义可以看到DecorView其实就是一个FrameLayout而已。

4.5 DecorView的初始化过程

在activity中设置View是通过setContentView()方法来完成的,所以我们从这里开始分析一下是怎么操作的。
Activity.setContentView()

public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }

PhoneWindow.setContentView()

@Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
        // decor, when theme attributes and the like are crystalized. Do not check the feature
        // before this happens.
        if (mContentParent == null) {
          // 这里负责DecorView和mContentParent的初始化,至于mContentParent,它是:
          // ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
          // ID_ANDROID_CONTENT = com.android.internal.R.id.content
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {// Activity过渡动画
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            // 把我们自己的布局添加到mContentParent容器中
            mContentParent.addView(view, params);
        }
        //.........
    }

PhoneWindow.installDecor()

private void installDecor() {
       private void installDecor() {
        if (mDecor == null) {
            // 初始化DecorView
            mDecor = generateDecor();
            // .........
        }
        if (mContentParent == null) {
            // 初始化ViewGroup,也就是我们定义的布局的父容器
            mContentParent = generateLayout(mDecor);
            // .....................
        }
    }
    

PhoneWindow.generateDecor()

protected DecorView generateDecor() {
        return new DecorView(getContext(), -1);
    }

PhoneWindow.generateLayout()

protected ViewGroup generateLayout(DecorView decor) {
        //............ 

        mDecor.startChanging();
        // 这个名为in的View,用来决定整个window会显示成什么样子(Theme),
        View in = mLayoutInflater.inflate(layoutResource, null);
        decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        mContentRoot = (ViewGroup) in;
        // 从in这个view中找到我们自己定义的布局的父容器
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }
        //....................
        mDecor.finishChanging();
        return contentParent;
    }

这里贴两个名为in的View对应的布局文件
R.layout.screen_custom_title.xml



    
    

    
    
    

R.layout.screen_simple.xml



    
    
    
        
    
    

关于DectorView的初始化过程就是这样了,从这里也可以看出来整个View的层级关系:
Activity->window->decorview->SystemLayout->contentView->应用自定义布局对应view

4.6 继续事件分发:DecorView.superDispatchTouchEvent()
public boolean superDispatchTouchEvent(MotionEvent event) {
        // 因为DecorView是一个FrameLayout,因而这里就是ViewGroup.dispatchTouchEvent()
            return super.dispatchTouchEvent(event);
        }
4.7 ViewGroup.dispatchTouchEvent()
@Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(ev, 1);
        }

        // If the event targets the accessibility focused view and this is it, start
        // normal event dispatch. Maybe a descendant is what will handle the click.
        if (ev.isTargetAccessibilityFocus() && isAccessibilityFocusedViewOrHost()) {
            ev.setTargetAccessibilityFocus(false);
        }

        boolean handled = false;
        // 是否对事件进行分发,如果窗口被遮挡,直接返回false,否则返回true
        if (onFilterTouchEventForSecurity(ev)) {// 窗口没有被遮挡
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;

            // 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.
                // 如果监听到ACTION_DOWN事件,表明一次触摸的开始,那么一些东西就要清空和重置了。
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;// 是否对事件进行了拦截
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                // mFirstTouchTarget != null这个意思是:ViewGroup本身不拦截事件,而是把事件传给子View处理了。
                // 也就是说,一旦ViewGroup本身对事件进行了拦截处理,那么mFirstTouchTarget = null
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                // FLAG_DISALLOW_INTERCEPT:requestDisallowInterceptTouchEvent()中设置
                if (!disallowIntercept) {// 允许对事件进行拦截处理
                    intercepted = onInterceptTouchEvent(ev);// 
                    ev.setAction(action); // restore action in case it was changed
                } else {// 不允许对事件拦截
                    intercepted = false;
                }
            } else {// 如果ViewGroup本身对事件进行了处理,那么ACTION_MOVE,ACTION_UP等事件就直接被ViewGroup拦截了,不会再传给子View了
                // There are no touch targets and this action is not an initial down
                // so this view group continues to intercept touches.
                intercepted = true;
            }
            // 以上部分总结一下:当接收到ACTION_DOWN事件的时候,会把一些东西清空和重置
            
            // 对于一次触摸会产生一个事件序列,如果接收到action_down事件或者事件交给了子View来处理,
            // 在允许对事件进行拦截的情况下,ViewGroup的onInterceptTouchEvent()会被执行到并且只会执行到一次(ACTION_DOWN事件)。
// 所以这里要注意一点,如果ViewGroup本身对ACTION_DOWN事件进行了拦截,
// 那么对于ACTION_MOVE,ACTION_UP等事件,因为mFirstTouchTarget != null为false,
// actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null则返回false,
// 那么intercepted一直为true,因而这些事件是不会再传给子view的。
            
            // 从这里可以看到:
            // 如果ViewGroup本身对事件进行了处理,mFirstTouchTarget != null为false,那么onInterceptTouchEvent()只会被执行一次,
            // 后续事件ACTION_MOVE,ACTION_UP等都不会进入 onInterceptTouchEvent();
            // 而如果ViewGroup本事没有对事件进行处理,而是交给了子View来处理,
            // 那么mFirstTouchTarget != null为true,则事件序列中的每个事件都会进入onInterceptTouchEvent()
            // 验证测试3.3结果
            

            // If intercepted, start normal event dispatch. Also if there is already
            // a view that is handling the gesture, do normal event dispatch.
            if (intercepted || mFirstTouchTarget != null) {
                // ViewGroup本身对事件进行了拦截
                ev.setTargetAccessibilityFocus(false);
            }

            // 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;
            TouchTarget newTouchTarget = null;
            boolean alreadyDispatchedToNewTouchTarget = false;
            if (!canceled && !intercepted) {
                // 事件没有取消也没有被ViewGroup本身拦截:事件分发给子View

                // If the event is targeting accessiiblity focus we give it to the
                // view that has accessibility focus and if it does not handle it
                // we clear the flag and dispatch the event to all children as usual.
                // We are looking up the accessibility focused host to avoid keeping
                // state since these events are very rare.
                View childWithAccessibilityFocus = ev.isTargetAccessibilityFocus()
                        ? findChildWithAccessibilityFocus() : null;

                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;

                    // Clean up earlier touch targets for this pointer id in case they
                    // have become out of sync.
                    removePointersFromTouchTargets(idBitsToAssign);

                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        // Find a child that can receive the event.
                        // Scan children from front to back.
                        // 对子view进行排序处理,最上层到最下层
                        final ArrayList preorderedList = buildOrderedChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        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;
                            }

                            // 子View不可见没有播放动画,或者触摸点不在子view上
                            if (!canViewReceivePointerEvents(child)// 子view既不可见或也没有播放动画
                                    || !isTransformedTouchPointInView(x, y, child, null)) {// 触摸点在子view外
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                                
                            }

                            // 子View正在接收touch事件
                            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;
                            }
                            // 到这里newTouchTarget还是为null的
                            resetCancelNextUpFlag(child);
                            // 事件交由子view来处理了,这里的child是不为null的
                            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();
                                // mFirstTouchTarget 被赋值,验证了mFirstTouchTarget!=null表示事件交由子view来消费
                                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来处理,但是却没有找到这样的子view
                    if (newTouchTarget == null && mFirstTouchTarget != null) {
                        // Did not find a child to receive the event.
                        // Assign the pointer to the least recently added target.
                        newTouchTarget = mFirstTouchTarget;
                        while (newTouchTarget.next != null) {
                            newTouchTarget = newTouchTarget.next;
                        }
                        newTouchTarget.pointerIdBits |= idBitsToAssign;
                    }
                }
            }

            // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {//
                // No touch targets so treat this as an ordinary view.
// 这里的child为null,因而直接执行view的dispatchTouchEvent()方法,对事件进行处理,不再传给子view
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
                // Dispatch to touch targets, excluding the new touch target if we already
                // dispatched to it.  Cancel touch targets if necessary.
                TouchTarget predecessor = null;
                TouchTarget target = mFirstTouchTarget;
                while (target != null) {
                    final TouchTarget next = target.next;
                    if (alreadyDispatchedToNewTouchTarget && target == newTouchTarget) {
                        handled = true;
                    } else {
                        final boolean cancelChild = resetCancelNextUpFlag(target.child)
                                || intercepted;
                        if (dispatchTransformedTouchEvent(ev, cancelChild,
                                target.child, target.pointerIdBits)) {
                            handled = true;
                        }
                        if (cancelChild) {
                            if (predecessor == null) {
                                mFirstTouchTarget = next;
                            } else {
                                predecessor.next = next;
                            }
                            target.recycle();
                            target = next;
                            continue;
                        }
                    }
                    predecessor = target;
                    target = next;
                }
            }

            // Update list of touch targets for pointer up or cancel, if needed.
            if (canceled
                    || actionMasked == MotionEvent.ACTION_UP
                    || actionMasked == MotionEvent.ACTION_HOVER_MOVE) {
                resetTouchState();
            } else if (split && actionMasked == MotionEvent.ACTION_POINTER_UP) {
                final int actionIndex = ev.getActionIndex();
                final int idBitsToRemove = 1 << ev.getPointerId(actionIndex);
                removePointersFromTouchTargets(idBitsToRemove);
            }
        }

        if (!handled && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(ev, 1);
        }
        return handled;
    }
4.8 ViewGroup.dispatchTransformedTouchEvent()
/**
     * Transforms a motion event into the coordinate space of a particular child view,
     * filters out irrelevant pointer ids, and overrides its action if necessary.
     * If child is null, assumes the MotionEvent will be sent to this ViewGroup instead.
     */
    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;

        // Canceling motions is a special case.  We don't need to perform any transformations
        // or filtering.  The important part is the action, not the contents.
        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;
        }

        // Calculate the number of pointers to deliver.
        final int oldPointerIdBits = event.getPointerIdBits();
        final int newPointerIdBits = oldPointerIdBits & desiredPointerIdBits;

        // If for some reason we ended up in an inconsistent state where it looks like we
        // might produce a motion event with no pointers in it, then drop the event.
        if (newPointerIdBits == 0) {
            return false;
        }

        // If the number of pointers is the same and we don't need to perform any fancy
        // irreversible transformations, then we can reuse the motion event for this
        // dispatch as long as we are careful to revert any changes we make.
        // Otherwise we need to make a copy.
        final MotionEvent transformedEvent;
        if (newPointerIdBits == oldPointerIdBits) {// 触摸点和上次一样
            if (child == null || child.hasIdentityMatrix()) {
                if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    final float offsetX = mScrollX - child.mLeft;
                    final float offsetY = mScrollY - child.mTop;
                    event.offsetLocation(offsetX, offsetY);

                    handled = child.dispatchTouchEvent(event);

                    event.offsetLocation(-offsetX, -offsetY);
                }
                return handled;
            }
            transformedEvent = MotionEvent.obtain(event);
        } else {
            transformedEvent = event.split(newPointerIdBits);
        }

        // Perform any necessary transformations and dispatch.
        if (child == null) {// 子View不存在,则交给ViewGroup的父类View来处理了
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {//子View存在
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }
            // 子view dispatchTouchEvent()
            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }
4.8 ViewGroup.addTouchTarget()
private TouchTarget addTouchTarget(View child, int pointerIdBits) {
        TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
// mFirstTouchTarget 赋值了
        mFirstTouchTarget = target;
        return target;
    }
4.9 View.dispatchTouchEvent()
public boolean dispatchTouchEvent(MotionEvent event) {
        // If the event should be handled by accessibility focus first.
        if (event.isTargetAccessibilityFocus()) {
            // We don't have focus or no virtual descendant has it, do not handle the event.
            if (!isAccessibilityFocusedViewOrHost()) {
                return false;
            }
            // We have focus and got the event, then use normal event dispatch.
            event.setTargetAccessibilityFocus(false);
        }

        boolean result = false;

        if (mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onTouchEvent(event, 0);
        }

        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            // Defensive cleanup for new gesture
            stopNestedScroll();
        }

        if (onFilterTouchEventForSecurity(event)) {
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            // 优先执行OnTouchListener.onTouch()
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            // 在OnTouchListener为null时才执行onTouchEvent
           // 在onTouchEvent()中的ACTION_UP事件中判断ClickListener是否不为null,
// 如果 不为null,则执行onClick()
            if (!result && onTouchEvent(event)) {
                result = true;
            }
        }

        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }

        // Clean up after nested scrolls if this is the end of a gesture;
        // also cancel it if we tried an ACTION_DOWN but we didn't want the rest
        // of the gesture.
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }

        return result;
    }

以上,是View事件分发机制的基本过程,总得来说,当设备接收到事件的时候,先由activity来进行分发(dispatchTouchEvent())给window,window接收到事件的时候交给DecorView来处理,而DecorView本身是一个ViewGroup,因而存在自己拦截处理或是分发给子view来处理等几种情况,如果自己进行拦截处理(OnInterceptTouchEvent() return true),那么事件最终是否消费取决于ViewGroup.onTouchEvent(),并且事件是不会再传给子View的;如果事件交由子view来处理,那么事件是否消费则由子view来定了,这个逻辑基本和现有逻辑一致。view对于事件的处理,是直接由onTouchEvent来决定的,在View.onTouchEvent()中首先会判断是否有TouchListener,如果有,则直接执行onTouch,如果没有,则再看clickListener有没有,有,执行onClick。

你可能感兴趣的:(View事件分发机制)