Android 事件分发总结

前言

在面试过程中Android的事件分发机制是经常被问到的知识点,今天对照源码简单分析一下
众所周知事件分发的顺序为Activity(Window)->ViewGroup->View去实现的主要的方法有三个dispatchTouchEvent 、onInterceptTouchEvent、onTouchEvent

首先我们必须要了解Touch事件,一共四种类型如下MotionEvent.ACTION_DOWN、MotionEvent.ACTION_UP、MotionEvent.ACTION_MOVE、MotionEvent.CANCEL 不清楚的可以去搜一下,事件分发都是从ACTION_DOWN 事件开始,所以这个是导火索

Activity 事件传递

上源码

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

Activity 中只有一个分发的方法dispatchTouchEvent,这个方法中分别由三个方法,
1、onUserInteraction()

	public void onUserInteraction() {
    }

这个方法里面什么都没有就不用管他

2、getWindow().superDispatchTouchEvent(ev)
直接看PhoneWindow的superDispatchTouchEvent

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

继续跟进

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

继续跟进发现dispatchTouchEvent 的实现是在ViewGroup中的,这个时候Activity和ViewGroup就联系在一起了
3、onTouchEvent Activity中事件消费的方法

   public boolean onTouchEvent(MotionEvent event) {
        if (mWindow.shouldCloseOnTouch(this, event)) {
            finish();
            return true;
        }
        return false;
    }

由此可以分析出 getWindow().superDispatchTouchEvent(ev) 只有返回true时,Activity才不会走onTouchEvent事件,交由ViewGroup去实现事件分发业务,接下来看ViewGroup的事件分发

ViewGroup 事件传递

直接看dispatchTouchEvent 方法,由于方法比较长就截取关键代码来分析了

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

铁铁们记住优先找ACTION_DOWN,这不就来了吗
一个关键参数intercepted 这个参数是通过onInterceptTouchEvent(ev)来赋值的,相信这个参数大家都熟悉了,源码如下

//ViewGroup中事件拦截的方法
public boolean onInterceptTouchEvent(MotionEvent ev) {
        if (ev.isFromSource(InputDevice.SOURCE_MOUSE)
                && ev.getAction() == MotionEvent.ACTION_DOWN
                && ev.isButtonPressed(MotionEvent.BUTTON_PRIMARY)
                && isOnScrollbarThumb(ev.getX(), ev.getY())) {
            return true;
        }
        return false;

当返回false时会执行

if (!canceled && !intercepted) {
。。。
}

咱们看里面的关键步骤

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

这个if里面有个关键函数addTouchTarget 看下里面干了什么

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

现在把关注点放在mFirstTouchTarget变量上,
当dispatchTransformedTouchEvent 返回true时 addTouchTarget()才会执行,mFirstTouchTarget 才会被赋值,记住这个值哦,后面会用到。
继续往下分析,我们发现mFirstTouchTarget 是否为null,dispatchTransformedTouchEvent 这个方法始终会执行,唯一不同的是第三个参数不一样,如下

 // Dispatch to touch targets.
            if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary 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;
                }
            }

现在我们看看dispatchTransformedTouchEvent ()都干了些啥

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) {
            handled = super.dispatchTouchEvent(transformedEvent);
        } else {
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            transformedEvent.offsetLocation(offsetX, offsetY);
            if (! child.hasIdentityMatrix()) {
                transformedEvent.transform(child.getInverseMatrix());
            }

            handled = child.dispatchTouchEvent(transformedEvent);
        }

        // Done.
        transformedEvent.recycle();
        return handled;
    }

看关键的步骤

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

这个child的值决定了dispatchTouchEvent调用的是ViewGroup的还是View的
1、当child==null是会调用父类的dispatchTouchEvent,而ViewGroup中的父类是View,相当于是调用了父类View的dispatchTouchEvent,而dispatchTouchEvent又调用了onTouchEvent方法,即调用的相当于是子类ViewGroup的onTouchEvent方法,事件不会再像子View传递了
2、当child!=null时会执行子控件中的dispatchTouchEvent方法,这个时候ViewGroup和View之间的事件传递就联系在一起了

ViewGroup事件分发总结
如果onInterceptTouchEvent方法返回true,事件被拦截,那么接着就会调用ViewGroup的onTouchEvent方法,事件不会再向ViewGroup的子View中传递。如果onInterceptTouchEvent方法返回false,事件未被拦截,会会调用ViewGroup子View的dispatchTouchEvent,进而调用子View的onTouchEvent。

接下view的事件传递了

View 事件传递

由ViewGroup的事件分析,咱们都知道了首先分下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)) {
            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;
    }

看重点

if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true;
            }

            if (!result && onTouchEvent(event)) {
                result = true;
            }

从这里可以看出onTouch的值会影响onTouchEvent是否可以执行,比较简单就不细说了,自此事件的传递就结束了,本来想自己画个流程图,发现都没有网上大神画的好就直接借用大神的流程图了。在这里插入图片描述
如有不到之处,还请谅解,技术有限!!!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值