View的绘制-layout流程详解

栏目: IOS · Android · 发布时间: 5年前

内容简介:根据 measure 测量出来的宽高,确定所有 View 的位置。View 本身的位置是通过它的四个点来控制的:以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删减。
View的绘制-layout流程详解

作用

根据 measure 测量出来的宽高,确定所有 View 的位置。

具体分析

View 本身的位置是通过它的四个点来控制的:

View的绘制-layout流程详解

以下涉及到源码的部分都是版本27的,为方便理解观看,代码有所删减。

layout 的流程

先通过 measure 测量出 ViewGroup 宽高,ViewGroup 再通过 layout 方法根据自身宽高来确定自身位置。当 ViewGroup 的位置被确定后,就开始在 onLayout 方法中调用子元素的 layout 方法确定子元素的位置。子元素如果是 ViewGroup 的子类,又开始执行 onLayout,如此循环往复,直到所有子元素的位置都被确定,整个 View 树的 layout 过程就执行完了。

在上一节 《View的绘制-measure流程详解》 中说过,View 的绘制流程是从 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 开始的。在执行完 performMeasure() 后,开始执行 performLayout 方法:(以下源码有所删减)

//ViewRootViewImpl 类
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
    int desiredWindowHeight) {
    final View host = mView;
    /*代码省略*/
    //开始执行 View 的 layout 方法
    host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
    /*代码省略*/
}
复制代码

如此,就执行了 View 的 layout 方法:

//View 类
public void layout(int l, int t, int r, int b) {
    /*代码省略*/
    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    //确定 View 的四个点后,调用 setOpticalFrame/setFrame 方法来控制 View 位置。
    //方法 1--->setOpticalFrame
    //方法 2--->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);
        /*代码省略*/
    }
    /*代码省略*/
}

//方法 1---->setOpticalFrame 
//View 类
private boolean setOpticalFrame(int left, int top, int right, int bottom) {
    Insets parentInsets = mParent instanceof View ?
            ((View) mParent).getOpticalInsets() : Insets.NONE;
    Insets childInsets = getOpticalInsets();
    //内部最终也是调用 setFrame  方法
    return setFrame(
            left   + parentInsets.left - childInsets.left,
            top    + parentInsets.top  - childInsets.top,
            right  + parentInsets.left + childInsets.right,
            bottom + parentInsets.top  + childInsets.bottom);
}

//方法 2--->setFrame
//View 类
protected boolean setFrame(int left, int top, int right, int bottom) {
    boolean changed = false;

    if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
        changed = true;
        /*代码省略*/
        int oldWidth = mRight - mLeft;
        int oldHeight = mBottom - mTop;
        int newWidth = right - left;
        int newHeight = bottom - top;
        //控件的大小和位置有没有改变
        boolean sizeChanged = (newWidth != oldWidth) || (newHeight != oldHeight);

        // Invalidate our old position
        invalidate(sizeChanged);
        //对 View 的四个点赋值
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
        mPrivateFlags |= PFLAG_HAS_BOUNDS;


        if (sizeChanged) {
            //这里 sizeChange 方法内部调用了 onSizeChanged 方法。
            //所以当控件的大小和位置改变的时候会回调 onSizeChanged 方法
            //方法 3--->sizeChange
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }
        /*代码省略*/
    }
    return changed;
}

//方法 3--->sizeChange
// View 类
private void sizeChange(int newWidth, int newHeight, int oldWidth, int oldHeight) {
    //执行 onSizeChanged 方法
    onSizeChanged (newWidth, newHeight, oldWidth, oldHeight);
    if (mOverlay != null) {
        mOverlay.getOverlayView().setRight(newWidth);
        mOverlay.getOverlayView().setBottom(newHeight);
    }
    rebuildOutline();
}
复制代码

接下来我们再看 onLayout 方法,在 View 中找到 onLayout 方法,会发现这是一个空实现的方法,里面什么也没有执行,那么我们就在 ViewGroup 中找 onLayout 的实现,发现只是一个抽象方法。

//View 类
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
}
复制代码
//ViewGroup 类
@Override
protected abstract void onLayout(boolean changed,
        int l, int t, int r, int b);
复制代码

在 View 类中 onLayout 是一个空实现不难理解,因为如果一个控件继承了 View ,它是没有子元素的,不需要确定子元素的位置,只需要确定自己的位置就够了。

在 ViewGroup 中是一个抽象方法,意思也很明显了,在控件继承自 ViewGroup 的时候,我们必须重写 onLayout 方法。因为如 LinearLayoutRelativeLayout ,他们的布局特性都是不一样的,需要各自根据自己的特性来进行制定确定子元素位置的规则。

下面以 LinearLayout 为例,分析 onLayout 里面的逻辑。

LinearLayout 的 onLayout 逻辑

//LinearLayout 类
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    if (mOrientation == VERTICAL) {
        //垂直排列
        layoutVertical(l, t, r, b);
    } else {
        //水平排列
        layoutHorizontal(l, t, r, b);
    }
}
复制代码

layoutVerticallayoutHorizontal 执行流程类似,就分析 layoutVertical

//LinearLayout 类
void layoutVertical(int left, int top, int right, int bottom) {
    final int paddingLeft = mPaddingLeft;

    int childTop;
    int childLeft;

    // Where right end of child should go
    final int width = right - left;
    int childRight = width - mPaddingRight;

    // Space available for child
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();
    /*省略代码*/

    for (int i = 0; i < count; i++) {
        //遍历子 View
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            //获取子元素的宽度
            final int childWidth = child.getMeasuredWidth();
            //获取子元素的高度
            final int childHeight = child.getMeasuredHeight();

            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }
            //加上 子元素的 topMargin 属性值
            childTop += lp.topMargin;
            //设置子元素的 位置
            //方法 1 ----->setChildFrame
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            /*加上子元素的高度、bottomMargin、偏移量,就是下一个子元素的 初始 top。
            如此,子元素就从上到下排列,符合我们所知的 LinearLayout 的特性*/
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);

            i += getChildrenSkipCount(child, i);
        }
    }
}

//方法 1 ----->setChildFrame
//LinearLayout 类
private void setChildFrame(View child, int left, int top, int width, int height) {
    //最终又调用了子元素的 layout 方法.
    child.layout(left, top, left + width, top + height);
}
复制代码

可以看到,在遍历子元素后,又调用了子元素的 layout 方法。子元素如果是继承 ViewGroup,还是会调用到子元素的 onLayout 方法,遍历自己的子元素,调用自己子元素的 layout 方法,如此循环递归,就完成了整个 View 树的 layout 流程。

getWidth、getMeasureWidth分析

getWidth 获取的值和 getMeasureWidth 获取的值有什么不同吗? 首先看 getWidth 源码:

//View 类
public final int getWidth() {
    return mRight - mLeft;
}
复制代码

然后再看 mRight 和 mLeft 赋值(在我们分析 layout 的时就有展现):

以下为超精简代码,哈哈:

//View 类
public void layout(int l, int t, int r, int b) {
    //setOpticalFrame 最终也是调用了 setFrame 方法
     boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
}

//View 类
protected boolean setFrame(int left, int top, int right, int bottom) {
    //对四个值进行赋值
    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
}
复制代码

然后我们再回顾调用 layout 的方法:

//LinearLayout 类
void layoutVertical(int left, int top, int right, int bottom) {
    for (int i = 0; i < count; i++) {
        final int childWidth = child.getMeasuredWidth();
        final int childHeight = child.getMeasuredHeight();
        //将获取的 childWidth 和 childHeight 传入进去
        setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
    }
}
//LinearLayout 类
private void setChildFrame(View child, int left, int top, int width, int height) {
    //子元素 layout 的方法中传入的 right  = left + width
    //子元素 layout 的方法中传入的 bottom  = left + height
    child.layout(left, top, left + width, top + height);
}
复制代码

这下就一目了然了,在 getWidth() 方法中 mRight - mLeft 其实就是等于 childWidth ,也就是 getWidth() = getMeasureWidth()

那么,他们两个有不相等的时候吗?有的:

//自定义 View 
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    //手动改变传入的值 
    super.onLayout(changed, left, top, right+100, bottom+100);
}
复制代码

如果我们在自定义 View 中重写 onLayout 方法,并手动改变传入的值, getWidth()getMeasureWidth() 的值自然就不一样了。不过这样做好像没什么意义?

还有一种情况就是在某些情况下,View 需要多次 measure 才能确定自己的测量宽/高,在前几次测量的时候,其得出的测量宽/高 ( getMeasureWidth()/ getMeasureHeight()) 和最终宽/高 ( getWidth()/ getHeight()) 可能不一致,但是最终他们的值还是相等的。(这段话摘自刚哥的《Android 开发艺术探索》)

getHeight 和 getMeasuredHeight 过程类似,这里就不分析了。

所以我们在日常开发中,我们可以认为两者就是相等的。

另:可能还是会有疑惑,这里只分析了 LinearLayout,那么其他布局也适用这个结论么?

FrameLayout 类

//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) {
    final int count = getChildCount();

    for (int i = 0; i < count; i++) {
        final View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();
            //从这里看出 FrameLayout 也是适用这个结论的
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
复制代码

RelativeLayout 类

//RelativeLayout 类
@Override
protected void onLayout(boolean changed, int l, int t, int r, int b) {
    final int count = getChildCount();

    for (int i = 0; i < count; i++) {
        View child = getChildAt(i);
        if (child.getVisibility() != GONE) {
            RelativeLayout.LayoutParams st =
                    (RelativeLayout.LayoutParams) child.getLayoutParams();
            //Relativelayout 这里传入的是 LayoutParams 中的属性
            //st.mRight 和 st.mBottom 赋值很复杂,不过他们也是适用这个结论的,具体可以查看源码分析
            child.layout(st.mLeft, st.mTop, st.mRight, st.mBottom);
        }
    }
}
复制代码

其他我们日常开发使用的布局也是适用于这个结论的。


以上就是本文的全部内容,希望本文的内容对大家的学习或者工作能带来一定的帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

How to Think About Algorithms

How to Think About Algorithms

Jeff Edmonds / Cambridge University Press / 2008-05-19 / USD 38.99

HOW TO THINK ABOUT ALGORITHMS There are many algorithm texts that provide lots of well-polished code and proofs of correctness. Instead, this one presents insights, notations, and analogies t......一起来看看 《How to Think About Algorithms》 这本书的介绍吧!

随机密码生成器
随机密码生成器

多种字符组合密码

MD5 加密
MD5 加密

MD5 加密工具

SHA 加密
SHA 加密

SHA 加密工具