美文网首页
View绘制及事件机制原理

View绘制及事件机制原理

作者: 玉圣 | 来源:发表于2022-03-31 16:48 被阅读0次

    一、View绘制流程机制

    1、View绘制起点

    • performTraversals()方法触发了View 的绘制。

      Activity调用流程
    • 说明:
      在Activity显示时,WindowManager 将View添加到 DecorView ,两者通过 ViewRoot 连接起来。
      具体实现类是 ViewRootImpl
      再通过 ViewRootImpl 的一系列处理,最终调用 performTraversals 方法,在performTraversals 方法中,依次调用了 performMeasure()performLayout()performDraw(),将View 的measurelayout,draw` 过程从顶层View 分发了下去。开始了View的绘制。

    2、View绘制流程

    • 绘制过程分为三步:
      measure(测量) --> layout(布局) --> draw(绘制),
      draw 流程结束以后就可以在屏幕上看到view了。
    • 流程图如下:


      绘制流程
    .1、measure(测量)
    • 测量的目的,是为了计算出View的大小,通过MeasureSpec来进行计算的。
      MeasureSpec是一个 specSizespecMode 信息的32 位int 值,其中高两位表示 specMode,低30位表示 specSize

    • specMode 模式:
      UNSPECIFIED:父容器不对View有任何限制,要多大有多大。常用于系统内部。
      EXACTLY(精确模式):父视图为子视图指定一个确切的尺寸SpecSize。对应LyaoutParams中的match_parent或具体数值。
      AT_MOST(最大[限制]模式):父容器为子视图指定一个最大尺寸SpecSize,View的大小不能大于这个值。对应LayoutParams中的wrap_content。

    • 决定View大小的因素:
      因素:widthMeasureSpecheightMeasureSpec
      MeasureSpec 值由 子View的布局参数LayoutParams父容器的MeasureSpec值 共同决定。具体规则见下图:

      MeasureSpec创建规则

    顶级View(即DecorView)的测量在ViewRootImpl的源码里(getRootMeasureSpec方法):

    //desire的这2个参数就代表屏幕的宽高,
      childWidthMeasureSpec = getRootMeasureSpec(desiredWindowWidth, lp.width);
      childHeightMeasureSpec = getRootMeasureSpec(desiredWindowHeight, lp.height);
      performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    
      //decorView的measureSpec就是在这里确定的,其实比普通view的measurespec要简单的多
      private static int getRootMeasureSpec(int windowSize, int rootDimension) {
            int measureSpec;
            switch (rootDimension) {
    
            case ViewGroup.LayoutParams.MATCH_PARENT:
                // Window can't resize. Force root view to be windowSize.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
                break;
            case ViewGroup.LayoutParams.WRAP_CONTENT:
                // Window can resize. Set max size for root view.
                measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
                break;
            default:
                // Window wants to be an exact size. Force root view to be that size.
                measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
                break;
            }
            return measureSpec;
    }
    
    • 流程:
      performMeasure() 会调用 measure()(final方法),将计算的MeasureSpec 传递给调用的onMeasure()方法,此方法会调用setMeasuredDimension()来设置自身大小;
      如果是ViewGroup,先遍历子View,测量出子View的MeasureSpec ,再调用measureChild*相关方法让子View通过调用measure方法来测量自己的大小;再根据所有子View的大小确定自身的大小,其中的子View会重复此类的measure过程,如此反复至完成整个View树的遍历,确定各个View自身的大小。

    注意:

    • ViewGroup中没有onMeasure方法
    • View会进行多次的测量,第一次测量和最终测量的实际宽高不一定相等,在layout流程中可确定View的实际宽高
    • 获取measure()后的宽高方法:
    • Activity#onWindowFocusChange()中获取
    • view.post(Runnable)将获取的代码投递到消息队列的尾部
    • ViewTreeObservable方法
    .2、layout(布局)
    • 布局目的:确定View的 最终宽高四个顶点的位置

    • 流程:
      performLayout() 会调用顶级View的layout() 方法,其中调用 setFrame() 方法来设置其四个顶点(mLeft、mRight、mTop、mBottom);
      接着调用 onLayout() (空方法),此方法由具体实现的View自身重写,用来确定自身位置,及循环其子View来确定坐标位置,子View 会循环调用setChildFrame()(就是调用 View.layout())。

      layout流程
    • layout和onLayout方法有什么区别?
      layout是确定本身view的位置,通过serFrame方法设定本身view的四个顶点的位置。
      onLayout是确定所有子元素的位置。
      View和ViewGroup的 onLayout 方法都是空方法。都留给我们自己给子元素布局。

    .3、draw(绘制)
    • 绘制目的:显示View
    • 流程:
      performMeasure() 会调用 ViewRootImpl的draw方法,再调用drawSoftWare()方法,其中会调用mView.draw(),是真正绘制步骤的开始。绘制步骤如下:(1、3、4、6四步为主要步骤)
    • 1、Draw the background:绘制背景 —— drawBackground(canvas)
      1. If necessary, save the canvas' layers to prepare for fading:保存图层
      1. Draw view's content:绘制view自身的内容 —— onDraw(canvas)
      1. Draw children:绘制子View(分发) —— dispatchDraw(canvas)
      1. If necessary, draw the fading edges and restore layers:绘制一些图层
      1. Draw decorations (scrollbars for instance):绘制装饰(如滚动条) —— onDrawForeground(canvas)

    注:View中的dispatchDraw(canvas) 是空方法,ViewGroup中的dispatchDraw(canvas)调用了其drawChild方法

    • 绘制流程图:


      draw流程
    • 说明:

    • setWillNotDraw:用于设置绘制的标志位,view是否需要draw绘制。
      若自定义的View 不需要draw,则可以设置这个方法为true。系统会根据此标记来优化执行速度。
      ViewGroup 一般都默认设置这个为true,因为ViewGroup多数都是只负责布局,不负责draw的。
      View 的这个标志位默认一般都是关闭的。
        public void setWillNotDraw(boolean willNotDraw) {
            setFlags(willNotDraw ? WILL_NOT_DRAW : 0, DRAW_MASK);
        }
    
    • 一般执行动画,会多次调用onDraw方法,通过监听动画的参数值变化,不断 invalidate,不断重绘。
      invalidate 是在 主线程 中进行调用,会引发onDraw进行重绘
      postInvalidate 是在 子线程 中调用,最终调用的仍是invalidate

    参考链接:
    Android View绘制13问13答
    要点提炼|开发艺术之View

    二、View及ViewGroup的事件机制

    1、相关概念:

    • MotionEvent事件
    • 事件类型:
      ACTION_DOWN:手指刚接触屏幕,按下去的那一瞬间产生该事件
      ACTION_MOVE:手指在屏幕上移动时候产生该事件
      ACTION_UP:手指从屏幕上松开的瞬间产生该事件
    • 事件序列:
      从ACTION_DOWN开始到ACTION_UP结束我们称为一个事件序列
    • TouchSlop
      系统所能识别的被认为是滑动的最小距离。
      即当手指在屏幕上滑动时,如果两次滑动之间的距离小于这个常量,那么系统就不认为你是在进行滑动操作。
      该常量和设备有关,可用它来判断用户的滑动是否达到阈值,获取方法:
       ViewConfiguration.get(getContext()).getScaledTouchSlop()
    
    • VelocityTracker
      速度追踪,用于追踪手指在滑动过程中的速度,包括水平和竖直方向的速度。

    • GestureDetector
      手势检测,用于辅助检测用户的单击、滑动、长按、双击等行为。

    2、事件分发的要点

    • 事件分发的本质:
      是对MotionEvent事件分发的过程,并将事件消费处理。

    • 事件分发的传递顺序:
      Activity(Window) --> ViewGroup --> View
      即最终调用的是 View的dispatchTouchEvent(MotionEvent event)方法

    • 事件分发的重要方法:
      1、dispatchTouchEvent(MotionEvent event):分发事件,返回boolean类型,表示事件是否消费
      2、onInterceptTouchEvent(MotionEvent event):中断事件(仅ViewGroup有),返回boolean类型,表示事件是否中断
      3、onTouchEvent(MotionEvent event):消费事件(仅View有),返回boolean类型,表示事件是否消费
      4、perform*Click():执行事件(仅View有),最终是onClick(View view),或长按、双击等事件处理。

    • 事件分发伪代码:

       // ViewGroup
       public boolean dispatchTouchEvent(MotionEvent ev) {
           // 事件是否被消费
           boolean consume = false;
           // 调用onInterceptTouchEvent判断是否拦截事件
           if (onInterceptTouchEvent(ev)) {
               // 如果拦截则调用自身的onTouchEvent方法
               consume = onTouchEvent(ev);
           } else {
               if (targetChild == null) {
                  // 没有找到目标child,则调用父容器的分发方法
                  consume = super.dispatchTouchEvent(ev);
               } else {
                  // 不拦截调用子View的dispatchTouchEvent方法
                  consume = child.dispatchTouchEvent(ev);
               }
           }
           // 返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
           return consume;
       }
    
       // View
       public boolean dispatchTouchEvent(MotionEvent ev) {
           boolean consume = false;
           // 是否实现了TouchListener#onTouch方法
           if (onTouchListener != null) {
               // 调用实现的onTouchListener#onTouch方法
               consume = onTouchListener.onTouch(ev);
           } else {
               // onTouchEvent()中调用了perform*Click()等方法
               consume = onTouchEvent(ev);
           }
           //返回值表示事件是否被消费,true事件终止,false调用父View的onTouchEvent方法
           return consume;
       }
    

    3、事件分发的机制&流程

    • 示意图


      View及ViewGroup事件流程示意图
    流程详述:
    • 1、ViewGroup分发开端:
      Acivity#dispatchTouchEvent(MotionEvent)
      事件最开始从Activity开始,由Acivity的dispatchTouchEvent方法来对事件进行分发。
        // Activity源码:
        public boolean dispatchTouchEvent(MotionEvent ev) {
            if (ev.getAction() == MotionEvent.ACTION_DOWN) {
                onUserInteraction();
            }
            // 事件分发并返回结果
            if (getWindow().superDispatchTouchEvent(ev)) {
                //事件被消费
                return true;
            }
            // 无View 消费事件,则调用Activity#onTouchEvent方法
            return onTouchEvent(ev);
        }
    

    PhoneWindow#superDispatchTouchEvent(MotionEvent)
    getWindow().superDispatchTouchEvent(ev)Window的抽象方法,具体由PhoneWindow实现
    其内部是调用的顶级View(DecorView)的superDispatchTouchEvent方法

        // PhoneWindow源码:
        // This is the top-level view of the window, containing the window decor.
        private DecorView mDecor;
        public boolean superDispatchTouchEvent(MotionEvent event) {
            return mDecor.superDispatchTouchEvent(event);
        }
    

    DecorView.superDispatchTouchEvent(MotionEvent)
    顶级View(DecorView)一般为ViewGroup,其方法中调用了ViewGroup#dispatchTouchEvent方法

        public boolean superDispatchTouchEvent(MotionEvent event) {
            // 此处调用的是ViewGroup的dispatchTouchEvent方法
            return super.dispatchTouchEvent(event);
        }
    
    • 2、ViewGroup事件分发 — onInterceptTouchEvent
      MotionEvent.ACTION_DOWN事件:
      先判断是否为MotionEvent.ACTION_DOWN事件,是,则清除 FLAG_DISALLOW_INTERCEPT 设置并且mFirstTouchTarget 设置为null,然后根据条件调用 onInterceptTouchEvent方法,来处理拦截事件。
      如果不是MotionEvent.ACTION_DOWN事件,且mFirstTouchTarget == null,则直接设置中断标记为true(intercepted = true),ViewGroup直接拦截其他事件(如MOVE和UP等)进行处理。
      FLAG_DISALLOW_INTERCEPT标志位:
      如果通过requestDisallowInterceptTouchEvent方法设置了此标志位,则子View可以以此来干预父View的事件分发过程(ACTION_DOWN事件除外,上面的原因),而这就是我们处理滑动冲突常用的关键方法。
        // ViewGroup源码:
        public boolean dispatchTouchEvent(MotionEvent 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);
                //清除FLAG_DISALLOW_INTERCEPT设置并且mFirstTouchTarget 设置为null
                resetTouchState();
            }
            // Check for interception.
            final boolean intercepted;//是否拦截事件
            if (actionMasked == MotionEvent.ACTION_DOWN
                    || mFirstTouchTarget != null) {
                //FLAG_DISALLOW_INTERCEPT是子View通过
                //requestDisallowInterceptTouchEvent方法进行设置的
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //调用onInterceptTouchEvent方法判断是否需要拦截
                    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;
            }
            ...
        }
    
    • 3、ViewGroup事件分发 — 子View遍历mFirstTouchTarget
      当ViewGroup不拦截事件,则遍历子View,找到事件接收的目标View(mFirstTouchTarget),条件:

      • View可见且没有播放动画:canViewReceivePointerEvents方法
      • 事件的坐标落在View的范围内:isTransformedTouchPointInView

      当mFirstTouchTarget不为null,则说明已经找到过了目标child,则newTouchTarget不为null,会跳出循环。
      但此时还没有将事件分发给子View,所以newTouchTarget为null,mFirstTouchTarget也是null。
      如果dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法返回了true,即子View消费了事件,则会将mFirstTouchTarget进行赋值为该子View,终止子View的遍历。此时,子View的遍历完成。
      dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)方法中,如果子View不为null,则调用了子View的child.dispatchTouchEvent分发方法,进行View的分发。

        // ViewGroup源码:
        public boolean dispatchTouchEvent(MotionEvent ev) {
            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;
                }
    
                //判断1,View可见并且没有播放动画。2,点击事件的坐标落在View的范围内
                //如果上述两个条件有一项不满足则continue继续循环下一个View
                if (!canViewReceivePointerEvents(child)
                        || !isTransformedTouchPointInView(x, y, child, null)) {
                    ev.setTargetAccessibilityFocus(false);
                    continue;
                }
    
                newTouchTarget = getTouchTarget(child);
                //如果有子View处理即newTouchTarget 不为null则跳出循环。
                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第三个参数child这里不为null
                //实际调用的是child的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();
                    //当child处理了点击事件,那么会设置mFirstTouchTarget 在addTouchTarget被赋值
                    newTouchTarget = addTouchTarget(child, idBitsToAssign);
                    alreadyDispatchedToNewTouchTarget = true;
                    //子View处理了事件,然后就跳出了for循环
                    break;
                }
            }
        }
    

    dispatchTransformedTouchEvent方法:

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

    mFirstTouchTarget的赋值:

        /**
         * Adds a touch target for specified child to the beginning of the list.
         * Assumes the target child is not already present.
         */
        private TouchTarget addTouchTarget(@NonNull View child, int pointerIdBits) {
            final TouchTarget target = TouchTarget.obtain(child, pointerIdBits);
            target.next = mFirstTouchTarget;
            mFirstTouchTarget = target;
            return target;
        }
    
    • 4、ViewGroup事件分发 — View#dispatchTouchEvent
      若遍历子View后,ViewGroup没有找到事件处理者(① ViewGroup没有子View 或 ② 子View处理了事件却在dispatchTouchEvent方法返回了false),则ViewGroup会去处理这个事件。
      dispatchTouchEvent方法返回了false,mFirstTouchTarget 必然为null,则再次调用自身的dispatchTransformedTouchEvent 方法(但传入的child为null),其内部会调用super.dispatchTouchEvent(event);方法,将调用View#dispatchTouchEvent,将事件传给View,至此,ViewGroup的分发过程完成。
       // 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);
       }
    
    • 5、View的事件分发View#dispatchTouchEvent
      mOnTouchListener.onTouch监听:
      View中首先判断是否设置了OnTouchListener监听(开发者自己实现的),若设置了且onTouch返回true,则之后的onTouchEvent方法不会调用;若没有设置监听或 onTouch 返回 false,则会调用onTouchEvent方法。
      onTouchEvent方法:
      在此方法中,具体的处理了各个事件。
      如果View设置成了disabled状态(即不可用),只要 CLICKABLELONG_CLICKABLE 有一个为true,就一定会消费这个事件(即onTouchEvent返回true),只是它看起来不可用。只有不可点击(clickablelongClickable同时为false),才会返回false,即onTouchEvent不消费此事件。
      performClick()方法:
      就点击事件而言,在ACTION_UP 事件的条件下,会调用performClickInternal方法(内部实际是performClick());在performClick()方法中,如果设置了OnClickListener,则会回调onClick方法。

    dispatchTouchEvent(MotionEvent)

         // View源码:
        //如果窗口没有被遮盖
        if (onFilterTouchEventForSecurity(event)) {
            if ((mViewFlags & ENABLED_MASK) == ENABLED && handleScrollBarDragging(event)) {
                result = true;
            }
            //noinspection SimplifiableIfStatement
            //当前监听事件
            ListenerInfo li = mListenerInfo;
            //需要特别注意这个判断当中的li.mOnTouchListener.onTouch(this, event)条件
            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;
            }
        }
    

    onTouchEvent(MotionEvent)

    // View源码:
    public boolean onTouchEvent(MotionEvent event) {
            final float x = event.getX();
            final float y = event.getY();
            final int viewFlags = mViewFlags;
            final int action = event.getAction();
    
            final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                    || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                    || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;
    
            // 判断是否不可用,但仍会消费事件
            if ((viewFlags & ENABLED_MASK) == DISABLED) {
                if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
                    setPressed(false);
                }
                mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
                // 此处重点
                // A disabled view that is clickable still consumes the touch
                // events, it just doesn't respond to them.
                return clickable;
            }
            if (mTouchDelegate != null) {
                if (mTouchDelegate.onTouchEvent(event)) {
                    return true;
                }
            }
    
            if (clickable || (viewFlags & TOOLTIP) == TOOLTIP) {
                switch (action) {
                    case MotionEvent.ACTION_UP:
                        ...
                                    if (!post(mPerformClick)) {
                                        // 其内部调用的是performClick方法
                                        performClickInternal();
                                    }
                        ...
                        break;
                        ...
                }
    
                return true;
            }
    
            return false;
        }
    

    performClick()

       // View源码:
        /**
         * Call this view's OnClickListener, if it is defined.  Performs all normal
         * actions associated with clicking: reporting accessibility event, playing
         * a sound, etc.
         *
         * @return True there was an assigned OnClickListener that was called, false
         *         otherwise is returned.
         */
        public boolean performClick() {
            final boolean result;
            final ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnClickListener != null) {
                playSoundEffect(SoundEffectConstants.CLICK);
                li.mOnClickListener.onClick(this);
                result = true;
            } else {
                result = false;
            }
    
            sendAccessibilityEvent(AccessibilityEvent.TYPE_VIEW_CLICKED);
            return result;
        }
    

    说明: setClickable 失效的原因:
    View的setOnClickListener会默认将View的clickable设置成true。
    View的setOnLongClickListener同样会将View的longClickable设置成true。

        // View源码:
        public void setOnClickListener(@Nullable OnClickListener l) {
            if (!isClickable()) {
                setClickable(true);
            }
            getListenerInfo().mOnClickListener = l;
        }
    
        public void setOnLongClickListener(@Nullable OnLongClickListener l) {
            if (!isLongClickable()) {
                setLongClickable(true);
            }
            getListenerInfo().mOnLongClickListener = l;
        }
    
    

    4、事件分发的相关问题:

    -- 问题:如果一个事件序列的 ACTION_DOWN 事件被 ViewGroup 拦截,此时子 View 调用 requestDisallowInterceptTouchEvent 方法有没有用?

    子View可以通过 requestDisallowInterceptTouchEvent方法干预父View的事件分发过程(ACTION_DOWN事件除外),而这就是我们处理滑动冲突常用的关键方法。

    requestDisallowInterceptTouchEvent 中,设置了 FLAG_DISALLOW_INTERCEPT 标志位,表示子View不希望此父级及其祖先使用 ViewGroup.onInterceptTouchEvent(MotionEvent) 拦截触摸事件。

    ACTION_DOWN 事件是清除了这个标志位的,所以,requestDisallowInterceptTouchEvent 的设置对ACTION_DOWN无效。

        // ViewGroup源码,实现的是ViewParent的抽象方法
        @Override
        public void requestDisallowInterceptTouchEvent(boolean disallowIntercept) {
            if (disallowIntercept == ((mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0)) {
                // We're already in this state, assume our ancestors are too
                return;
            }
    
            if (disallowIntercept) {
                mGroupFlags |= FLAG_DISALLOW_INTERCEPT;
            } else {
                mGroupFlags &= ~FLAG_DISALLOW_INTERCEPT;
            }
    
            // Pass it up to our parent
            if (mParent != null) {
                mParent.requestDisallowInterceptTouchEvent(disallowIntercept);
            }
        }
    
        // ViewGroup#dispatchTouchEvent
        public boolean dispatchTouchEvent(MotionEvent ev) {
            ...
            final int action = ev.getAction();
            final int actionMasked = action & MotionEvent.ACTION_MASK;
            if (actionMasked == MotionEvent.ACTION_DOWN) {
                ....
                //清除FLAG_DISALLOW_INTERCEPT设置并且mFirstTouchTarget 设置为null
                resetTouchState();
            }
            //是否拦截事件
            final boolean intercepted;
            if (actionMasked == MotionEvent.ACTION_DOWN || mFirstTouchTarget != null) {
                //FLAG_DISALLOW_INTERCEPT是子View通过
                //requestDisallowInterceptTouchEvent方法进行设置的
                final boolean disallowIntercept = (mGroupFlags & FLAG_DISALLOW_INTERCEPT) != 0;
                if (!disallowIntercept) {
                    //调用onInterceptTouchEvent方法判断是否需要拦截
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
            } else {
                intercepted = true;
            }
            ...
        }
    
    
    -- 问题:ACTION_DOWN 事件被子 View 消费了,那 ViewGroup 能拦截剩下的事件吗?如果拦截了剩下事件,当前这个事件 ViewGroup 能消费吗?子 View 还会收到事件吗?

    ACTION_DOWN 事件被子 View 消费后,mFirstTouchTarget 则不为null了,就会直接拦截其他事件,intercepted = true;,见下面的源码。
    设置了拦截,就不会再遍历子View进行事件分发了。则会取消cancelChild,拦截子View的事件。

       // ViewGroup#dispatchTouchEvent
       if (actionMasked == MotionEvent.ACTION_DOWN
               || mFirstTouchTarget != null) {
           // onInterceptTouchEvent方法的判断
           ......
       } else {
           // 重点在这里
           // There are no touch targets and this action is not an initial down
           // so this view group continues to intercept touches.
           intercepted = true;    // <---------------------------------------重点
       }
    
       ....
       // Dispatch to touch targets.
       if (mFirstTouchTarget == null) {
           // 没有子 View 消费事件,则传入 null 去分发,最终调用的是自身的 onTouchEvent 方法,进行处理 touch 事件
           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 {
                   // 如果 intercepted 就取消 cancelChild,这便是拦截子 View 事件的原理
                   final boolean cancelChild = resetCancelNextUpFlag(target.child)
                           || intercepted;   // <---------------------------------------重点
                   if (dispatchTransformedTouchEvent(ev, cancelChild,
                           target.child, target.pointerIdBits)) {
                       //内部会比较 pointerIdBits 和当前事件的 pointerIdBits,一致才会处理
                       //这便是 Down 事件处理后后续事件都交给该 View 处理的原理
                       handled = true;
                   }
                   if (cancelChild) {
                       if (predecessor == null) {
                           mFirstTouchTarget = next;
                       } else {
                           predecessor.next = next;
                       }
                       target.recycle();
                       // 没有next则为null,就结束了循环
                       target = next;
                       continue;
                   }
               }
               predecessor = target;
               target = next;
           }
       }
    
    -- 问题:当 View Disable 时,会消费事件吗?

    会消费事件,只是设为了不可用,可以看到,在源码中的注释为:

    A disabled view that is clickable still consumes the touch events, it just doesn't respond to them.
    [一个可点击的禁用view,仍然可以消费事件,它只是没有响应它们(事件)而已。]

        // View#onTouchEvent
       // 判断是否不可用,但仍会消费事件
       if ((viewFlags & ENABLED_MASK) == DISABLED) {
           if (action == MotionEvent.ACTION_UP && (mPrivateFlags & PFLAG_PRESSED) != 0) {
               setPressed(false);
           }
           mPrivateFlags3 &= ~PFLAG3_FINGER_DOWN;
           // 此处重点
           // A disabled view that is clickable still consumes the touch
           // events, it just doesn't respond to them.
           return clickable;
       }
    

    参考链接:
    一文读懂Android View事件分发机制
    必问的事件分发,你答得上来吗
    Android事件分发机制详解:史上最全面、最易懂
    要点提炼|开发艺术之View

    相关文章

      网友评论

          本文标题:View绘制及事件机制原理

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