Android View的測(cè)量,布局,繪制(二)

前言

在Android View的測(cè)量,布局,繪制(一)中,描述了View測(cè)量,這篇文章,主要針對(duì)View的布局進(jìn)行講解。

View的布局?jǐn)[放,主要是在performLayout方法中進(jìn)行。

##ViewRootImpl
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;

        final View host = mView;
        if (host == null) {
            return;
        }
        ...
        try {
            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());  //1

           ...
        } finally {
            Trace.traceEnd(Trace.TRACE_TAG_VIEW);
        }
        mInLayout = false;
}

注釋1host是DectorView,這里調(diào)用了host.layout方法,把起始點(diǎn)x=0,y=0傳入,然后將測(cè)量好的寬高傳入。

##View
public void layout(int l, int t, int r, int b) {
        //如果不是第一次,跳過(guò)否則會(huì)在此進(jìn)行測(cè)量,意思是第一次進(jìn)來(lái)會(huì)進(jìn)行一次測(cè)量用于保存寬高,意義在于優(yōu)化,接著往下看
        if ((mPrivateFlags3 & PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT) != 0) {
            onMeasure(mOldWidthMeasureSpec, mOldHeightMeasureSpec);
            mPrivateFlags3 &= ~PFLAG3_MEASURE_NEEDED_BEFORE_LAYOUT;
        }
        //初次進(jìn)行上下左右點(diǎn)的初始化
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        //這里調(diào)用了setFrame進(jìn)行初始化mLeft,mRight,mTop,mBottom這四個(gè)值
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);  //1

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

注釋1 調(diào)用了setFrame方法進(jìn)行初始化mLeft,mRight,mTop,mBottom這四個(gè)值。

##View
protected boolean setFrame(int left, int top, int right, int bottom) {
        boolean changed = false;
        ...
        if (mLeft != left || mRight != right || mTop != top || mBottom != bottom) {  //1
            changed = true;

            // Remember our drawn bit
            int drawn = mPrivateFlags & PFLAG_DRAWN;

            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使舊信息無(wú)效
            invalidate(sizeChanged);
            //重新初始化定位
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            ...
        }
        return changed;
}

setFrame方法在進(jìn)行初始化的時(shí)候會(huì)對(duì)比上一次是否一致,若一致則不會(huì)進(jìn)入注釋1的if判斷, 若是一致,則會(huì)使舊的信息直接失效invalidate(sizeChanged)。接著在對(duì)View的上下左右?guī)讉€(gè)點(diǎn)賦值。

注釋2

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

onLayout方法,在View類中,只是個(gè)空的方法,具體的業(yè)務(wù)都交給子類是重寫(xiě)。從上面我們知道當(dāng)前View的子類是DecorView。

##DecorView
@Override
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
        super.onLayout(changed, left, top, right, bottom);
        ...
}

接著他調(diào)用了父類的onLayout而它的父類是FreamLayout所以,找到最終目標(biāo)。

##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 */);
}

這里請(qǐng)注意,有個(gè)核心問(wèn)題要注意的是同之前所講的測(cè)量流程, 布局也是同樣, 每一個(gè)不同布局組件她們的實(shí)現(xiàn)是不一樣的,而在這里我們以FreamLayout舉例,在這里他開(kāi)始調(diào)用了一個(gè)
layoutChildren方法。

##FrameLayout
void layoutChildren(int left, int top, int right, int bottom, boolean forceLeftGravity) {
        final int count = getChildCount();

        final int parentLeft = getPaddingLeftWithForeground();
        final int parentRight = right - left - getPaddingRightWithForeground();

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();

        for (int i = 0; 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 == -1) {
                    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:  //水平居中
                        /**
                         * parentLeft + (parentRight - parentLeft - width) / 2 中心點(diǎn)
                         * 加上左邊偏移量,減去右邊偏移量得到起點(diǎn)Left坐標(biāo)
                         */
                        childLeft = parentLeft + (parentRight - parentLeft - width) / 2 +
                        lp.leftMargin - lp.rightMargin;
                        break;
                    case Gravity.RIGHT:
                        if (!forceLeftGravity) {
                            childLeft = parentRight - width - lp.rightMargin;
                            break;
                        }
                    case Gravity.LEFT:
                    default:
                        childLeft = parentLeft + lp.leftMargin;
                }
                //childTop同理
                switch (verticalGravity) {
                    case Gravity.TOP:
                        childTop = parentTop + lp.topMargin;
                        break;
                    case Gravity.CENTER_VERTICAL:
                        childTop = parentTop + (parentBottom - parentTop - height) / 2 +
                        lp.topMargin - lp.bottomMargin;
                        break;
                    case Gravity.BOTTOM:
                        childTop = parentBottom - height - lp.bottomMargin;
                        break;
                    default:
                        childTop = parentTop + lp.topMargin;
                }

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

這個(gè)時(shí)候會(huì)發(fā)現(xiàn),當(dāng)前組件在不斷的迭代當(dāng)前的子view,然后開(kāi)始調(diào)用自己layout方法進(jìn)行定位,所以直接從此處可以看出來(lái),當(dāng)前布局?jǐn)[放流程實(shí)際上是,先得到頂層, 頂層自己先開(kāi)始layout進(jìn)行布局定位,然后調(diào)用onLayout調(diào)用子view讓子view調(diào)用自己的layout對(duì)自己進(jìn)行定位以達(dá)到定位的所有目的,

總結(jié):
那么其實(shí)要清楚了當(dāng)前的繪制流程和布局流程,需要開(kāi)發(fā)自己自定義的布局其實(shí)實(shí)際上就只需要添加我門(mén)自己的業(yè)務(wù)代碼,不管是FreamLayout,還是LinearLayout等官方提供出來(lái)的布局組件, 都是依照這套機(jī)制來(lái)玩的, 只不過(guò)是添加了不同的業(yè)務(wù),實(shí)現(xiàn)了相對(duì)應(yīng)的效果。

備注:文中Android源碼版本9.0

作者:Alan
原創(chuàng)博客,請(qǐng)注明轉(zhuǎn)載處....

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

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

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