美文网首页
View篇_04view绘制流程

View篇_04view绘制流程

作者: 冉桓彬 | 来源:发表于2018-05-08 23:13 被阅读0次
参考文章 :

目标

1. measure();
2. layout();
3. draw();

在进行源码分析之前, 先根据setContentView源码分析流程画出一个Activity布局图;


为了展示的方便, 上图在层级之间留了空白;

一、ViewRootImpl绘制流程起始点

1.1 ViewRootImpl.requestLayout
@Override
public void requestLayout() {
    // mHandlingLayoutInLayoutRequest为测量控制器, 测量中置为true, 结束或未开始测量置为false;
    if (!mHandlingLayoutInLayoutRequest) {
        // 1. 为了保证View的测量绘制单线程执行, 在测量开始之前, 需要检测当前线程;模块<1.6>
        // 2. 线程安全之后才会进行测量布局绘制等操作;模块<1.7>
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
1.2 ViewRootImpl.checkThread:
void checkThread() {
    // 1. 注意这里的检测方式, 并不是使用的"main".Thread.currentThread(), 而是将我们在初始化ViewRootImpl
    //    时所在的线程保存下来, 然后在这里与当前线程进行比较, 如果是同一个线程, 则不会抛异常, 反之抛出异常, 
    //    然后注意异常的提示内容: 当前线程不是创建ViewRootImpl的线程;
    // 2. 所以其实通常所说不能在非主线程中做UI操作, 以及不能在非UI线程中操作UI其实不算准确, 但是为何这句话
    //    这么流行, 因为onXXX的回调都是在主线程中执行, 在这条线程里执行onResume时会初始化ViewRootImpl, 
    //    所以这里的ViewRootImpl持有主线程的Thread;
    // 3. 1和2说的感觉又像是废话, 归根结底就是一句话, 不要在非ViewRootImpl初始化线程中操作ViewRootImpl
    //   下的View;
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
          "Only the original thread that created a view hierarchy can touch its views.");
    }
}
1.3 ViewRootImpl.performTraversals:
private void performTraversals() {
    // mView这里指向的是DecorView, 将DecorView赋值给host;
    final View host = mView;
    WindowManager.LayoutParams lp = mWindowAttributes;
    int desiredWindowWidth;
    int desiredWindowHeight;
    Rect frame = mWinFrame;
    if (mWidth != frame.width() || mHeight != frame.height()) {
        // frame与SurfaceHolder有关, 为当前View附属的区域的大小, 也就是当前DecorView的大小;
        mWidth = frame.width();
        mHeight = frame.height();
    }
    // 1. 通过getRootMeasureSpec算出ChildMeasureSpec, 即DecorView的MeasureSpec;
    // 2. getRootMeasureSpec内部分析可知, 此时DecorView的mode只有两种模式EXACTLY/AT_MOST;
    // 3. 而结合实际情况, DecorView的specMode为MeasureSpec.EXACTLY;
    int childWidthMeasureSpec = getRootMeasureSpec(mWidth, lp.width);
    int childHeightMeasureSpec = getRootMeasureSpec(mHeight, lp.height);
    // childWidthMeasureSpec与childHeightMeasureSpec其实均为DecorView的MeasureSpec;
    // 通过performMeasure测量计算DecorView及其子类的MeasureSpec;
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    // host指向跟节点, 对应Activity为DecorView;
    int width = host.getMeasuredWidth();
    int height = host.getMeasuredHeight();
    boolean measureAgain = false;
    ...
    layoutRequested = true;
    final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
    if (didLayout) {
        // 测量完成之后对View进行布局;
        performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    }
    // 测量完成之后对View进行渲染;
    performDraw();
    mIsInTraversal = false;
}
1.4 ViewRootImpl.getRootMeasureSpec
private static int getRootMeasureSpec(int windowSize, int rootDimension) {
    int measureSpec;
    // 此时rootDimension指的实际为DecorView的尺寸, 而DecorView的尺寸为确定值;
    // (1) layout_width指定为MATCH_PARENT, 由于显示区域的尺寸的确定的, 所以此时DecorView的size
    //     为确定值, mode为EXACTLY;
    // (2) layout_width指定为WRAP_CONTENT, DecorView的尺寸为不确定值, 但是他又有一个极限值, 不能
    //     超过父类规定的最大尺寸, 所以模式为AT_MOST;
    // (3) layout_width明确指定了值, 则size取xml中设置的值, 模式为EXACTLY;
    switch (rootDimension) {
        case ViewGroup.LayoutParams.MATCH_PARENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.EXACTLY);
            break;
        case ViewGroup.LayoutParams.WRAP_CONTENT:
            measureSpec = MeasureSpec.makeMeasureSpec(windowSize, MeasureSpec.AT_MOST);
            break;
        default:
            measureSpec = MeasureSpec.makeMeasureSpec(rootDimension, MeasureSpec.EXACTLY);
            break;
        }
        return measureSpec;
    }
}
1.5 ViewRootImpl.performMeasure:
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
    // 结合上文可知这里传入的childWidthMeasureSpec/childHeightMeasureSpec只有两种情况,
    // 既MeasureSpec.EXACTLY/MeasureSpec.AT_MOST
    mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}

二、测量(Measure) :

2.1 FrameLayout.onMeasure:

DecorView继承自FrameLayout, 而measure为final类型, 只有onMeasure允许被子类重写;

// 1.再次强调测量是从根节点开始,而根节点指的是DecorView,而非Activity.setContentView
//   中传入的布局的根节点;
// 2.widthMeasureSpec与heightMeasureSpec均为当前节点的父节点的参数;
@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    int count = getChildCount();
    final boolean measureMatchParentChildren =
                MeasureSpec.getMode(widthMeasureSpec) != MeasureSpec.EXACTLY ||
                MeasureSpec.getMode(heightMeasureSpec) != MeasureSpec.EXACTLY;
    mMatchParentChildren.clear();
    int maxHeight = 0;
    int maxWidth = 0;
    int childState = 0;
    // 遍历子View, 对子View进行测量;
    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        // 遍历子View(非GONE状态), INVISIBLE虽然也不可见, 但是有占位的作用, 所以也需要进行测量;
        if (mMeasureAllChildren || child.getVisibility() != GONE) {
            // 1.这里采用的递归的方式, 递归的出口是直到childView为View类型或者childView的childCount==0;
            // 2.否则就是根据parentMeasureSpec以及自身size获取自身的measureSpec;
            // 3.然后在根据自己的measureSpec去测量childView的measureSpec;
            measureChildWithMargins(child, widthMeasureSpec, 0, heightMeasureSpec, 0);  
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            // 取所有ChildView的size的最大值;
            maxWidth = Math.max(maxWidth, child.getMeasuredWidth() + lp.leftMargin + lp.rightMargin);
            maxHeight = Math.max(maxHeight, child.getMeasuredHeight() + lp.topMargin + lp.bottomMargin);
            ...
        }
    }
    // for循环结束也就是表示所有的View的测量工作都完成了, 接下来开始从最底层依次获取每个View的实际尺寸;
    // 计算maxWidth/maxHeight, 为后边ParentView测量自身作准备;
    maxWidth += getPaddingLeftWithForeground() + getPaddingRightWithForeground();
    maxHeight += getPaddingTopWithForeground() + getPaddingBottomWithForeground();

    maxHeight = Math.max(maxHeight, getSuggestedMinimumHeight());
    maxWidth = Math.max(maxWidth, getSuggestedMinimumWidth());

    ...
    // 根据childView的最大值设置自身尺寸的最大值;
    setMeasuredDimension(
                resolveSizeAndState(maxWidth, widthMeasureSpec, childState),
                resolveSizeAndState(maxHeight, heightMeasureSpec, childState << MEASURED_HEIGHT_STATE_SHIFT));

    count = mMatchParentChildren.size();
    ...
}
2.2 ViewGropu.measureChildWithMargins:
// parentWidthMeasureSpec: 当前View的父View对该View的约束;
// parentHeightMeasureSpec: 当前View的父View对该View的约束;
protected void measureChildWithMargins(View child,
            int parentWidthMeasureSpec, int widthUsed,
            int parentHeightMeasureSpec, int heightUsed) {
    final MarginLayoutParams lp = (MarginLayoutParams) child.getLayoutParams();
    // 根据parentMeasureSpec算出childMeasureSpec;
    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);
    // 1.根据下文对getChildMeasureSpec()方法的分析可知, 对child进行测量并不表示获取到了childView的
    //   精确大小, 而仅仅是根据parentView的约束拿到了parentView对自己的约束值;
    // 2.此时再根据该约束值去测量自己的childView;
    // 3.如果child为View类型, 则调用View.onMeasure方法, 如果child为GroupView类型, 则调用
    //   ViewGroup.onMeasure方法;
    child.measure(childWidthMeasureSpec, childHeightMeasureSpec);
}
2.3 ViewGropu.getChildMeasureSpec
// spec: 当前View的父View对该View的约束;
public static int getChildMeasureSpec(int spec, int padding, int childDimension) {
    // 1. 算出ParentView的mode;
    // 2. 算出ParentView的size;
    int specMode = MeasureSpec.getMode(spec);
    int specSize = MeasureSpec.getSize(spec);
     // 通过ParentView尺寸 - ParentPadding - ChildMargin算出ChildView的尺寸;
    int size = Math.max(0, specSize - padding);
    int resultSize = 0;
    int resultMode = 0;
    // specSize是当前View的parentView的specSize;
    switch (specMode) {
        case MeasureSpec.EXACTLY://如果parentView尺寸为精确值时;
            if (childDimension >= 0) {
                // 1.如果childSize此时有尺寸, 则childMode尺寸模式此时也是精确值;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 2.如果此时childSize的尺寸与parentSize一致, 那么此时由于parentSize是精确值, 所以此时
                //   childSize的尺寸也是精确值, childMode自然也是精确模式;
                resultSize = size;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 3.如果此时childSize是包裹值, 但是parentSize是精确值, 所以先给childSize设置一个临时的值,
                //   但是该临时的值有上限, 所以模式为AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.AT_MOST:// 如果此时parentMode为AT_MOST模式, 表示此时parentSize其实为不确定值;
            if (childDimension >= 0) {
                // 1.如果childSize此时有尺寸, 则childMode尺寸模式此时也是精确值;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 2.如果此时childSize为MATCH_PARENT, 但是parentSize此时为不确定值, 所以此时childSize也是
                //   不确定值, 所以此时childMode为AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                // 3.如果此时childSize是包裹值, parentSize也是包裹值, 所以先给childSize设置一个临时的值,
                //   但是该临时的值有上限, 所以模式为AT_MOST;
                resultSize = size;
                resultMode = MeasureSpec.AT_MOST;
            }
            break;
        case MeasureSpec.UNSPECIFIED:// 此时parentMode为MeasureSpec.UNSPECIFIED, 表示parentView大小为任意值;
            if (childDimension >= 0) {
                // 1.如果childSize此时有尺寸, 则childMode尺寸模式此时也是精确值;
                resultSize = childDimension;
                resultMode = MeasureSpec.EXACTLY;
            } else if (childDimension == LayoutParams.MATCH_PARENT) {
                // 由于parentSize为任意值, 而childSize此时又表示随parentSize的尺寸, 所以childSize也是任意值;
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            } else if (childDimension == LayoutParams.WRAP_CONTENT) {
                resultSize = View.sUseZeroUnspecifiedMeasureSpec ? 0 : size;
                resultMode = MeasureSpec.UNSPECIFIED;
            }
            break;
    }
    return MeasureSpec.makeMeasureSpec(resultSize, resultMode);
}
2.4 View.onMeasure:
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    /**
     * 对当前View的mMeasuredWidth和mMeasuredHeight进行赋值, 结合下面代码可知, mMeasuredWidth
     * 和mMeasuredHeight并不一定精确, 在实际运行中可能会动态变化;
     */
    setMeasuredDimension(getDefaultSize(getSuggestedMinimumWidth(), widthMeasureSpec),
                         getDefaultSize(getSuggestedMinimumHeight(), heightMeasureSpec));
}

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

/**
 * measureSpec是根据ParentMode, ParentSize, ChildSize一起算出来的;
 */
public static int getDefaultSize(int size, int measureSpec) {
    int result = size;
    int specMode = MeasureSpec.getMode(measureSpec);
    int specSize = MeasureSpec.getSize(measureSpec);
    /**
     * 1. 结合前几个模块可知, 其实只有模式为EXACTLY时, ChildView的size才是精确的, 另外两种模式下
     *    的result都只能算作是参考值;
     */
    switch (specMode) {
        case MeasureSpec.UNSPECIFIED:
            result = size;
            break;
        case MeasureSpec.AT_MOST:
        case MeasureSpec.EXACTLY:
            result = specSize;
            break;
    }
    return result;
}

三、布局(layout) :

3.1 ViewRootImpl.performLayout:
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth, int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;
    final View host = mView;
    // host指向DecorView, 切入到FrameLayout;模块<3.2>
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    ...
    mInLayout = false;
}
3.2 FrameLayout.layout:
View.class:
// ViewGroup.layout直接调用了View.layout;  
public void layout(int l, int t, int r, int b) {

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    // 计算子布局是否发生了变化;
    boolean changed = isLayoutModeOptical(mParent) ? setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    /**
     * onLayout被调用有两个情况:
     * 1. changed = true;
     * 2. 显示调用requestLayout()触发mPrivateFlags &= PFLAG_LAYOUT_REQUIRED运算;
     */
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        // 对ChildView进行布局;模块<3.3>
        onLayout(changed, l, t, r, b); 
    }
}
3.3 FrameLayout.onLayout:
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    layoutChildren(left, top, right, bottom, false);
}

void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
    final int count = getChildCount();

    final int parentLeft = getPaddingLeftWithForeground();
    final int parentRight = right - left - getPaddingRightWithForeground();

    final int parentTop = getPaddingTopWithForeground();
    final int parentBottom = bottom - top - getPaddingBottomWithForeground();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();

            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft;
            int childTop;

            int gravity = lp.gravity;
            if (gravity == -1) {
                gravity = DEFAULT_CHILD_GRAVITY;
            }

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;

            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                     lp.leftMargin - lp.rightMargin;
                    break;
                case Gravity.RIGHT:
                    if (!forceLeftGravity) {
                        childLeft = parentRight - width - lp.rightMargin;
                        break;
                    }
                case Gravity.LEFT:
                default:
                    childLeft = parentLeft + lp.leftMargin;
            }
            switch (verticalGravity) {
                case Gravity.TOP:
                    childTop = parentTop + lp.topMargin;
                    break;
                case Gravity.CENTER_VERTICAL:
                    childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                    lp.topMargin - lp.bottomMargin;
                    break;
                case Gravity.BOTTOM:
                    childTop = parentBottom - height - lp.bottomMargin;
                    break;
                default:
                    childTop = parentTop + lp.topMargin;
            }
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

相关文章

网友评论

      本文标题:View篇_04view绘制流程

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