美文网首页自定义view相关ViewAndroid技术知识
简单理解Android事件分发机制(下)——走进源码解析原理

简单理解Android事件分发机制(下)——走进源码解析原理

作者: Ruheng | 来源:发表于2017-02-21 17:57 被阅读277次

    本篇文章将从源码的角度解析事件分发机制的详细内容。关于上篇文章的那些情况迥异的分发处理过程,是如何在源码中实现的?本篇文章将逐一揭晓。

    一、分发机制中三个方法的关系

    上篇文章关于dispatchTouchEvent()方法,onInterceptTouchEvent()方法和onTouchEvent ()方法的流程进行了梳理。那么在源码实现中,三者之间的关系具体是什么样的?用一段伪代码来介绍。

    / 点击事件产生后,会直接调用dispatchTouchEvent()方法
    public boolean dispatchTouchEvent(MotionEvent ev) {
       //代表是否消耗事件
        boolean consume = false;
        if (onInterceptTouchEvent(ev)) {
        //如果onInterceptTouchEvent()返回true则代表当前View拦截了点击事件
        //则该点击事件则会交给当前View进行处理
        //即调用onTouchEvent ()方法去处理点击事件
          consume = onTouchEvent (ev) ;
        } else {
          //如果onInterceptTouchEvent()返回false则代表当前View不拦截点击事件
          //则该点击事件则会继续传递给它的子元素
          //子元素的dispatchTouchEvent()就会被调用,重复上述过程
          //直到点击事件被最终处理为止
          consume = child.dispatchTouchEvent (ev) ;
        }
        return consume;
       }
    

    上述伪代码清楚地描述了,事件分发从Activity->ViewGroup->View过程中,三大方法之间的调用关系。

    二、Activity中的分发机制

    上篇文章,我们介绍到,分发机制是从Activity开始的,当触摸屏幕时,Activity先感受到,并调用dispatchTouchEvent()方法进行分发。下面就来具体了解这一方法的内容。

    public boolean dispatchTouchEvent(MotionEvent ev) {
        if (ev.getAction() == MotionEvent.ACTION_DOWN) {
            //第一次按下操作时,用户希望能与设备进行交互,可通过实现该方法
            onUserInteraction();
        }
    
        //获取当前Activity的顶层窗口是PhoneWindow,执行其superDispatchTouchEvent()方法
        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        //当没有任何view处理时,交由activity的onTouchEvent处理
        return onTouchEvent(ev);
    }
    

    可以看到,当Activity向下分发事件,最终没有任何控件进行处理后,将会交给Activity的onTouchEvent()方法处理。
    继续看superDispatchTouchEvent()方法

    public boolean superDispatchTouchEvent(KeyEvent event) {
        return mDecor.superDispatcTouchEvent(event);
    }
    

    PhoneWindow的最顶View是DecorView,再交由DecorView处理。而DecorView的父类的父类是ViewGroup,接着调用 ViewGroup.dispatchTouchEvent()方法。

    所以Activity中的分发机制简述为:若不重写该方法,则调用根ViewGroup的dispatchTouchEvent()方法,进行分发,如果事件没有任何控件进行处理,则最后返回给Activity的onTouchEvent()方法进行处理。若重写该方法,不论返回值是false/true,都不会向下进行事件分发,也就是事件停止分发,已经在Activity中消费了。

    三、ViewGroup中的分发机制

    从Acivity中向下分发,就到达了ViewGroup中的dispatchTouchEvent()方法,下面来具体看看这个方法。
    该方法比较复杂,篇幅有限,就截取几个重要的逻辑片段进行介绍,来解析整个分发流程。

             // 发生ACTION_DOWN事件或者已经发生过ACTION_DOWN,并且将mFirstTouchTarget赋值,才进入此区域,主要功能是拦截器
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN|| mFirstTouchTarget != null) {
                //disallowIntercept:是否禁用事件拦截的功能(默认是false),即不禁用
                //可以在子View通过调用requestDisallowInterceptTouchEvent方法对这个值进行修改,不让该View拦截事件
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                //默认情况下会进入该方法
                if (!disallowIntercept) {
                    //调用拦截方法
                    intercepted = onInterceptTouchEvent(ev); 
                    ev.setAction(action);
                } else {
                    intercepted = false;
                }
            } else {
                // 当没有触摸targets,且不是down事件时,开始持续拦截触摸。
                intercepted = true;
            }
    

    这一段的内容主要是为判断是否拦截。如果当前事件的MotionEvent.ACTION_DOWN,则进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果mFirstTouchTarget != null,即已经发生过MotionEvent.ACTION_DOWN,并且该事件已经有ViewGroup的子View进行处理了,那么也进入判断,调用ViewGroup onInterceptTouchEvent()方法的值,判断是否拦截。如果不是以上两种情况,即已经是MOVE或UP事件了,并且之间的事件没有对象进行处理,则设置成true,开始拦截接下来的所有事件。这也就解释了如果子View的onTouchEvent()方法返回false,那么接下来的一些列事件都不会交给他处理。其实这并不是onTouchEvent()方法傲娇,而是onInterceptTouchEvent()方法没给他机会,直接拦截了,不给子View机会。如果VieGroup的onInterceptTouchEvent()第一次执行为true,则mFirstTouchTarget = null,则也会使得接下来不会调用onInterceptTouchEvent(),直接将拦截设置为true。

    当ViewGroup不拦截事件的时候,事件会向下分发交由它的子View或ViewGroup进行处理。

                        /* 从最底层的父视图开始遍历,
                        ** 找寻newTouchTarget,即上面的mFirstTouchTarget
                        ** 如果已经存在找寻newTouchTarget,说明正在接收触摸事件,则跳出循环。
                        */
                        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 (childWithAccessibilityFocus != null) {
                                if (childWithAccessibilityFocus != child) {
                                    continue;
                                }
                                childWithAccessibilityFocus = null;
                                i = childrenCount - 1;
                            }
                            //如果view不可见,或者触摸的坐标点不在view的范围内,则跳过本次循环
                            if (!canViewReceivePointerEvents(child)
                                    || !isTransformedTouchPointInView(x, y, child, null)) {
                                ev.setTargetAccessibilityFocus(false);
                                continue;
                            }
    
                            newTouchTarget = getTouchTarget(child);
                            // 已经开始接收触摸事件,并退出整个循环。
                            if (newTouchTarget != null) {
                                newTouchTarget.pointerIdBits |= idBitsToAssign;
                                break;
                            }
    
                            //重置取消或抬起标志位
                            //如果触摸位置在child的区域内,则把事件分发给子View或ViewGroup
                            if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                                // 获取TouchDown的时间点
                                mLastTouchDownTime = ev.getDownTime();
                                // 获取TouchDown的Index
                                if (preorderedList != null) {
                                    for (int j = 0; j < childrenCount; j++) {
                                        if (children[childIndex] == mChildren[j]) {
                                            mLastTouchDownIndex = j;
                                            break;
                                        }
                                    }
                                } else {
                                    mLastTouchDownIndex = childIndex;
                                }
    
                                //获取TouchDown的x,y坐标
                                mLastTouchDownX = ev.getX();
                                mLastTouchDownY = ev.getY();
                                //添加TouchTarget,则mFirstTouchTarget != null。
                                newTouchTarget = addTouchTarget(child, idBitsToAssign);
                                //表示以及分发给NewTouchTarget
                                alreadyDispatchedToNewTouchTarget = true;
                                break;
                            }
    

    dispatchTransformedTouchEvent()方法实际就是调用子元素的dispatchTouchEvent()方法。
    其中dispatchTransformedTouchEvent()方法的重要逻辑如下:

     if (child == null) {
                    handled = super.dispatchTouchEvent(event);
                } else {
                    handled = child.dispatchTouchEvent(event);
                }
    

    由于其中传递的child不为空,所以就会调用子元素的dispatchTouchEvent()。
    如果子元素的dispatchTouchEvent()方法返回true,那么mFirstTouchTarget就会被赋值,同时跳出for循环。

    //添加TouchTarget,则mFirstTouchTarget != null。
    newTouchTarget = addTouchTarget(child, idBitsToAssign);
     //表示以及分发给NewTouchTarget
     alreadyDispatchedToNewTouchTarget = true;
    

    其中在addTouchTarget(child, idBitsToAssign);内部完成mFirstTouchTarget被赋值。
    如果mFirstTouchTarget为空,将会让ViewGroup默认拦截所有操作。
    如果遍历所有子View或ViewGroup,都没有消费事件。ViewGroup会自己处理事件。

    所以ViewGroup中的分发机制简述为:若子View或ViewGroup不处理MotionEvent.ACTION_DOWN事件,那么接下来的一些列事件都交由ViewGroup处理。若ViewGroup的onInterceptTouchEvent()执行为true,则接下来的所有事件都默认由该ViewGroup执行。若子View或ViewGroup处理MotionEvent.ACTION_DOWN事件,则接下来的事件处理交给谁要看onInterceptTouchEvent()的返回值,如果返回true,则第一个MOVE事件,会变成CANCEL事件,继续交由原来的子View或ViewGroup处理,接下来的一些列事件都交由ViewGroup执行。如果返回false,则继续交给原来的子View或ViewGroup处理。

    四、View中的分发机制

    View中的分发机制就比较简单了。上面ViewGroup中已经开始调用View.dispatchTouchEvent()方法,下面来具体看一下。

    public boolean dispatchTouchEvent(MotionEvent event) {
        ...
        
        final int actionMasked = event.getActionMasked();
        if (actionMasked == MotionEvent.ACTION_DOWN) {
            //在Down事件之前,如果存在滚动操作则停止。不存在则不进行操作
            stopNestedScroll();
        }
        if (onFilterTouchEventForSecurity(event)) {
            ListenerInfo li = mListenerInfo;
            //第一个条件默认为true
            //第二个条件mOnTouchListener 不为空
           //第三个条件该条件是判断当前点击的控件是否enable 
           //第四个条件onTouch返回值是true
            if (li != null && li.mOnTouchListener != null
                    && (mViewFlags & ENABLED_MASK) == ENABLED
                    && li.mOnTouchListener.onTouch(this, event)) {
                result = true; //满足上述四个条件,已经消费事件,则返回True
            }
            //如果OnTouch()返回false,或者没满足其他条件,没有消费Touch事件则调用OnTouchEvent()
            if (!result && onTouchEvent(event)) { 
                result = true; //onTouchEvent(event)返回true,已经消费事件,则返回True
            }
        }
    
        if (!result && mInputEventConsistencyVerifier != null) {
            mInputEventConsistencyVerifier.onUnhandledEvent(event, 0);
        }
    
        // 处理取消或抬起操作
        if (actionMasked == MotionEvent.ACTION_UP ||
                actionMasked == MotionEvent.ACTION_CANCEL ||
                (actionMasked == MotionEvent.ACTION_DOWN && !result)) {
            stopNestedScroll();
        }
    
        return result;
    }
    

    针对上述四个条件的判断,很多View默认是(mViewFlags & ENABLED_MASK) == ENABLED,通过设置OnTouchListener中的onTouch返回true,那么onTouchEvent()方法就不会调用,表明OnTouchListener的优先级高于onTouchEvent。

    //手动调用设置
    button.setOnTouchListener(new OnTouchListener() {  
    
      @Override  
      public boolean onTouch(View v, MotionEvent event) {  
    
          return false;  
      }  
    });
    

    如果OnTouchListener中的onTouch返回false,那么会调用onTouchEvent()方法。

    public boolean onTouchEvent(MotionEvent event) {
        final float x = event.getX();
        final float y = event.getY();
        final int viewFlags = mViewFlags;
    
        // 当View状态为DISABLED,如果可点击或可长按,则返回True,即消费事件
        if ((viewFlags & ENABLED_MASK) == DISABLED) {
            if (event.getAction() == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                setPressed(false);
            }
            return (((viewFlags & CLICKABLE) == CLICKABLE ||
                    (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE));
        }
    
        if (mTouchDelegate != null) {
            if (mTouchDelegate.onTouchEvent(event)) {
                return true;
            }
        }
    
        //当View状态为ENABLED,如果可点击或可长按,则返回True,即消费事件;
        //与前面的的结合,可得出结论:只要view是可点击或可长按,则消费该事件.
        if (((viewFlags & CLICKABLE) == CLICKABLE ||
                (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {
            switch (event.getAction()) {
                case MotionEvent.ACTION_UP:
                    boolean prepressed = (mPrivateFlags & PFLAG_PREPRESSED) != 0;
                    if ((mPrivateFlags & PFLAG_PRESSED) != 0 || prepressed) {
                        boolean focusTaken = false;
                        if (isFocusable() && isFocusableInTouchMode() && !isFocused()) {
                            focusTaken = requestFocus();
                        }
    
                        if (prepressed) {
                            setPressed(true, x, y);
                       }
    
                        if (!mHasPerformedLongPress) {
                            //这是Tap操作,移除长按回调方法
                            removeLongPressCallback();
    
                            if (!focusTaken) {
                                if (mPerformClick == null) {
                                    mPerformClick = new PerformClick();
                                }
                                //调用View.OnClickListener
                                if (!post(mPerformClick)) {
                                    performClick();
                                }
                            }
                        }
    
                        if (mUnsetPressedState == null) {
                            mUnsetPressedState = new UnsetPressedState();
                        }
    
                        if (prepressed) {
                            postDelayed(mUnsetPressedState,
                                    ViewConfiguration.getPressedStateDuration());
                        } else if (!post(mUnsetPressedState)) {
                            mUnsetPressedState.run();
                        }
    
                        removeTapCallback();
                    }
                    break;
    
                case MotionEvent.ACTION_DOWN:
                    mHasPerformedLongPress = false;
    
                    if (performButtonActionOnTouchDown(event)) {
                        break;
                    }
    
                    //获取是否处于可滚动的视图内
                    boolean isInScrollingContainer = isInScrollingContainer();
    
                    if (isInScrollingContainer) {
                        mPrivateFlags |= PFLAG_PREPRESSED;
                        if (mPendingCheckForTap == null) {
                            mPendingCheckForTap = new CheckForTap();
                        }
                        mPendingCheckForTap.x = event.getX();
                        mPendingCheckForTap.y = event.getY();
                        //当处于可滚动视图内,则延迟TAP_TIMEOUT,再反馈按压状态,用来判断用户是否想要滚动。默认延时为100ms
                        postDelayed(mPendingCheckForTap, ViewConfiguration.getTapTimeout());
                    } else {
                        //当不再滚动视图内,则立刻反馈按压状态
                        setPressed(true, x, y);
                        checkForLongClick(0); //检测是否是长按
                    }
                    break;
    
                case MotionEvent.ACTION_CANCEL:
                    setPressed(false);
                    removeTapCallback();
                    removeLongPressCallback();
                    break;
    
                case MotionEvent.ACTION_MOVE:
                    drawableHotspotChanged(x, y);
    
                    if (!pointInView(x, y, mTouchSlop)) {
                        removeTapCallback();
                        if ((mPrivateFlags & PFLAG_PRESSED) != 0) {
                            removeLongPressCallback();
                            setPressed(false);
                        }
                    }
                    break;
            }
    
            return true;
        }
        return false;
    }
    

    只要View的CLICKABLE和LONG_CLICKABLE有一个为true,那么它就会消耗这个事件。
    如果View设置了OnClickLisenter,那么performClick方法内部就会调用onClick方法。

    public boolean performClick() {  
        sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);  
    
        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
        }  
        return false;  
    }
    

    则表明onTouch()优先级高于onTouchEvent(),并高于onClick()。
    所以View中的分发机制简述为:默认情况下,如果View的CLICKABLE和LONG_CLICKABLE有一个为true(默认LONG_CLICKABLE为false,一般可以点击的View中CLICKABLE为true。),则就会消费该事件,如果都为false,则不会消费该事件,dispatchTouchEvent()方法返回false,交由父控件的循环下一个子View进行同样操作。如果重写onTouchEvent()方法,返回false,dispatchTouchEvent()方法返回false,交由父控件的循环下一个子View进行同样操作。如果重写onTouchEvent()方法,返回true,则消费该事件。

    以上就是源码解析事件分发的内容。
    参考文章:
    Android事件分发机制完全解析,带你从源码的角度彻底理解(上)
    Android事件分发机制完全解析,带你从源码的角度彻底理解(下)
    Android事件分发机制

    相关文章

      网友评论

        本文标题:简单理解Android事件分发机制(下)——走进源码解析原理

        本文链接:https://www.haomeiwen.com/subject/dbsuwttx.html