View 的工作原理

初识 ViewRoot 和 DecorView

ViewRoot 对应于 ViewRootImpl 类,它是连接 WindowManager 和 DecorView 的纽带,View 的绘制流程通过 ViewRoot 来完成,在 ActivityThread 中,Activity 创建完毕后,会将 DecorView 添加到 Window中,同时会创建 ViewRootImpl 对象,并和 DecorView 建立关联。

View 的绘制流程是从 ViewRoot 的 performTraversals 方法开始,它经过 measure 用来测量 View 的宽和高,layout 来确定 View 在父容器中的放置位置,而 draw 则负责将 View 绘制在屏幕上。

理解 MeasureSpec

  • MeasureSpec 代表一个32位 int 值,高 2 位代表 SpecMode 测量模式,低 30 位代表 SpecSize 某种测试模式下的规格大小。
  • SpecMode 有三类
    1. UNSPECIFIED 父容器不对 View 有任何限制,要多大给多大,一般用于系统内部,表示一种测量状态。
    2. EXACTLY 父容器检测出 View 的精确大小,大小是 SpecSize 指定的值,对应于 LayoutParams 中的 match_parent 和具体数值两种模式。
    3. AT_MOST 父容器指定一个可用大小即 SpecSize ,View 的大小不能大于这个值,对应于LayoutParams 中的 wrap_content。
  • 对于顶级 View(DecorView),其MeasureSpec由窗口尺寸和自身LayoutParams共同决定。
  • 对于普通 View ,其MeasureSpec有父容器的 MeasureSpec 和自身的LayoutParams 共同决定。

普通 View 的 MeasureSpec 的创建规则

LayoutParams\parentSpecMode EXACTLY AT_MOST UNSPECIFIED
dp/px EXACTLY(childSize) EXACTLY(childSize) EXACTLY(childSize)
match_parent EXACTLY(parentSize) AT_MOST(parentSize) UNSPECIFIED(0)
wrap_content AT_MOST(parentSize) AT_MOST(parentSize) UNSPECIFIED(0)

总结
1. 当 View 采用固定宽高的时候,不管父容器的 SpecMode 是什么,View 的MeasureSpec 都是精确模式,大小遵循LayoutParams中的大小。
2. 当 View 宽高是 match_parent 时,如果父容器 SpecMode 是精确模式,那么 View 也是精确模式,大小是父容器的剩余的空间大小。如果父容器 SepcMode 是最大模式,那么 View 也是最大模式,大小不会超过父容器的剩余空间。
3. 当 View 宽高wrap_content 时,不管父容器的 SpecMode 是什么,View 的MeasureSpec 都是最大模式,大小不能超过父容器的剩余空间。

View 的测量过程

  • View 的 measure 的过程由ViewGroup 的 measureChildWithMargins 方法
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
        final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();

        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);
    }
  • 调用子元素的 measure 方法之前会通过 getChildMeasureSpec 方法得到子元素的MeasureSpec,与父容器的 MeasureSpec 和自身的 LayoutParams 有关。
  • ViewGroup 中的 child.measure 方法,是调用的 View 的 measure 方法,再调用 onMeasure 方法进行测量。

View 的工作流程

  • View 的 measure 过程是由 measure 方法完成,是一个 final 类型的方法,不能有子类重写,measure 方法中会调用 onMeasure 方法,方法如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
        getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
    } 

protected int getSuggestedMinimumWidth() {
        return (mBackground == null) ? mMinWidth : max(mMinWidth, mBackground.getMinimumWidth());
    }

public static int getDefaultSize(int size, int measureSpec) {
        int result = size;
        int specMode = MeasureSpec.getMode(measureSpec);
        int specSize = MeasureSpec.getSize(measureSpec);

        switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
        }
        return result;
    }

只需要看 SpecMode 的 EXACTLY 和 AT_MOST 两种情况,返回的大小是SpecSize ,是 View 测量后的大小。
getSuggestedMinimumWidth 方法,如果 View 没有设置背景,那么用 minWith 属性所指定的值,如果有背景,取两者之间的最大值。

  • 直接继承 View 的自定义控件,需要重写 onMeasure 方法,并设置 wrap_content 时的大小,否则相当于使用了match_parent,因为使用 wrap_content 时 SpecMode 是AT_MOST,根据查表这种情况下的 SpecSize 是 parentSize ,解决方式如下:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
        super.onMeasure(widthMeasureSpec, heightMeasureSpec);
        int widthMode = MeasureSpec.getMode(widthMeasureSpec);
        int withSize = MeasureSpec.getSize(widthMeasureSpec);
        int heightMode = MeasureSpec.getMode(heightMeasureSpec);
        int heightSize = MeasureSpec.getSize(heightMeasureSpec);
        if (widthMode == MeasureSpec.AT_MOST && heightMeasureSpec == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth,mHeight);
        } else if (widthMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(mWidth,heightSize);
        } else if (heightMode == MeasureSpec.AT_MOST) {
            setMeasuredDimension(withSize,mHeight);
        }
    }

你可能感兴趣的:(Android开发艺术探索)