Android View的排版

由于performLayout之前是performMeasure()操作晶疼,所以不熟悉測量的小伙伴看我上一篇博客Android View 測量原理
我想了想,如果直接從ViewGroup里面的方法談起猾普,可能和網(wǎng)上很多博客一樣了,但是如果只是向framework開發(fā)者分析哪些缔御,又分析不到應(yīng)用層抬闷,所以我覺得應(yīng)該從performLayout()這個方法開始分析測量,因為如果在向framework層深入,那就會接觸到WindowManagerService笤成,這個過程需要掌握Binder知識评架,但是Binder知識很多人一時半會掌握不了,尤其是對于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)的布局是一個FrameLayout浙芙,所以我們進入FrameLayout的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;
    }

    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進入調(diào)用到onLayout方法
    if (changed || (mPrivateFlags & PFLAG_LAYOUT_REQUIRED) == PFLAG_LAYOUT_REQUIRED) {
        onLayout(changed, l, t, r, b);
        ......
    }
    .....
}

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

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

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

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

LinearLayout實現(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時候
       case Gravity.BOTTOM:
           // 看出來是已父容器總內(nèi)容寬度為基準的最下面
           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++) {
        //得到每一個孩子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);
            //下一個view
            i += getChildrenSkipCount(child, i);
        }
    }
}

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

同時注意幾個方法

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

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

  • getMeasuredWidth與getWidth

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

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市攘宙,隨后出現(xiàn)的幾起案子屯耸,更是在濱河造成了極大的恐慌,老刑警劉巖蹭劈,帶你破解...
    沈念sama閱讀 206,839評論 6 482
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件疗绣,死亡現(xiàn)場離奇詭異,居然都是意外死亡铺韧,警方通過查閱死者的電腦和手機多矮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,543評論 2 382
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人塔逃,你說我怎么就攤上這事讯壶。” “怎么了湾盗?”我有些...
    開封第一講書人閱讀 153,116評論 0 344
  • 文/不壞的土叔 我叫張陵伏蚊,是天一觀的道長。 經(jīng)常有香客問我格粪,道長躏吊,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,371評論 1 279
  • 正文 為了忘掉前任帐萎,我火速辦了婚禮比伏,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘疆导。我一直安慰自己凳怨,他們只是感情好,可當(dāng)我...
    茶點故事閱讀 64,384評論 5 374
  • 文/花漫 我一把揭開白布是鬼。 她就那樣靜靜地躺著肤舞,像睡著了一般。 火紅的嫁衣襯著肌膚如雪均蜜。 梳的紋絲不亂的頭發(fā)上李剖,一...
    開封第一講書人閱讀 49,111評論 1 285
  • 那天,我揣著相機與錄音囤耳,去河邊找鬼篙顺。 笑死,一個胖子當(dāng)著我的面吹牛充择,可吹牛的內(nèi)容都是我干的德玫。 我是一名探鬼主播,決...
    沈念sama閱讀 38,416評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼椎麦,長吁一口氣:“原來是場噩夢啊……” “哼宰僧!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起观挎,我...
    開封第一講書人閱讀 37,053評論 0 259
  • 序言:老撾萬榮一對情侶失蹤琴儿,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后嘁捷,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體造成,經(jīng)...
    沈念sama閱讀 43,558評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,007評論 2 325
  • 正文 我和宋清朗相戀三年雄嚣,在試婚紗的時候發(fā)現(xiàn)自己被綠了晒屎。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,117評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖鼓鲁,靈堂內(nèi)的尸體忽然破棺而出履肃,到底是詐尸還是另有隱情,我是刑警寧澤坐桩,帶...
    沈念sama閱讀 33,756評論 4 324
  • 正文 年R本政府宣布尺棋,位于F島的核電站,受9級特大地震影響绵跷,放射性物質(zhì)發(fā)生泄漏膘螟。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,324評論 3 307
  • 文/蒙蒙 一碾局、第九天 我趴在偏房一處隱蔽的房頂上張望荆残。 院中可真熱鬧,春花似錦净当、人聲如沸内斯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,315評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽俘闯。三九已至,卻和暖如春忽冻,著一層夾襖步出監(jiān)牢的瞬間真朗,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,539評論 1 262
  • 我被黑心中介騙來泰國打工僧诚, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留遮婶,地道東北人。 一個月前我還...
    沈念sama閱讀 45,578評論 2 355
  • 正文 我出身青樓湖笨,卻偏偏與公主長得像旗扑,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子慈省,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 42,877評論 2 345

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