Android View的排版

由于performLayout之前是performMeasure()操作,所以不熟悉測量的小伙伴看我上一篇博客Android View 測量原理
我想了想,如果直接從ViewGroup里面的方法談起,可能和網(wǎng)上很多博客一樣了,但是如果只是向framework開發(fā)者分析哪些,又分析不到應(yīng)用層,所以我覺得應(yīng)該從performLayout()這個(gè)方法開始分析測量,因?yàn)槿绻谙騠ramework層深入,那就會(huì)接觸到WindowManagerService,這個(gè)過程需要掌握Binder知識,但是Binder知識很多人一時(shí)半會(huì)掌握不了,尤其是對于application開發(fā)者,不關(guān)注這些,所以從performLayout()說起。

    private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        ...
        Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
            ...
        }finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
}

這里的host是decorView,decorView對應(yīng)的布局是一個(gè)FrameLayout,所以我們進(jìn)入FrameLayout的layout方法

//傳遞進(jìn)來的是左上右下的值
public void layout(int l, int t, int r, int b) {
    if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
        onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
        mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
    }

    int oldL = mLeft;
    int oldT = mTop;
    int oldB = mBottom;
    int oldR = mRight;
    //判斷是不是左上右下這些值有所改變,如果改變的話為true,并且在setFrame中給mLeft...mRight賦值
    boolean changed = isLayoutModeOptical(mParent) ?
            setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
    //true進(jìn)入調(diào)用到onLayout方法
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        ......
    }
    .....
}

繼續(xù)進(jìn)入onLyout方法中,我們會(huì)發(fā)現(xiàn)是空方法,所以我們此時(shí)想到了ViewGroup

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

繼續(xù)看ViewGroup的onLayout方法,可想而知每個(gè)子類都有自己的實(shí)現(xiàn),我們用LinearLayout舉例

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

LinearLayout實(shí)現(xiàn)方法是:

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

針對于垂直方向和水平方向不同

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;

    // 水平可用寬度
    int childSpace = width - paddingLeft - mPaddingRight;

    final int count = getVirtualChildCount();//調(diào)用getChildCount()

    final int majorGravity = mGravity & Gravity.VERTICAL_GRAVITY_MASK;
    final int minorGravity = mGravity & Gravity.RELATIVE_HORIZONTAL_GRAVITY_MASK;
    //gravity屬性配置的值
    switch (majorGravity) {
        //當(dāng)配置bottom時(shí)候
       case Gravity.BOTTOM:
           // 看出來是已父容器總內(nèi)容寬度為基準(zhǔn)的最下面
           childTop = mPaddingTop + bottom - top - mTotalLength;
           break;

           // 同理配置的center
       case Gravity.CENTER_VERTICAL:
           childTop = mPaddingTop + (bottom - top - mTotalLength) / 2;
           break;
            // 同理配置的top
       case Gravity.TOP:
       default:
           childTop = mPaddingTop;
           break;
    }

    for (int i = 0; i < count; i++) {
        //得到每一個(gè)孩子View
        final View child = getVirtualChildAt(i);
        if (child == null) {
            childTop += measureNullChild(i);
        } else if (child.getVisibility() != GONE) {
            final int childWidth = child.getMeasuredWidth();//view的寬
            final int childHeight = child.getMeasuredHeight();//view的高
            final LinearLayout.LayoutParams lp =
                    (LinearLayout.LayoutParams) child.getLayoutParams();

            int gravity = lp.gravity;
            if (gravity < 0) {
                gravity = minorGravity;
            }
            final int layoutDirection = getLayoutDirection();//得到方向
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, layoutDirection);
            switch (absoluteGravity & Gravity.HORIZONTAL_GRAVITY_MASK) {
                case Gravity.CENTER_HORIZONTAL:
                    //水平方向的話view左側(cè)的距離
                    childLeft = paddingLeft + ((childSpace - childWidth) / 2)
                            + lp.leftMargin - lp.rightMargin;
                    break;

                case Gravity.RIGHT:
                    childLeft = childRight - childWidth - lp.rightMargin;
                    break;

                case Gravity.LEFT:
                default:
                    childLeft = paddingLeft + lp.leftMargin;
                    break;
            }
            //有沒有分割線
            if (hasDividerBeforeChildAt(i)) {
                childTop += mDividerHeight;
            }

            childTop += lp.topMargin;
            //設(shè)置子view的坐標(biāo)
            setChildFrame(child, childLeft, childTop + getLocationOffset(child),
                    childWidth, childHeight);
            //加上子view的坐標(biāo)繼續(xù)向下排列
            childTop += childHeight + lp.bottomMargin + getNextLocationOffset(child);
            //下一個(gè)view
            i += getChildrenSkipCount(child, i);
        }
    }
}

通過上面標(biāo)記的注釋我們知道了對每一個(gè)子view進(jìn)行排列。

同時(shí)注意幾個(gè)方法

  • setFrame
      當(dāng)size和position變化時(shí),返回true。如果發(fā)生了變化,會(huì)在setFrame方法內(nèi)部調(diào)用invalidate。

  • onLayout
      View中onLayout什么都沒有做,在ViewGroup中,根據(jù)各自實(shí)際規(guī)則(Linear、Relative 等)對內(nèi)部Views進(jìn)行布局安排。

  • getMeasuredWidth與getWidth

可以調(diào)用的時(shí)機(jī)不同:getMeasuredWidth在measure后即可調(diào)用,getWidth要在layout后才可以調(diào)用。(在發(fā)生時(shí)機(jī)之前調(diào)用的話均返回0)
含義不同:getMeasuredWidth是View計(jì)算出自己的實(shí)際大小,getWidth是在布局后的大小。最簡單的,在ScrollLayout中,getHeight返回屏幕內(nèi)的高度,getMeasuredHeight返回屏幕內(nèi)+屏幕外的總高度。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容