View的繪制流程五、layout

之前文章了解到ViewRootImpl的performTraversals()引導(dǎo)了View的測量苏章、布局寂嘉、繪制的流程今天我們就從performLayout()方法來分析View的布局流程

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
        int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;
    final View host = mView;
    Trace.traceBegin(Trace.TRACE_TAG_VIEW, "layout");
    try {
        host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
   ...省略部分代碼
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

前文中我們已經(jīng)知道m(xù)View就是DecorView奏瞬,這里調(diào)用 host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());就是調(diào)用decorView的layout方法

@Override
public final void layout(int l, int t, int r, int b) {
    if (!mSuppressLayout && (mTransition == null || !mTransition.isChangingLayout())) {
        if (mTransition != null) {
            mTransition.layoutChange(this);
        }
        super.layout(l, t, r, b);
    } else {
        // record the fact that we noop'd it; request layout when transition finishes
        mLayoutCalledWhileSuppressed = true;
    }
}

再看看super.layout的代碼

@SuppressWarnings({"unchecked"})
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;
    //setFrame 記錄了View的位置以及是否變動如果變動則保存最新的位置信息如果沒有變動則跳過
    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);
        if (shouldDrawRoundScrollbar()) {
            if(mRoundScrollbarRenderer == null) {
                mRoundScrollbarRenderer = new RoundScrollbarRenderer(this);
            }
        } else {
            mRoundScrollbarRenderer = null;
        }
        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的布局已經(jīng)結(jié)束了,而onLayout則是父容器對子View的一個遍歷布局作用該方法View是沒有實現(xiàn)的主要由ViewGroup實現(xiàn)

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 + ")");
    }
    //判斷坐標(biāo)位置是否變動如果變動重新保存坐標(biāo)并更新
    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);
        mLeft = left;
        mTop = top;
        mRight = right;
        mBottom = bottom;
        mRenderNode.setLeftTopRightBottom(mLeft, mTop, mRight, mBottom);
        mPrivateFlags |= PFLAG_HAS_BOUNDS;
        if (sizeChanged) {
            sizeChange(newWidth, newHeight, oldWidth, oldHeight);
        }
        if ((mViewFlags & VISIBILITY_MASK) == VISIBLE || mGhostView != null) {
            // 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, thereby clearing
            // the DRAWN bit.
            mPrivateFlags |= PFLAG_DRAWN;
            invalidate(sizeChanged);
            // parent display list may need to be recreated based on a change in the bounds
            // of any child
            invalidateParentCaches();
        }
        // Reset drawn bit to original value (invalidate turns it off)
        mPrivateFlags |= drawn;
        mBackgroundSizeChanged = true;
        if (mForegroundInfo != null) {
            mForegroundInfo.mBoundsChanged = true;
        }
        notifySubtreeAccessibilityStateChangedIfNeeded();
    }
    return changed;
}

這步保存了View的頂點位置泉孩,也就是從這步開始我們getHeight硼端,getWidth就可以獲取到具體的值了。onLayout方法View里面并沒有實現(xiàn)所以我們直接看看onLayout中是怎么實現(xiàn)的

@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();
    //獲取padding值
    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;
            //根據(jù)布局中設(shè)置的Gravity屬性來設(shè)置布局
            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;
            }
            //最后確定坐標(biāo)點對子元素進行布局
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

由源碼看出寓搬,onLayout方法內(nèi)部直接調(diào)用了layoutChildren方法珍昨,而layoutChildren則是具體的實現(xiàn)。 FrameLayout會遍歷子元素并調(diào)用子元素的layout方法來對子View進行遍歷布局句喷。到目前為止镣典,View的布局流程就已經(jīng)全部分析完了⊥偾恚可以看出兄春,布局流程的邏輯相比測量流程來說,簡單許多锡溯,獲取一個View的測量寬高是比較復(fù)雜的赶舆,而布局流程則是根據(jù)已經(jīng)獲得的測量寬高進而確定一個View的四個位置參數(shù)。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末祭饭,一起剝皮案震驚了整個濱河市芜茵,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌倡蝙,老刑警劉巖夕晓,帶你破解...
    沈念sama閱讀 218,640評論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異悠咱,居然都是意外死亡蒸辆,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評論 3 395
  • 文/潘曉璐 我一進店門析既,熙熙樓的掌柜王于貴愁眉苦臉地迎上來躬贡,“玉大人,你說我怎么就攤上這事眼坏》鞑#” “怎么了?”我有些...
    開封第一講書人閱讀 165,011評論 0 355
  • 文/不壞的土叔 我叫張陵宰译,是天一觀的道長檐蚜。 經(jīng)常有香客問我,道長沿侈,這世上最難降的妖魔是什么闯第? 我笑而不...
    開封第一講書人閱讀 58,755評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮缀拭,結(jié)果婚禮上咳短,老公的妹妹穿的比我還像新娘填帽。我一直安慰自己,他們只是感情好咙好,可當(dāng)我...
    茶點故事閱讀 67,774評論 6 392
  • 文/花漫 我一把揭開白布篡腌。 她就那樣靜靜地躺著,像睡著了一般勾效。 火紅的嫁衣襯著肌膚如雪嘹悼。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,610評論 1 305
  • 那天层宫,我揣著相機與錄音杨伙,去河邊找鬼。 笑死卒密,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的棠赛。 我是一名探鬼主播哮奇,決...
    沈念sama閱讀 40,352評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼睛约!你這毒婦竟也來了鼎俘?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,257評論 0 276
  • 序言:老撾萬榮一對情侶失蹤辩涝,失蹤者是張志新(化名)和其女友劉穎贸伐,沒想到半個月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體怔揩,經(jīng)...
    沈念sama閱讀 45,717評論 1 315
  • 正文 獨居荒郊野嶺守林人離奇死亡捉邢,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,894評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了商膊。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伏伐。...
    茶點故事閱讀 40,021評論 1 350
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖晕拆,靈堂內(nèi)的尸體忽然破棺而出藐翎,到底是詐尸還是另有隱情,我是刑警寧澤实幕,帶...
    沈念sama閱讀 35,735評論 5 346
  • 正文 年R本政府宣布吝镣,位于F島的核電站,受9級特大地震影響昆庇,放射性物質(zhì)發(fā)生泄漏末贾。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,354評論 3 330
  • 文/蒙蒙 一整吆、第九天 我趴在偏房一處隱蔽的房頂上張望未舟。 院中可真熱鬧圈暗,春花似錦、人聲如沸裕膀。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽昼扛。三九已至寸齐,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間抄谐,已是汗流浹背渺鹦。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留蛹含,地道東北人毅厚。 一個月前我還...
    沈念sama閱讀 48,224評論 3 371
  • 正文 我出身青樓,卻偏偏與公主長得像浦箱,于是被迫代替她去往敵國和親吸耿。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 44,974評論 2 355

推薦閱讀更多精彩內(nèi)容