【Android】Android事件分发机制的学习笔记和理解

前言:

Android事件分发机制让我头疼了很久,很多概念容易混淆,在简书上看到一篇绝赞的文章,在阅读view源码的帮助下,终于可以说是大致搞懂了整个的流程,以下做一些笔记和自己的简略概括总结
这里附上原文地址,再次感谢作者 一文读懂Android View事件分发机制
首先确定几个面试官可能会问到的问题

  1. 事件分发机制的分发过程
  2. onTouchEvent,onTouch方法的区别
  3. 如何去理解dispatchTouchEvent 和 onInterceptTouchEvent,onTouchEvent
  4. 如何使用requestDisallowInterceptTouchEvent方法,如何调用这个方法可以解决子view和父view之间的冲突
  5. 各个方法的返回值都代表了什么,怎么去理解

一 事件分发机制的分发过程

引用原文一张流程图,秒懂:
事件分发流程
简而言之,就是Activity的dispatchTouchEvent()–>Window–>DecorView(顶级的ViewGrop)的dispatchTouchEvent()点击事件传到了 ViewGrop里,之后再由viewgrop传入他自己的子view里

ViewGrop:可以有很多子view ,所以有dispatchTouchEvent, onInterceptTouchEvent,onTouchEvent(除了分发和触摸事件,还有拦截事件)
View:只有dispatchTouchEvent ,onTouchEvent(只有分发和触摸事件)

参照源码可以更加方便的便于理解:

我们模拟一个情景,来方便理解,引用https://blog.csdn.net/qq_30379689/article/details/53967177这篇文章中的情形

在这里插入图片描述




    

        
    

--------------------- 
作者:Hensen_ 
来源:CSDN 
原文:https://blog.csdn.net/qq_30379689/article/details/53967177 
版权声明:本文为博主原创文章,转载请附上博文链接!

文章中只是说出了假如我们重写各个方法会出现的情况,并没有说明具体流程,下面我来根据view的源码,分析一下自己的思考过程。
直接分析viewgrop的源码,我把他大致简化一下

 public boolean dispatchTouchEvent(MotionEvent ev) {
        //省略
        boolean handled = false;
        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.
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }

            // Check for interception.
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                   // 这里通过requestDisallowInterceptTouchEvent 方法可以设置 FLAG_SPLIT_MOTION_EVENTS 这个标签,来控制是否拦截事件
                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;
            }

            // 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) {
                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) {

                // 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.
                        final ArrayList preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //这里是遍历所有的子view
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, 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,使得newTouchTarget 不为null
                            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);
                            //事件在这个dispatchTransformedTouchEvent方法里进行分发
                            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赋值
                                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();
                    }

                    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.
            //如果mFirstTouchTarget 不为空,即 这个viewgrop有子view,
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                // 事件在这个dispatchTransformedTouchEvent方法里进行分发,表示事件还未消耗
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } else {
              //省略
            }
          //省略
        return handled;
    }
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;
        }
        //省略
    }
 private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
        final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
        target.next = mFirstTouchTarget;
        mFirstTouchTarget = target;
        return target;
    }

View 的dispatchTouchEvent方法,在ViewGrop类里的dispatchTransformedTouchEvent方法里进行调用,调用方式:super.dispatchTouchEvent(event);

  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)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

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

1、dispatchTouchEvent(分发事件)

如果在MyViewGroup01的dispatchTouchEvent方法中返回true,表示需要在MyViewGroup01消费了整个事件,即不会再分发,也不会再处理。dispatchTouchEvent方法中返回true的打印信息

//分发过程
MyViewGroup02 dispatchTouchEvent
MyViewGroup02 onInterceptTouchEvent
MyViewGroup01 dispatchTouchEvent

如果在MyViewGroup01的dispatchTouchEvent方法中返回false,表示在MyViewGroup01点击事件在本层不再继续进行分发,并交由上层控件的onTouchEvent方法进行消费。dispatchTouchEvent方法中返回false的打印信息

//分发过程
MyViewGroup02 dispatchTouchEvent
MyViewGroup02 onInterceptTouchEvent
MyViewGroup01 dispatchTouchEvent
//处理过程
MyViewGroup02 onTouchEvent

作者:Hensen_
来源:CSDN
原文:https://blog.csdn.net/qq_30379689/article/details/53967177
版权声明:本文为博主原创文章,转载请附上博文链接!

分析:当MyViewGroup01的dispatchTouchEvent为true的时候,MyViewGroup02的dispatchTouchEvent 和onInterceptTouchEvent按照顺序执行,MyViewGroup01 的dispatchTouchEvent也能执行,但是MyViewGroup01的 dispatchTouchEvent直接返回了true,所以在第一次调用dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法的时候(代码115行),就直接返回了true,所以接着又会执行newTouchTarget = addTouchTarget(child, idBitsToAssign)(代码132行)这个方法,这个方法则是给mFirstTouchTarget赋了值,所以当mFirstTouchTarget == null时候才会执行的handled = dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)(代码158行)方法不会被执行,而最关键的view的onTouchEvent方法,就在chlid为null的时候会执行,所以当dispatchTransformedTouchEvent(ev, canceled, null,TouchTarget.ALL_POINTER_IDS)(代码161行)不执行的时候,就不会调用onTouchEvent方法,所以当MyViewGroup01的dispatchTouchEvent为true的时候,onTouchEvent不会执行,反之,则会执行

你可能感兴趣的:(Android)