深入理解 Android 之 View 的繪製流程(三)_Layout

上篇介紹了ViewRootImpl調用View的測量操作,下面就開始介紹ViewRootImpl中的佈局操作了。我們還是從ViewRootImpl中的performLayout開始。

ViewRootImpl#performLayout

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
        mLayoutRequested = false;
        mScrollMayChange = true;
        mInLayout = true;  //標記開始佈局

        final View host = mView;
        . . . . . . . . .

            host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
        . . . . . . . . .

        mInLayout = false;  //標記佈局結束
}

該方法中的核心方法是調用View中的layout。

View#layout

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;
        }
        // 保存上次View的四個位置
        int oldL = mLeft;
        int oldT = mTop;
        int oldB = mBottom;
        int oldR = mRight;
        // 設置當前視圖View的左,頂,右,底的位置,並且判斷佈局是否有改變
        boolean changed = isLayoutModeOptical(mParent) ?
                setOpticalFrame(l, t, r, b) : setFrame(l, t, r, b);
        //如果佈局有改變,則視圖View重新佈局
        if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
            onLayout(changed, l, t, r, b);
            mPrivateFlags &= ~PFLAG_LAYOUT_REQUIRED;

            ListenerInfo li = mListenerInfo;
            if (li != null && li.mOnLayoutChangeListeners != null) {
                ArrayList<OnLayoutChangeListener> listenersCopy =
                        (ArrayList<OnLayoutChangeListener>)li.mOnLayoutChangeListeners.clone();
                int numListeners = listenersCopy.size();
                for (int i = 0; i < numListeners; ++i) {
                    listenersCopy.get(i).onLayoutChange(this, l, t, r, b, oldL, oldT, oldR, oldB);
                }
            }
        }

        mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
        mPrivateFlags3 |= PFLAG3_IS_LAID_OUT;
}

方法中,首先判斷是否需要重新進行測量,然後保存佈局的四個位置。調用setFrame方法來設置View的佈局位置。

View#setFrame

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;

            // 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
       // 清除上次佈局的位置
            invalidate(sizeChanged);
     //保存當前View的最新位置
            mLeft = left;
            mTop = top;
            mRight = right;
            mBottom = bottom;
            mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);

            mPrivateFlags |= PFLAG_HAS_BOUNDS;

            //如果當前View的尺寸有所變化
            if (sizeChanged) {
                sizeChange(newWidth, newHeight, oldWidth, oldHeight);
            }
            . . . . . . . . .
        }
        return changed;
}

方法中判斷當前View的視圖的位置與上次不一致時,則View會重新佈局。調用方法invalidate
清除上次的位置,然後保存最新的View的位置。
完成setFrame來設置view的位置之後,就會繼續調用onLayout(changed, l, t, r, b)方法。

View#onLayout

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

可以看到該方法是個空方法,具體的實現是在子類中。我們知道DecorView是繼承自FrameLayout類,我們從該類中查看:

FrameLayout#onLayout

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

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

        final int parentTop = getPaddingTopWithForeground();
        final int parentBottom = bottom - top - getPaddingBottomWithForeground();
        //遍歷子view
        for (int i = 0; i < count; i++) {
            final View child = getChildAt(i);
            //過濾掉Visibility爲Gone的情況
            if (child.getVisibility() != GONE) {
                final LayoutParams lp = (LayoutParams) child.getLayoutParams();
                //獲取子View的寬高
                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:
                        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;
                }

                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;
                }
                //調用子View的佈局
                child.layout(childLeft, childTop, childLeft + width, childTop + height);
            }
        }
}

從上面的代碼中分析:佈局中的具體邏輯實現是有ViewGroup父View來實現的。遍歷獲得子View的寬高和位置,然後調用child.layout對子視圖View進行佈局操作。
總結:
1.視圖View的佈局邏輯(onLayout)是由父View,也就是ViewGroup的具體子類來實現的。因此,我們如果自定義View一般都無需重寫onMeasure方法,但是如果自定義一個ViewGroup的話,就必須實現onLayout方法,因爲該方法在ViewGroup是抽象的,所有ViewGroup的所有子類必須實現onLayout方法。
2.當我們的視圖View在佈局中使用 android:visibility=”gone” 屬性時,是不佔據屏幕空間的,因爲在佈局時ViewGroup會遍歷每個子視圖View,判斷當前子視圖View是否設置了 Visibility==GONE,如果設置了,當前子視圖View就會添加到父容器上,因此也就不佔據屏幕空間。
3.必須在View調用layout()之後調用getHeight()和getWidth()方法獲取到的View的寬高才大於0。因爲只有在View調用setFrame時,纔會給mLeft,mRight,mTop,mBottom賦值。

這裏寫圖片描述

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章