Android視圖布局原理


和視圖測量類似确丢,布局的入口也在ViewRootImpl類霎箍,在performTraversals方法,測量完成后魏保,進行視圖布局熬尺。

final boolean didLayout = layoutRequested && (!mStopped || mReportNextDraw);
...
if (didLayout) {
    performLayout(lp, desiredWindowWidth, desiredWindowHeight);
    ...
}

performLayout方法。

private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
            int desiredWindowHeight) {
    mLayoutRequested = false;
    mScrollMayChange = true;
    mInLayout = true;//設置正在布局標志

    final View host = mView;

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

        mInLayout = false;//布局完畢
        int numViewsRequestingLayout = mLayoutRequesters.size();
        ....
    } finally {
        Trace.traceEnd(Trace.TRACE_TAG_VIEW);
    }
    mInLayout = false;
}

從頂層視圖的layout方法開始谓罗,它在ViewGroup中定義粱哼,和measure方法一樣,final方法檩咱,不允許容器視圖子類重寫揭措,是一套模板。在layout方法中刻蚯,調用View的layout方法绊含。

public final void layout(int l, int t, int r, int b) {
    super.layout(l, t, r, b);//調用View的layout
    ...
}

每個類型的視圖容器布局方式不同,比如炊汹,LinearLayout是順序布局躬充,分垂直和水平,RelativeLayout是相對布局讨便。在View的layout方法中充甚,onLayout方法實現(xiàn)不同的布局類型。

public void layout(int l, int t, int r, int b) {
    ...
    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);//觸發(fā)onLayout器钟,容器類重寫此方法
        ListenerInfo li = mListenerInfo;//監(jiān)聽改變
        ...  
    }
    mPrivateFlags &= ~PFLAG_FORCE_LAYOUT;
}

ViewGroup的onLayout是抽象方法津坑,我們在重寫一種ViewGroup時,必須實現(xiàn)這個方法傲霸。葉子視圖的onLayout方法是空方法疆瑰。
View的layout方法還會通過setFrame方法設置內(nèi)部四個變量眉反,mLeft,mRight穆役,mTop寸五,mBottom,他們代表該視圖相對父視圖的位置耿币。
頂層視圖是FrameLayout布局梳杏,下面我們就看一下幀布局的onLayout方法。

protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
     layoutChildren(left, top, right, bottom, false /* no force left gravity */);
}

調用layoutChildren方法淹接,布局它的子視圖十性,將已經(jīng)確定好寬高大小的子視圖放到合適的位置。

void layoutChildren(int left, int top, int right, int bottom,
                                  boolean forceLeftGravity) {
    final int count = getChildCount();
    //childView在容器放置的位置
    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) {//非GONE
            final LayoutParams lp = (LayoutParams) child.getLayoutParams();
            //childView測量的寬高
            final int width = child.getMeasuredWidth();
            final int height = child.getMeasuredHeight();

            int childLeft; 
            int childTop;

            int gravity = lp.gravity;

            final int layoutDirection = getLayoutDirection();
            final int absoluteGravity = Gravity.getAbsoluteGravity(gravity, 
                            layoutDirection);
            final int verticalGravity = gravity & Gravity.VERTICAL_GRAVITY_MASK;
            //兩個switch目的就是計算childLeft和childTop
            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;
            }
            child.layout(childLeft, childTop, childLeft + width, childTop + height);
        }
    }
}

遍歷子視圖layout方法塑悼。若子視圖是葉子節(jié)點劲适,調用View的layout,View的onLayout是空方法厢蒜。若子視圖是容器視圖霞势,類似FrameLayout,繼續(xù)向下遍歷斑鸦,直到遇到葉子節(jié)點執(zhí)行onLayout愕贡。樹形結構布局順序與測量順序一致。

關鍵點是計算子視圖位于容器的左和上位置巷屿,即childLeft固以,childTop,然后嘱巾,再加上測量子視圖得到的寬高嘴纺,就可以得出下和右位置。
從子視圖的LayoutParams參數(shù)中獲取gravity位置浓冒,該值決定它在容器的位置,包括Gravity.LEFT 尖坤、Gravity.RIGHT稳懒、Gravity.BOTTOM、Gravity.TOP慢味,參與決策的還包括子視圖自身Margin场梆。
子視圖的layout方法將這個四個參數(shù)傳入,目的是告訴子視圖纯路,它在容器中的相對布局坐標Rect區(qū)域或油。
四個參數(shù)值是相對容器坐標系(0,0,容器寬,容器高)的Rect驰唬。layoutChildren的四個參數(shù)代表著該視圖在他的父容器中的坐標系中相對的位置顶岸,因為DecorView已是頂層視圖腔彰,該值是窗體Rect。

視圖布局時序圖.png

總結

1辖佣,容器視圖布局的入口是ViewGroup的layout方法霹抛,final類型,ViewGroup的子類無法重寫卷谈,會調用父類View的layout方法杯拐。
2,View的layout方法世蔗,如果是葉子節(jié)點視圖調用端逼,會將相對父視圖的位置存儲在四個變量中,然后執(zhí)行的onLayout方法是空方法污淋。如果是容器視圖調用顶滩,正如1所說,是ViewGroip子類調用芙沥,也會存儲相對變量诲祸,然后執(zhí)行的onLayout方法ViewGroup子類會實現(xiàn)。
3而昨,自定義一個ViewGroup視圖時救氯,必須實現(xiàn)onLayout方法,通過View類的layout方法進入onLayout方法中執(zhí)行歌憨,計算子視圖在父視圖的上下/左右區(qū)域着憨,在該方法中遍歷子視圖的layout方法,區(qū)域通知子視圖务嫡。
4甲抖,不管是View還是ViewGroup,只有調用了layout心铃,設置了相對父視圖的位置准谚,才可以獲取getWidth和getHeight。


任重而道遠

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末去扣,一起剝皮案震驚了整個濱河市柱衔,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌愉棱,老刑警劉巖唆铐,帶你破解...
    沈念sama閱讀 219,188評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異奔滑,居然都是意外死亡艾岂,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,464評論 3 395
  • 文/潘曉璐 我一進店門朋其,熙熙樓的掌柜王于貴愁眉苦臉地迎上來王浴,“玉大人脆炎,你說我怎么就攤上這事〉鸢遥” “怎么了腕窥?”我有些...
    開封第一講書人閱讀 165,562評論 0 356
  • 文/不壞的土叔 我叫張陵,是天一觀的道長筛婉。 經(jīng)常有香客問我簇爆,道長,這世上最難降的妖魔是什么爽撒? 我笑而不...
    開封第一講書人閱讀 58,893評論 1 295
  • 正文 為了忘掉前任入蛆,我火速辦了婚禮,結果婚禮上硕勿,老公的妹妹穿的比我還像新娘哨毁。我一直安慰自己,他們只是感情好源武,可當我...
    茶點故事閱讀 67,917評論 6 392
  • 文/花漫 我一把揭開白布扼褪。 她就那樣靜靜地躺著,像睡著了一般粱栖。 火紅的嫁衣襯著肌膚如雪话浇。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,708評論 1 305
  • 那天闹究,我揣著相機與錄音幔崖,去河邊找鬼。 笑死渣淤,一個胖子當著我的面吹牛赏寇,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播价认,決...
    沈念sama閱讀 40,430評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼嗅定,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了用踩?” 一聲冷哼從身側響起露戒,我...
    開封第一講書人閱讀 39,342評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎捶箱,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體动漾,經(jīng)...
    沈念sama閱讀 45,801評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡丁屎,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,976評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了旱眯。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片晨川。...
    茶點故事閱讀 40,115評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡证九,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出共虑,到底是詐尸還是另有隱情愧怜,我是刑警寧澤,帶...
    沈念sama閱讀 35,804評論 5 346
  • 正文 年R本政府宣布妈拌,位于F島的核電站拥坛,受9級特大地震影響,放射性物質發(fā)生泄漏尘分。R本人自食惡果不足惜猜惋,卻給世界環(huán)境...
    茶點故事閱讀 41,458評論 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望培愁。 院中可真熱鬧著摔,春花似錦、人聲如沸定续。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,008評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽私股。三九已至摹察,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間庇茫,已是汗流浹背港粱。 一陣腳步聲響...
    開封第一講書人閱讀 33,135評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留旦签,地道東北人查坪。 一個月前我還...
    沈念sama閱讀 48,365評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像宁炫,于是被迫代替她去往敵國和親偿曙。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,055評論 2 355

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