Android----View 事件分发和绘制机制

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/m0_38089373/article/details/81145543

一.View 的事件分发

(一).概念

1.事件分发

View 的事件分发其实就是点击事件( MotionEvent )从产生后系统开始分发,到传递给一个具体的 View ( 或者Activity )的过程,View (或 Activity )会选择是否对事件进行消耗。

2.事件的类型和事件序列

(1)事件类型
  • MotionEvent.ACTION_DOWN 按下时产生的事件
  • MotionEvent.ACTION_MOVE 滑动时产生的事件
  • MotionEvent.ACTION_UP 抬起时产生的事件
  • MotionEvent.ACTION_CANCEL 发生异常时产生的事件
(2)事件序列

同一个事件序列指的是从按下时候到抬起时产生的一系列事件,通常以 DOWN 开始,中间有不定数个 MOVE ,最后以 UP 或者 CANCLE 结束。

3.分发对象和对应的方法

对事件的分发主要涉及三个对象,Activity , ViewGroup ,具体的 View,这三个对象按分发的层次依次是Activity -> ViewGroup -> 具体的 View 。而涉及分发的方法同样主要有三个:
- dispatchTouchEvent 对一个事件进行分发,可能是分发给下一层处理,或者分发给自己。
- onInterceptTouchEvent 这个方法只有 ViewGroup 有,用来判断对事件是否进行拦截,如果拦截就不会分发给下一层.
- onTouchEvent 对事件进行处理,消耗或者不消耗,不消耗就会返回给上层。对于 ViewGroup 和 View 这个方法还受到 OnTouchListener 和 enable 属性 的影响,具体的后面会阐述。

(二).事件分发

1.Activity 对事件的分发

public boolean dispatchTouchEvent(MotionEvent ev) {
        ...

        if (getWindow().superDispatchTouchEvent(ev)) {
            return true;
        }
        return onTouchEvent(ev);
    }

Activity 的事件的处理其实并不负责,即如果下层(不管是 ViewGroup 还是 View )消耗了这个事件,那么 if 语句就为 true , 则 dispatchTouchEvent 就返回 true 。如果没有消耗就自己对事件进行处理,即调用 onTouchEvent 方法。

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

        return false;
    }
    /** @hide */
    public boolean shouldCloseOnTouch(Context context, MotionEvent event) {
        final boolean isOutside =
                event.getAction() == MotionEvent.ACTION_DOWN && isOutOfBounds(context, event)
                || event.getAction() == MotionEvent.ACTION_OUTSIDE;
        if (mCloseOnTouchOutside && peekDecorView() != null && isOutside) {
            return true;
        }
        return false;
    }

Activity 的 onTouchEvent 会对这个事件进行判断,如果事件在窗口边界外就返回 true,dispatchTouchEvent 就返回 true ;如果在边界内就 返回 false ,最后 dispatchTouchEvent 也会返回 false 。这部分流程如图 (这是截自整体流程图的一部分)
View 事件分发1.png

2.View 对事件的分发

这里先说 View 对事件的分发是因为 ViewGroup 继承自 View ,ViewGroup 对事件的分发会调用到父类(也就是View )的方法,因此先理清 View 的分发有助于理解。

 public boolean dispatchTouchEvent(MotionEvent event) {
           ...
            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;
            }
            ...
            return result;
    }

可以看到 View 的事件的处理是先判断 mOnTouchListener !=null 和 View 设置 ENABLED 这两个条件成不成立,不过成立则 调用 onTouch 方法,且如果 onTouch 返回了 true ,那个事件就被消耗 ,View 的 dispatchTouchEvent 就返回 true ; 相反,如果条件不成立或者 onTouch 返回 false ,那么就会执行 View 的 onTouchEvent 方法。

public boolean onTouchEvent(MotionEvent event) {  
     ...
     final boolean clickable = ((viewFlags & CLICKABLE) == CLICKABLE
                || (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)
                || (viewFlags & CONTEXT_CLICKABLE) == CONTEXT_CLICKABLE;


    // 若可点击,包括LONG_CLICKABLE 或者 CLICKABLE
    if (((viewFlags & CLICKABLE) == CLICKABLE ||  
            (viewFlags & LONG_CLICKABLE) == LONG_CLICKABLE)) {  

                switch (event.getAction()) { 


                    case MotionEvent.ACTION_UP:  
                        boolean prepressed = (mPrivateFlags & PREPRESSED) != 0;  

                            ...

                            // 执行performClick() 
                            performClick();  
                            break;  


                    case MotionEvent.ACTION_DOWN:  
                        ...
                        break;  


                    case MotionEvent.ACTION_CANCEL:  
                        ...
                        break;


                    case MotionEvent.ACTION_MOVE:  
                        ...
                        break;  
                } //>> 若可点击,就返回true
                return true;  
            }  //>> 若不可点击,就返回false
            return false;  
        }
public boolean performClick() {  

        if (mOnClickListener != null) {  
            playSoundEffect(SoundEffectConstants.CLICK);  
            mOnClickListener.onClick(this);  
            return true;  
        }  
        return false;  
    }

在 onTouchEvent 方法中,如果 View 是可点击的,比如设置了 onClick 或者 onLongClick ,就会执行 onClick 方法,并且 onTouchEvent 返回 true ,如果是不可点击的就返回 false 。需要注意的是这里的 onTouchEvent 是可以被重写的。如果 onTouchEvent 返回 true 那么 View 的 dispatchTouchEvent 就返回 true ,事件就被消耗,如果 onTouchEvent 返回 false , 那么 dispatchTouchEvent 也返回 false ,这时 事件就交由上层处理,也就是 ViewGroup 。这部分流程如图

View 事件分发2.png

3.ViewGroup 对事件的分发

ViewGroup 对事件的分发也是从 dispatchTouchEvent 方法开始的,不同的是 ViewGroup 对了一个对事件进行拦截的方方法 onInterceptTouchEvent 。
    public boolean dispatchTouchEvent(MotionEvent ev) {

        final boolean intercepted;
                ...
                if (!disallowIntercept) {
                    intercepted = onInterceptTouchEvent(ev);
                    ev.setAction(action); // restore action in case it was changed
                } else {
                    intercepted = false;
                }
                ...
                //不拦截,分发给下一层
                if (!canceled && !intercepted) {

                        ...
                        if (dispatchTransformedTouchEvent(ev, false, child, idBitsToAssign)) {
                        // Child wants to receive touch within its bounds.
                        ...
                        }


                ...
                // 子View 不处理,分发给自己
             if (mFirstTouchTarget == null) {
                // No touch targets so treat this as an ordinary view.
                handled = dispatchTransformedTouchEvent(ev, canceled, null,
                        TouchTarget.ALL_POINTER_IDS);
            } ...
    }
 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) {  //没有 子View 就自己处理
                handled = super.dispatchTouchEvent(event); 
            } else {     //有就分发给下一层 
                handled = child.dispatchTouchEvent(event);
            }
            event.setAction(oldAction);
            return handled;
        }

ViewGroup 首先会判断 onInterceptTouchEvent 表示要不要拦截,这个方法也可以重写设置是否拦截,如果返回 false 就表示不拦截,这个事件就会分发给下一层,如果拦截就会分发给自己,当然如果子 View 不处理这个事件,还是会传到 ViewGroup ,ViewGroup 会调用父类也就是 View 的方法,后面的过程就和 View 对事件的处理是一样的 onTouch ,onTouchEvent , onClock …
这部分的流程如图:

View 事件分发 3.png

ViewGroup 对事件的拦截 onInterceptTouchEvent 并不是每一次都会调用

  if (actionMasked == MotionEvent.ACTION_DOWN) {

        cancelAndClearTouchTargets(ev);
        resetTouchState();//重置标志
    }
  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;
        }
    }

可以看到这里有三个条件:ACTION_DOWN 事件,mFirstTouchTarget 变量,FLAG_DISALLOW_INTERCEPT 这个标志。

1.对于 ACTION_DOWN 事件

在判断之前都会调用 resetTouchState 这个方法重新给 FLAG_DISALLOW_INTERCEPT 置位,因此只要是 DOWN 事件,onInterceptTouchEvent 就会调用。

2.对于 mFirstTouchTarget 这个变量

只要有 View 对事件进行处理,那么这个事件的后续事件就会直接交给这个 View ,mFirstTouchTarget 就直接指向 View , 正常情况下不会再询问 ViewGroup 是否拦截。而特殊情况就是下面这个。

3.对于 FLAG_DISALLOW_INTERCEPT

子 View 可以重置这个标志,使得 disallowIntercept 的值改变从而可能会重新 onInterceptTouchEvent 对事件进行拦截。

View 事件分发 4.png

到这里View 的事件分发的各个流程就已经讲完,最后是一个整体的流程:

View 事件分发机制.png

二.View 的绘制

(一).概念

1.绘制机制

View 的绘制机制实际上指的是 View 的三大流程
- 测量流程,测量 View 的大小,对应 measure 方法。
- 布局流程,有了 View 的大小后确定 View 的位置,对应 layout 方法。
- 绘制流程,对 View 的颜色,内容等进行绘制,对应 draw 方法。

View 的绘制从 ViewRootImpl 的 performTraversals 开始,首先会调用 performMeasure 方法,在这个方法中会一个 View 会调用 measure 去测量自己的大小,在此之前会调用 onMeasure 去测量子 View 的大小,这样层层调用,最后就会完成整个测量过程,后面的 layout 和 draw 的过程也是大致如此。流程如图
View 绘制1.png

2.MeasureSpec

MeasureSpec 是一个测量规格,在测量一个 View 的时候 从父类计算出来的 MeasureSpec 会传给这个 View ,同时会根据 View 自身的 LayoutParams 属性,也就是指定的一些 MATCH_PARENT, WRAP_CONTENT,xxdp 等属性最终一起决定 View 的大小。

MeasureSpec 是一个 int 值,有32位,高2位代表 Mode ,后30 位Size .之所以将两个值包装在在一个 int 是因为这样可以减少减少对象的分配。而 Mode 表示测量模式,有三种测量模式分为别
- UPSPECIFIED ,未指定,父 View 对子 View 不做任何限制
- EXACTLY,精确,父 View 给子 View 的大小是一个确定的值,为 Size
- AT_MOST,最大,父 View 给子 View 的大小 是一个不确定的值,最大为 Size

具体的计算就在下面的方法中

protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {

        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
        //这里的第一个参数就是 父View 的  MeasureSpec
        //第二个参数就是父 View 对子View 的位置限制 padding 
        //和 子View 对自己的位置限制 margin 和 已使用的宽度  widthUsed
        //第三参数就是 xml 指定的子 View 宽度
        final int childWidthMeasureSpec = getChildMeasureSpec(parentWidthMeasureSpec,
                mPaddingLeft + mPaddingRight + lp.leftMargin + lp.rightMargin
                        + widthUsed, lp.width);

        final int childHeightMeasureSpec = getChildMeasureSpec(parentHeightMeasureSpec,
                mPaddingTop + mPaddingBottom + lp.topMargin + lp.bottomMargin
                        + heightUsed, lp.height);

        child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
    }

在父 View 的 MeasureSpec 确定后,会传递给子 View ,子View 就会根据这个 MeasureSpec 和自己的 LayoutParams 属性,计算出自己的 MeasureSpec 和 大小(即 Size),然后就会将这个 MeasureSpec 传递给子View 的 子View, 从而遍历测量完所有的 View。

    public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
        int specMode = MeasureSpec.getMode(spec);
        int specSize = MeasureSpec.getSize(spec);

        //父 View 减去一个 宽度/高度位置限制 就是 父View 给 子View 的 大小
        int size = Math.max(0, specSize - padding);

        int resultSize = 0;
        int resultMode = 0;

        switch (specMode) {
        // Parent has imposed an exact size on us
        //父 View 是 EXACTLY
        case MeasureSpec.EXACTLY:
            // 子View 是 xxdp
            if (childDimension >= 0) {
                resultSize = childDimension; //使用 子View 指定的
                resultMode = MeasureSpec.EXACTLY;

            // 子 View 是 MATCH_PARENT
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size. So be it.
                resultSize = size; //使用父 View 给的
                resultMode = MeasureSpec.EXACTLY;

            //子 View 是 WRAP_CONTENT
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size; //使用父 View 给的
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent has imposed a maximum size on us
         //父 View 是 AT_MOST
        case MeasureSpec.AT_MOST:
            // 子View 是 xxdp
            if (childDimension >= 0) {
                // Child wants a specific size... so be it
                resultSize = childDimension; //使用 子View 指定的
                resultMode = MeasureSpec.EXACTLY;

             // 子 View 是 MATCH_PARENT
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size, but our size is not fixed.
                // Constrain child to not be bigger than us.
                resultSize = size; //使用父 View 给的
                resultMode = MeasureSpec.AT_MOST;


            //子 View 是 WRAP_CONTENT
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size. It can't be
                // bigger than us.
                resultSize = size; //使用父 View 给的
                resultMode = MeasureSpec.AT_MOST;
            }
            break;

        // Parent asked to see how big we want to be
        //父 View 是 UNSPECIFIED
        case MeasureSpec.UNSPECIFIED:
            // 子View 是 xxdp
            if (childDimension >= 0) {
                // Child wants a specific size... let him have it
                resultSize = childDimension; //使用 子View 指定的
                resultMode = MeasureSpec.EXACTLY;

             // 子 View 是 MATCH_PARENT
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // Child wants to be our size... find out how big it should
                // be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 默认为 0
                resultMode = MeasureSpec.UNSPECIFIED;

            //子 View 是 WRAP_CONTENT
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // Child wants to determine its own size.... find out how
                // big it should be
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size; 默认为 0
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
        }
        //noinspection ResourceType
        //将 Mode 和 Size 包装成 MeasureSpec
        return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
    }

子View 的 MeasureSpec 确定就是上述过程,这个过程可用下图表示
View 绘制 2.png
理解这个图只需要注意下面几点
- 只要子 View 是 xxdp 的,不管父View 是什么模式,子 View 都是使用自己的指定的大小 childDimension.
- 如果父 View 是 确定的 (EXACTLY),那么 子View 是 MATCH_PARENT 也就是充满父 View 的话,子View 也是确定的 EXACTLY ;如果子 View 是 WRAP_CONTENT,那么子 View 就是不确定的,但是不能超过父 View 的大小,因此子View 就是 AT_MOST 。
- 如果父 View 是不确定的 (AT_MOST ),那么不管子View 是 MATCH_PARENT 还是 WRAP_CONTENT ,子 View 都是不确定的。
- 如果父 View 是未指定的 (UNSPECIFIED),那么子 View 也是未指定的,size 也就没意义,即为 0 。

(二).三个流程

View 的三个流程都都是从 ViewRootImpl 的 performTraversals 开始的,而且都是从 DecorView 开始的,这里就不对具体的情况进行梳理,而是从宏观的角度却分析,三个流程是如果在 ViewGroup 到其中的子View 中进行工作的。

1.measure

    private void performTraversals() {
    ...
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    ...
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    ...

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

在上述过程中,因为第一个 measureSpec 的产生总是布满全屏的, 即 measureSpec 是确定的 EXACTLY, size 就为屏幕大小.在 performMeasure 就会开始对 DecorView (也就是一个 ViewGroup ) 进行测量.

private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
        if (mView == null) {
            return;
        }
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
        try {
        //对 DecorView 开始测量
            mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
    }

因为 ViewGroup 继承自 View ,首先看 View 的 measure

public final void measure(int widthMeasureSpec, int heightMeasureSpec) {
        ...
        if (cacheIndex < 0 || sIgnoreMeasureCache) {
            // measure ourselves, this should set the measured dimension flag back
            onMeasure(widthMeasureSpec, heightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        ...

    }
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    //测量自己的大小,一旦完成,View 的测量也就结束,这是 View 默认的方法,具体不同的 View 会有不同的测量方式.
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    }

在 View 中 measure 是一个 final 方法,因此不能被重写,在里面会调用 onMeasure 方法,这个方法就可以被重写,接下看 具体的某一个的 ViewGroup ( FrameLayout )中的这个方法.

    @Override
    protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        int count = getChildCount();
        ...

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            if (mMeasureAllChildren || child.getVisibility() != GONE) {
                measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);
                ...
            }
        }
        ...
         setMeasuredDimension(resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec,
                        childState << MEASURED_HEIGHT_STATE_SHIFT));
        ...
    }    

在 FrameLayout 中的 onMeasure 首先会对去调用 measureChildWithMargins 去计算自己的 MeasureSpec 然后就去测量子 View 的大小,等所有的子 View 测量好了,就会测量自己的的大小.而子 View 会重复这个两个方法,最后完成所有 View 的 测量.这个流程如图所示:

View 绘制2.png

2.layout

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        ...

        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());

        ...
    }

在 performLayout 会直接 调用 host 的 layout ,这个 host 实际上就是 DecorView ,DecorView 是一个 ViewGroup ,首先看 ViewGroup 的 layout 方法.

 @Override
    public final void layout(int l, int t, int r, int b) {
        ...
        super.layout(l, t, r, b);
        ...
    }

    @Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);

在 ViewGroup 中 layout 是一个 final 的方法,在里面会调用父类的layout 方法,也就是 View 的 layout 方法.这里先说明 onLayout 方法,在这里是一个抽象方法,因为不同的 ViewGroup 对子 View 的位置安排是不一样的,因此具体的 onLayout 需要具体的继承类去实现.先看 View 中的 layout 方法

 @SuppressWarnings({"unchecked"})
    public void layout(int l, int t, int r, int b) {

        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //setFrame 确定自己的位置
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        ....

        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        //调用 onLayout
        onLayout(changed, l, t, r, b);

       ...
    }

首先在 layout 方法中会确定自己的位置,即 left,top,bottom,right 这个四个属性,接着就会调用 onLayout ,如果这是一个 View,那么 onLayout 就是一个空方法,如果这是一个 ViewGroup ,那么在这方法内就会去确定 子View 的位置.比如 FrameLayout 中.

    @Override
    protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        layoutChildren(left, top, right, bottom, false /* no force left gravity */);
    }

    void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {

        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            ...
            ...
             child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
      }

这部分的流程如图:

View 绘制 3.png

3.draw

draw 的绘制基本就包含五个步骤:
- 绘制背景
- 绘制自己的内容
- 绘制子 View
- 绘制foreground,比如滚动条
- 绘制一些高光

这个过程对于 View 和 ViewGroup 是一样的,只不过 View 中 不会绘制自己的子 View ,因此是个 dispatchDraw(canvas) 在这里是个空方法.具体的流程如图:

View 绘制4.png

猜你喜欢

转载自blog.csdn.net/m0_38089373/article/details/81145543