View的繪製-layout流程詳解

目錄

做用

根據 measure 測量出來的寬高,肯定全部 View 的位置。java

具體分析

View 自己的位置是經過它的四個點來控制的: ide

如下涉及到源碼的部分都是版本27的,爲方便理解觀看,代碼有所刪減。源碼分析

layout 的流程

先經過 measure 測量出 ViewGroup 寬高,ViewGroup 再經過 layout 方法根據自身寬高來肯定自身位置。當 ViewGroup 的位置被肯定後,就開始在 onLayout 方法中調用子元素的 layout 方法肯定子元素的位置。子元素若是是 ViewGroup 的子類,又開始執行 onLayout,如此循環往復,直到全部子元素的位置都被肯定,整個 View 樹的 layout 過程就執行完了。佈局

在上一節 《View的繪製-measure流程詳解》中說過,View 的繪製流程是從 ViewRootViewImpl 中的 performMeasure()performLayoutperformDraw 開始的。在執行完 performMeasure() 後,開始執行 performLayout 方法:(如下源碼有所刪減)post

//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 方法:ui

//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 的實現,發現只是一個抽象方法。spa

//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 ,它是沒有子元素的,不須要肯定子元素的位置,只須要肯定本身的位置就夠了。.net

在 ViewGroup 中是一個抽象方法,意思也很明顯了,在控件繼承自 ViewGroup 的時候,咱們必須重寫 onLayout 方法。由於如LinearLayoutRelativeLayout,他們的佈局特性都是不同的,須要各自根據本身的特性來進行制定肯定子元素位置的規則。code

下面以 LinearLayout 爲例,分析 onLayout 裏面的邏輯。orm

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);
        }
    }
}
複製代碼

其餘咱們平常開發使用的佈局也是適用於這個結論的。

總結

參考文獻

《Android開發藝術探索》第四章-View的工做原理

自定義View Layout過程 - 最易懂的自定義View原理系列(3)

Android開發之getMeasuredWidth和getWidth區別從源碼分析

相關文章
相關標籤/搜索