天天看點

【Android系列】View的繪制之layout過程

layout作為View繪制的三個過程中的第二個過程,負責在measure過程完成之後确定每個View的位置,也是在這個階段,View的最終寬高才能真正确定(measure過程計算的是測量寬高)。如果你還不清楚measure過程是如何進行的,可以浏覽【Android系列】View的繪制之measure過程。

layout過程和measure過程類似,也是從DecorView開始,并經由父View傳遞到子View,最終在View樹的葉子節點處結束。在layout過程中,通過

layout

方法可以确定View自身的位置,通過

onLayout

方法可以确定子View在父View中的位置。

layout第一步:從DecorView開始

layout過程是由

performLayout

方法執行的。在

performLayout

方法中有這樣一段代碼:

/* ViewRootImpl.performLayout */
final View host = mView;
host.layout(, , host.getMeasuredWidth(), host.getMeasuredHeight());
           

從這裡可以看出,layout過程同樣是從DecorView開始的。

layout第二步:從父View向子View傳遞

在文章一開頭就提到過:“在layout過程中,通過

layout

方法可以确定View自身的位置,通過

onLayout

方法可以确定子View在父View中的位置”。也就是說,layout過程是在父View确定子View位置的時候傳遞到子View的。

父View确定自身的位置

layout

方法負責确定View自身的位置,和

measure

方法類似,在

layout

方法的注釋中有這樣一段話:

Derived classes should not override this method. Derived classes with children should override onLayout. In that method, they should call layout on each of their children.

是以,在自定義ViewGroup時不需要重寫

layout

方法,而隻要也必須重寫

onLayout

方法。下面我們還是來看看

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

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

        mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;
    }
}
           

首先,

layout

方法的四個參數分别代表目前View的四個頂點的位置;接着,通過

setFrame

方法設定自身的位置(

setOpticalFrame

方法内部調用了

setFrame

方法);最後,又調用了

onLayout

方法設定子View的位置。

setFrame

方法中:

/* View.setFrame */
if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
    changed = true;

    mLeft = left;
    mTop = top;
    mRight = right;
    mBottom = bottom;
}
           

可以看到,這裡設定了

mLeft

mTop

mRight

mBottom

四個成員變量的值,而它們正好表示View的邊界,這樣就設定了View的位置。

父View确定子View的位置

就像我們不能在ViewGroup中找到

onMeasure

方法一樣,在ViewGroup中同樣“沒有”

onLayout

方法。實際上,ViewGroup的

onLayout

方法是一個抽象方法(不同View容器的布局規則不同),View的

onLayout

方法是一個空方法(View沒有子View)。通過

layout

方法的注釋我們知道,每個ViewGroup都要重寫

onLayout

方法。這裡我們仍以FrameLayout為例,看看它的

onLayout

方法做了些什麼。由于FrameLayout的

onLayout

方法直接調用了

layoutChildren

方法,是以我們直接看它的源碼:

/* FrameLayout.layoutChildren */
void layoutChildren(int left, int top, int right, int bottom, 
        boolean forceLeftGravity) {
    final int count = getChildCount();
    for (int i = ; 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 == -) {
                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) /  +
                        lp.leftMargin - lp.rightMargin;
                break;
            case Gravity.RIGHT:
                if (!forceLeftGravity) {
                    childLeft = parentRight - width - lp.rightMargin;
                    break;
                }
            case Gravity.LEFT:
            default:
                childLeft = parentLeft + lp.leftMargin;
            }

            // vertical cases

            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}
           

在FrameLayout的

onLayout

方法中,是通過子View的gravity來确定其位置的,換句話說,隻要确定了子View的gravity,就确定了子View在FrameLayout中的位置。那麼子View的gravity是如何确定的呢?

首先,通過

getLayoutDirection

方法确定目前布局的水準方向是從左到右還是從右到左;接着,通過

getAbsoluteGravity

方法把相對gravity轉換成絕對gravity,即把start和end轉換成left和right;最後,通過掩碼得到水準方向的gravity和垂直方向的gravity。

在得到子View的gravity之後,會根據其計算子View的左邊界和上邊界位置,即左上方頂點的位置。在得到子View的左上方頂點的位置後,由于子View的測量寬高已經确定了,是以又調用了子View的

layout

方法确定子View的位置,layout過程也在此進入到了子View中。

layout第三步:在View處終止

就像上面提到的,View的

onLayout

方法是一個空方法,是以,layout過程到這裡也結束了。

和measure過程比起來,layout過程簡單了很多,如果你已經清楚了measure過程,相信layout過程也難不倒你,最後,同樣給出一個layout過程的流程圖:

【Android系列】View的繪制之layout過程
【Android系列】View的繪制之layout過程

掃一掃,關注我٩(๑>◡<๑)۶