天天看點

android-View工作原理(四)view的layout過程

本文轉載自:http://blog.csdn.net/ff20081528/article/details/17784911

一、android中view的layout過程總概

Layout過程其實就是父視圖按照子視圖的大小及布局參數将子視圖放在視窗的合适的位置上。

視圖的布局過程是從ViewRoot對象調調用根視圖的layout()方法開始,接着layout()方法調用根視圖的onLayout()方法,onLayout()方法會對所包含的子視圖逐一執行layout操作,如果子視圖是ViewGroup子類對象,則繼續調用子視圖的layout(),重複這一過程。如果子視圖是View子類對象,則在子視圖重載的onLayout()内部隻需要将自己布局到視圖中,不需要對子視圖進行layout操作,這樣一次layout過程結束。過程如下圖:
           
android-View工作原理(四)view的layout過程

二、layout詳細過程

View中的layout()方法源碼(ViewGroup類繼承了View類,layout過程先從ViewGroup子類開始):

/**
     * Assign a size and position to a view and all of its
     * descendants
     *
     * <p>This is the second phase of the layout mechanism.
     * (The first is measuring). In this phase, each parent calls
     * layout on all of its children to position them.
     * This is typically done using the child measurements
     * that were stored in the measure pass().
     *
     * Derived classes with children should override
     * onLayout. In that method, they should
     * call layout on each of their their children.
     *
     * @param l Left position, relative to parent
     * @param t Top position, relative to parent
     * @param r Right position, relative to parent
     * @param b Bottom position, relative to parent
     */
    public final void layout(int l, int t, int r, int b) {
        boolean changed = setFrame(l, t, r, b);
        if (changed || (mPrivateFlags & LAYOUT_REQUIRED) == LAYOUT_REQUIRED) {
            if (ViewDebug.TRACE_HIERARCHY) {
                ViewDebug.trace(this, ViewDebug.HierarchyTraceType.ON_LAYOUT);
            }

            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~LAYOUT_REQUIRED;
        }
        mPrivateFlags &= ~FORCE_LAYOUT;
    }
           

a) 首先我們看這個方法的定義,用了關鍵字final,說明此方法是不可被重寫的,這樣也就保證了View的layout過程是不變的。四個參數看注釋,左、上、右、下分别相距父視圖的距離。

b) 調用setFrame(l,t,r,b)将位置儲存起來,這些參數将儲存到View内部變量 (mLeft、mTop、mRight、mBottom)中。儲存完變量前,會先對比這些參數是否和原來的相同,如果相同,則什麼都不做,如果不同則進行重新指派,并在指派前給mPrivateFlags中添加DRAWN辨別,同時調用invalidate()通知View系統原來占用的位置需要重繪。

c) 調用onLayout(),View中定義的onLayout()方法預設什麼都不做,View系統提供onLayout()方法的目的是為了使系統包含的子視圖的父視圖能夠在onLayout()方法對子視圖進行位置配置設定,正因為如此,如果是父視圖,則必須重寫onLayout(),也正因為如此ViewGroup類才會把onLayout重載改成了abstract類型。

d)清除mPrivateFlags中的LAYOUT_REQUIRED辨別,因為layout操作已經完成。
           

上面提到的setFrame方法源碼如下:

protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        if (DBG) {
            Log.d("View", this + " View.setFrame(" + left + "," + top + ","
                    + right + "," + bottom + ")");
        }
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {
            changed = true;
            // Remember our drawn bit
            int drawn = mPrivateFlags & DRAWN;
            // Invalidate our old position
            invalidate();
            int oldWidth = mRight - mLeft;
            int oldHeight = mBottom - mTop;
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mPrivateFlags |= HAS_BOUNDS;
            int newWidth = right - left;
            int newHeight = bottom - top;
            if (newWidth != oldWidth || newHeight != oldHeight) {
                onSizeChanged(newWidth, newHeight, oldWidth, oldHeight);
            }
            if ((mViewFlags & VISIBILITY_MASK) == VISIBLE) {
                // If we are visible, force the DRAWN bit to on so that
                // this invalidate will go through (at least to our parent).
                // This is because someone may have invalidated this view
                // before this call to setFrame came in, therby clearing
                // the DRAWN bit.
                mPrivateFlags |= DRAWN;
                invalidate();
            }
            // Reset drawn bit to original value (invalidate turns it off)
            mPrivateFlags |= drawn;
            mBackgroundSizeChanged = true;
        }
        return changed;
    }
           

View中的onLayout()方法如下:

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
    }
           

而ViewGroup中的onLayout()方法如下:

@Override
    protected abstract void onLayout(boolean changed,
            int l, int t, int r, int b);
           

因為ViewGroup中的onLayout()方法是一個抽象方法,是以下面我們用他的子類LinearLayout中的onLayout()方法來分析。源碼如下:

onlayout()方法:

@Override
    protected void onLayout(boolean changed, int l, int t, int r, int b) {
        if (mOrientation == VERTICAL) {
            layoutVertical();
        } else {
            layoutHorizontal();
        }
    }
           

layoutVertical()方法源碼:

void layoutVertical() {
        final int paddingLeft = mPaddingLeft;
        int childTop = mPaddingTop;
        int childLeft;
        // Where right end of child should go
        final int width = mRight - mLeft;
        int childRight = width - mPaddingRight;
        // Space available for child
        int childSpace = width - paddingLeft - mPaddingRight;
        final int count = getVirtualChildCount();
        final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
        final int minorGravity = mGravity & Gravity.HORIZONTAL_GRAVITY_MASK;
        if (majorGravity != Gravity.TOP) {
           switch (majorGravity) {
               case Gravity.BOTTOM:
                   // mTotalLength contains the padding already, we add the top
                   // padding to compensate
                   childTop = mBottom - mTop + mPaddingTop - mTotalLength;
                   break;
               case Gravity.CENTER_VERTICAL:
                   childTop += ((mBottom - mTop)  - mTotalLength) / ;
                   break;
           }
        }
        for (int i = ; i < count; i++) {
            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();
                int gravity = lp.gravity;
                if (gravity < ) {
                    gravity = minorGravity;
                }
                switch (gravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                    case Gravity.LEFT:
                        childLeft = paddingLeft + lp.leftMargin;
                        break;
                    case Gravity.CENTER_HORIZONTAL:
                        childLeft = paddingLeft + ((childSpace - childWidth) / )
                                + lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        childLeft = childRight - childWidth - lp.rightMargin;
                        break;
                    default:
                        childLeft = paddingLeft;
                        break;
                }
                childTop += lp.topMargin;
                setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                        childWidth, childHeight);
                childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
                i += getChildrenSkipCount(child, i);
            }
        }
    }
           
a) LinearLayout中的子視圖有兩種布局方式,一個是縱向的,一個是橫向的,這裡我們以縱向的分析。
b) 獲得子視圖的寬度。
c) 根據父視圖中的grarity屬性,來判斷子視圖的起始位置。
d) 開始for()循環,為每個子視圖配置設定位置。對于每個子視圖首先取出子視圖的LayoutParams屬性,并且獲得gravity的值。根據gravity的值确定水準方向的起始位置,三種值分别為:LEFT,CENTER_HORIZONTAL和RIGHT.接着調用setChildFrame(),該方法内部實際上就是調用child.layout()為子視圖設定布局位置。
           

繼續閱讀