Android View的事件分发机制源码分析

View的事件分发机制

最近看到事件分发机制的讨论,可是细节都忘的差不多了,看以前的笔记写的比较简单,就趁这个机会整(shui)理(wen)吧。

事件分发机制的传递规则

时间分发机制的传递规则可以用以下伪代码表示:

   public boolean dispatchTouchEvent(MotionEvent ev) {
        boolean result = false;//事件是否被消费
        if (onInterceptTouchEvent(ev)){//调用onInterceptTouchEvent判断是否拦截事件
            result = onTouchEvent(ev);//如果拦截则调用自身的onTouchEvent方法
        }else{
            result = child.dispatchTouchEvent(ev);//不拦截调用子View的dispatchTouchEvent方法
        }
        return result;//返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
    }

这段伪代码的总结可以说是非常精辟了,把复杂的源码逻辑给提炼出来,相信大家很从这段伪代码看出事件分发机制的传递规则。

先上总结:点击事件由Activity捕获,传递到顶层的ViewGroup,所以从顶层的ViewGroup的dispatchTouchEvent方法开始:首先判断顶层的ViewGroup的onInterceptTouchEvent方法,如果onInterceptTouchEvent方法返回true,则拦截事件,调用本身的onTouchEvent方法,否则调用子View/ViewGroup的dispatchTouchEvent下发事件,用Word简单画了一个流程图,大致如下:

流程图

之前我也这样浅尝即止了,对具体的细节也不太清楚,解决了一个滑动冲突之后也没深究,这次就从源码入手,详细的剖析一下事件分发机制。

源码分析

从上面的总结可以看出:首先被调用的dispatchTouchEvent是Activity中的顶层ViewGroup,于是先看ViewGroup的dispatchTouchEvent方法

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
           ···
            // 1.判断是否是ACTION_DOWN事件
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                cancelAndClearTouchTargets(ev);
                resetTouchState();
            }
 
            final boolean intercepted;
            // 2.当前ViewGroup是否拦截ACTION_DOWN以外的事件
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); 
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }            
           ···
 }

首先看1:Android中的点击事件都是以ACTION_DOWN开始,所以如果传入dispatchTouchEvent中的事件是ACTION_DOWN的话,说明接下来是一个新的点击事件序列,需要对之前的操作进行初始化
2:mFirstTouchTarget在这里可以简单看做当前ViewGroup是否拦截事件的Flag,如果拦截,则mFirstTouchTarget==null,所以当ViewGroup拦截点击事件,遇上ACTION_UPACTION_MOVE等事件时,会直接设置intercepted = true,跳过onInterceptTouchEvent,此后的一个事件序列都由这个ViewGroup进行处理,一句话概括:如果ViewGroup拦截事件,则跳过onInterceptTouchEvent,后续事件都由它自己处理
另一种情况:当事件为ACTION_DOWN或者mFirstTouchTarget != null即该ViewGroup不拦截点击事件的时候会进入if,intercepted = onInterceptTouchEvent(ev),这里我们看onInterceptTouchEvent方法

    public boolean onInterceptTouchEvent(MotionEvent ev) {
       return false;
   }

可以看到onInterceptTouchEvent方法默认返回一个false,也就是不拦截事件,我们可以通过自定义ViewGroup重写onInterceptTouchEvent达到拦截的目的

  • 扩展:在dispatchTouchEvent代码段2下面有一个标志位FLAG_DISALLOW_INTERCEPT,它也可以禁止ViewGroup拦截处理ACTION_DOWN以外的事件,可以通过子View的requestDisallowInterceptTouchEvent设置

onInterceptTouchEvent拦截讲完了,接着看dispatchTouchEvent代码

    @Override
    public boolean dispatchTouchEvent(MotionEvent ev) {
                    ···
                    final int childrenCount = mChildrenCount;
                    if (newTouchTarget == null && childrenCount != 0) {
                        final float x = ev.getX(actionIndex);
                        final float y = ev.getY(actionIndex);
                        final ArrayList<View> preorderedList = buildTouchDispatchChildList();
                        final boolean customOrder = preorderedList == null
                                && isChildrenDrawingOrderEnabled();
                        final View[] children = mChildren;
                        //1.循环遍历子View
                        for (int i = childrenCount - 1; i >= 0; i--) {
                            final int childIndex = getAndVerifyPreorderedIndex(
                                    childrenCount, i, customOrder);
                            final View child = getAndVerifyPreorderedView(
                                    preorderedList, children, childIndex);

                            if (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }

                            if (!child.canReceivePointerEvents()
                                    || !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);
                            //2.dispatchTransformedTouchEvent调用子元素或父类的dispatchTouchEvent
                            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();
                    }
                    ···
        }

我们可以看到在代码1处,倒序遍历了所有的子元素,也就是说,从最上层的元素开始遍历,如果子元素能够接受到点击事件,就交给子元素来处理。
再看2处,dispatchTransformedTouchEvent这个方法调用了子元素或者父类的dispatchTouchEvent方法,我们看一下dispatchTransformedTouchEvent的代码

    private boolean dispatchTransformedTouchEvent(MotionEvent event, boolean cancel,
            View child, int desiredPointerIdBits) {
        final boolean handled;
        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;
        }

可以看到,如果有子View,调用子View的dispatchTouchEvent方法,如果没有,调用super.dispatchTouchEvent,而ViewGroup是继承自View,所以归根到底都是调用View的dispatchTouchEvent方法:

    public boolean dispatchTouchEvent(MotionEvent event) {
        ···
           boolean result = false;
           //1
           if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
               result = true;
           }
           
           //2
           ListenerInfo li = mListenerInfo;
           if (li != null && li.mOnTouchListener != null
                   && (mViewFlags & ENABLED_MASK) == ENABLED
                   && li.mOnTouchListener.onTouch(this, event)) {
               result = true;
           }

           //3
           if (!result && onTouchEvent(event)) {
               result = true;
           }  
       ···
       return result;
   }

View.dispatchTouchEventresult == true代表了是否消费该事件,而result = true则只有上面三种情况:

1. 如果是通过鼠标输入拖动滚动条,则消耗这个事件,这种情况在开发中比较少见。

2. ListenerInfo 是view的一个内部类 里面有各种各样的listener,例如OnClickListenerOnLongClickListenerOnTouchListener等等

在这个if中,先获取View中的ListenerInfo对象li = mListenerInfo,如果li!=null 且我们通过setOnTouchListener设置了监听,即是否有实现OnTouchListener,如果有实现就判断当前的view状态是不是ENABLED,如果实现的OnTouchListeneronTouch中返回true,并处理事件,设置result = true

其中,onTouch方法返回的result决定了是否执行OnTouvhEvent(event)方法,可以看出,OnTouchListener.onTouch的优先级是高于OnTouvhEvent的。而onTouch方法一般是当我们调用View的setOnTouchListener时重写的方法,利用到实际开发中,如果重写的OnTouch方法中返回了true,View就不会调用OnTouvhEvent,反之返回false的时候不仅会走onTouch,还会调用调用ViewOnTouvhEvent,最终调用我们设置的setOnClickablesetLongClickable等方法

3. 第三种就是OnTouchListener未设置或者OnTouch返回false时,调用OnTouvhEvent(event)方法,如果OnTouvhEvent(event)方法返回true,则把result设置为true表示事件已经被消耗。具体在OnTouvhEvent(event)中发生了什么我们看代码:

    public boolean onTouchEvent(MotionEvent event) {
        ···
        final int action = event.getAction();
        final int viewFlags = mViewFlags;
        //1
        final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
        if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
            switch (action) {
                case MotionEvent.ACTION_UP:
                        ···
                        if (!mHasPerformedLongPress && !mIgnoreNextUpEvent) {
                            removeLongPressCallback();
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                if (!post(mPerformClick)) {
                                    //2
                                    performClickInternal();
                                }
                            }
                        }

            ···
            }

            return true;
        }

        return false;
    }

可以看到1处的if对viewFlags的与CLICKABLELONG_CLICKABLE标志位进行与运算,也就是说View的CLICKABLELONG_CLICKABLE只要有一个为true,那么就会进入这个if,最终返回true,消耗这个事件。
而开发中经常使用的View的setOnClickListenersetOnLongClickListener就会自动把CLICKABLELONG_CLICKABLE设置为true。
然后当事件为ACTION_UP时,也就是手势抬起,会调用performClickInternal()方法,而performClickInternal()方法最终调用了performClick()方法:

    public boolean performClick() {
        notifyAutofillManagerOnClick();

        final boolean result;
        final ListenerInfo li = mListenerInfo;
        //1
        if (li != null && li.mOnClickListener != null) {
            playSoundEffect(SoundEffectConstants.CLICK);
            li.mOnClickListener.onClick(this);
            result = true;
        } else {
            result = false;
        }

        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);

        notifyEnterOrExitForAutoFillIfNeeded(true);

        return result;
    }

可以看到在1处,如果我们设置了OnClickListener,会调用OnClickListener.onClick(this),也就是我们设置的点击事件,然后result = true事件被消耗。自此,View的事件分发结束。

原创文章 8 获赞 9 访问量 880

猜你喜欢

转载自blog.csdn.net/weixin_40303382/article/details/105629831