Android事件分发机制(二)

上篇文章概述了Android触摸事件的主流程 Android事件分发机制(一),本篇文章将从源码分析下事件分发的大致流程。好了马上进入主题。
Android的事件分发是从Activity的dispatchTouchEvent方法开始的,首先看看这个方法的实现。

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

Activity的dispatchTouchEvent方法里面通过getWindow方法拿到当前Activity的Window对象然后调用了它的superDispatchTouchEvent方法。

Window.superDispatchTouchEvent
public abstract boolean superDispatchTouchEvent(MotionEvent event);

Window类的superDispatchTouchEvent是一个抽象方法

Window
/**
 * <p>The only existing implementation of this abstract class is
 * android.view.PhoneWindow, which you should instantiate when needing a
 * Window.
 */
public abstract class Window

是Window是一个抽象类,它的唯一实现类是android.view.PhoneWindow。我们看下PhoneWindow的superDispatchTouchEvent是如何实现的。

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

PhoneWindow的superDispatchTouchEvent方法通过调用mDecor对象的superDispatchTouchEvent方法,继续把事件分下去了。mDecor是DecorView的对象,DecorView是FrameLayout的子类。

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

DecorView的superDispatchTouchEvent直接调了父类的dispatchTouchEvent方法,但DevorView的父类FrameLayout没有重写dispatchTouchEvent,所以事件的分发就传到了ViewGroup了。ViewGroup的dispatchTouchEvent方法有接近两百行的代码。我们删减部分代码只关注事件分发的主流程。

ViewGroup.dispatchTouchEvent
public boolean dispatchTouchEvent(MotionEvent ev) {


    boolean handled = false;
    if (onFilterTouchEventForSecurity(ev)) {
        final int action = ev.getAction();
        final int actionMasked = action & MotionEvent.ACTION_MASK;

        // ACTIOO_DOWN事件,重置状态。由于anr或其他原因导致上次事件ACTION_CANCLE或ACTION_UP被抛弃了
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            cancelAndClearTouchTargets(ev);
            resetTouchState();
        }

        // 检查事件是否被拦截
        final boolean intercepted;
        //当前事件是ACTION_DOWN或者已经有子view处理,进入if语句块里面判断ViewGroup是否拦截
        if (actionMasked == MotionEvent.ACTION_DOWN
                || mFirstTouchTarget != null) {
            final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;//判断是否允许ViewGroup拦截触摸事件
            if (!disallowIntercept) {//disallowIntercept等于false表示允许拦截
                //onInterceptTouchEvent默认返回false
                intercepted = onInterceptTouchEvent(ev);
                ev.setAction(action); // restore action in case it was changed
            } else {
                intercepted = false;
            }
        } else {
            //如果当前事件没有处理它的子view,也不是ACTION_DOWN事件,则父类拦截
            //也就是这里说明说如果一次view不处理ACTION_DOWN事件,它就不会后续事件的原因
            intercepted = true;
        }
        // 检查事件是否被取消
        final boolean canceled = resetCancelNextUpFlag(this)
                || actionMasked == MotionEvent.ACTION_CANCEL;

        final boolean split = (mGroupFlags & FLAG_SPLIT_MOTION_EVENTS) != 0;
        TouchTarget newTouchTarget = null;
        boolean alreadyDispatchedToNewTouchTarget = false;
        //如果没取消和拦截进入if语句块
        if (!canceled && !intercepted) {

            //如果当前事件是ACTION_DOWN,或者是ACTION_POINTER_DOWN,需要遍历子view,看看是否子view处理该事件。
            //ACTION_POINTER_DOWN是多点触控时,一个新的手指按压下来
            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;

                final int childrenCount = mChildrenCount;
                if (newTouchTarget == null && childrenCount != 0) {
                    final float x = ev.getX(actionIndex);
                    final float y = ev.getY(actionIndex);
                    //找到一个能接受事件的子view
                    //按照显示在最上层到最下层的顺序去遍历
                    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);

                     //canViewReceivePointerEvents方法判断子view是否visible或者子view是否在播动画或者即将播动画
                        //isTransformedTouchPointInView方法判断点击区域是否处于子view内
                        if (!canViewReceivePointerEvents(child)
                                || !isTransformedTouchPointInView(x, y, child, null)) {
                            continue;//以上两个条件要同时满足才继续处理
                        }

                        //判断当前子view之前是否有处理过该系列事件pointer的(这里可以理解为一个手指一个pointer)  
                        //也就是子view在多指触摸事件下,处理过其他手指的事件
                        //如果找到了就把当前的pointerId记录在该子viewpointerIdBits变量
                        newTouchTarget = getTouchTarget(child);
                        if (newTouchTarget != null) {
                            newTouchTarget.pointerIdBits |= idBitsToAssign;
                            break;
                        }
                        //dispatchTransformedTouchEvent方法是真正把事件分发给子view处理的
                        //返回值为true则代表该子view处理了事件
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                            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();
                            //记录子view是新的触摸目标,addTouchTarget方法会把子view加入TouchTarget触摸链表里面
                            newTouchTarget = addTouchTarget(child, idBitsToAssign);
                            alreadyDispatchedToNewTouchTarget = true;
                            break;
                        }
                    }
                    if (preorderedList != null) preorderedList.clear();
                }

                //newTouchTarget为空表示没有子view处理事件
                //同时mFirstTouchTarget不为空
                //则把找到最先处理了事件的view相关的TouchTarget对象当做newTouchTarget
                if (newTouchTarget == null && mFirstTouchTarget != null) {
                    newTouchTarget = mFirstTouchTarget;
                    while (newTouchTarget.next != null) {
                        newTouchTarget = newTouchTarget.next;
                    }
                    newTouchTarget.pointerIdBits |= idBitsToAssign;
                }
            }
        }

        //分发事件到触摸目标
        if (mFirstTouchTarget == null) {
            // 没有子view处理时,或者拦截了事件。把当前ViewGroup作为普通的view处理事件
            //dispatchTransformedTouchEvent方法第三个参数接受子view,当为false时就直接调用当前viewgroup的super.dispatchTouchEvent方法,也就是View类的dispatchTouchEvent方法
            handled = dispatchTransformedTouchEvent(ev, canceled, null,
                    TouchTarget.ALL_POINTER_IDS);
        } else {
            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;
                    }
                }
                target = next;
            }
        }
    }

    return handled;
}

ViewGroup的dispatchTouchEvent方法处理了事件分发的主流程。一个事件如果没有拦截也没有取消,则遍历子view,从所有子view中找到处理事件的那个子view。ViewGroup是否拦截事件是通过disallowIntercept变量和onInterceptTouchEvent方法返回值共同判断的。子view可以调用父view的requestDisallowInterceptTouchEvent方法不给父view拦截事件。注意的是requestDisallowInterceptTouchEvent会继续一层一层地调用上去顶级父view。在遍历过程中,通过点击区域是否处于子view内等条件判断,找到符合条件的子view会通过调用dispatchTransformedTouchEvent方法去处理。所以真正处理事件的是dispatchTransformedTouchEvent方法。我们进去该方法里面看看流程和逻辑

private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
    final boolean handled;

    if (child == null || child.hasIdentityMatrix()) {
        if (child == null) {
            //如果子view为空,直接调了ViewGroup的父类View的dispatchTouchEvent方法
            handled = super.dispatchTouchEvent(event);
        } else {
            //如果子view不等于空,调用子view的dispatchTouchEvent方法 
            //如果子view是一个ViewGroup,则递归ViewGroup的dispatchTouchEvent方法,重复上面分析的逻辑
            //如果子view是普通的view,则事件分发到View的dispatchTouchEvent方法
            final float offsetX = mScrollX - child.mLeft;
            final float offsetY = mScrollY - child.mTop;
            event.offsetLocation(offsetX, offsetY);

            handled = child.dispatchTouchEvent(event);
        }
        return handled;
    }
}

删除了大部分dispatchTransformedTouchEvent的代码,只保留了核心流程代码。 从代码注释可以看到子view不等于空时,调用子view的dispatchTouchEvent方法 但这里要注意下,如果子view是一个ViewGroup,则递归ViewGroup的dispatchTouchEvent方法,重复上面分析的逻辑,所以分析到这里可以总结出:事件是从父view通过dispatchTouchEvent方法一层一层地分发到子view

下面我们继续追踪到View的dispatchTouchEvent方法

public boolean dispatchTouchEvent(MotionEvent event) {

    boolean result = false;

    if (onFilterTouchEventForSecurity(event)) {
        //noinspection SimplifiableIfStatement
        ListenerInfo li = mListenerInfo;
        
        //首先判断mOnTouchListener等于等于空,并且view是否ENABLED
        //满足条件就会调用OnTouchListener.onTouch方法
        if (li != null && li.mOnTouchListener != null
                && (mViewFlags & ENABLED_MASK) == ENABLED
                && li.mOnTouchListener.onTouch(this, event)) {
            result = true;
        }

        //只有result为false事件才分发到onTouchEvent
        if (!result && onTouchEvent(event)) {
            result = true;
        }
    }
    return result;
}

View的dispatchTouchEvent方法里面首先判断 li != null && li.mOnTouchListener != null 这个条件。mOnTouchListener对象其实就算我们通过setOnTouchListener方法设置进去的。同时onTouch方法返回true,result变量就设为true了。而一个view的onClick方法是在onTouchEvent方法里面调用的,这里也解析了onTouch方法返回true为什么onClick方法就不运行了。
下面我们看看onTouchEvent方法

public boolean onTouchEvent(MotionEvent event) {
    final float x = event.getX();
    final float y = event.getY();
    final int viewFlags = mViewFlags;
    final int action = event.getAction();

    //判断view是否可点击
    final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
            || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
            || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;

    //判断view是否enable
    if ((viewFlags & ENABLED_MASK) == DISABLED) {
        if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
            setPressed(false);
        }
        mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
        //一个disable的view依然会消费事件,只是不会有任何反馈,也就是onClick方法不会再调用了,所以直接返回
        return clickable;
    }

    //如果View是可点击的,进入if语句块处理事件
    if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
        switch (action) {
            case MotionEvent.ACTION_UP:
                省略代码...
                
                if (mPerformClick == null) {
                    mPerformClick = new PerformClick();
                }
                if (!post(mPerformClick)) {
                    performClick();//内部调用OnClickListener的onClick方法
                }
                省略代码...
                
                break;

            case MotionEvent.ACTION_DOWN:
                省略代码...
                break;

            case MotionEvent.ACTION_CANCEL:
                省略代码...
                break;

            case MotionEvent.ACTION_MOVE:
                省略代码...
                break;
        }

        return true;
    }

    return false;
}

可以看到onTouchEvent方法里面的逻辑,只要一个view是可点击,都会返回true,也就是消费了事件,否则返回false不消费事件。回到View的dispatchTouchEvent方法,当onTouchEvent返回false时,View的dispatchTouchEvent方法同样返回false,dispatchTransformedTouchEvent也会返回false。我们继续回到ViewGroup的dispatchTouchEvent方法。当所有子View的dispatchTransformedTouchEvent方法同样返回false时,mFirstTouchTarget 就等于null,ViewGroup的dispatchTouchEvent方法处理事件时就会调用super.dispatchTouchEvent方法。也就是ViewGroup的父类View的dispatchTouchEvent方法。至此,我们又可以总结出:如果事件分发过程中,没有任何一个View处理事件,又会通过View的OnTouchEvent方法一层一层的传递到父view。 这恰好跟dispatchTouchEvent方法相反,形成了事件传递过程中的一个U字形流程,结合上面的事件分发流程,我们总结出如图所示ACTION_DOWN事件类型的分发流程

在这里插入图片描述

分析到这里。我们基本上就弄清楚了Android事件分发流程,分析过程还有很多细节没有涉及。但我们只要掌握了主流程,具体细节再开发时遇到问题可以点开代码看看,就能轻松解决。
这篇文章也只是从源码分析了Android的事件分发,下篇文章,我们将从实际例子中利用我们分析到的技术来解决一些常见的问题。

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

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值