Android 代碼動態(tài)布局 LayoutParams 使用

先來看一個簡單的布局摊阀,先用xml寫

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:background="@android:color/holo_blue_bright"
    android:gravity="center"
    tools:context=".MainActivity">

    <TextView
        android:layout_width="160px"
        android:layout_height="160px"
        android:background="@android:color/holo_red_dark"
        android:text="@string/tv" />

</LinearLayout>

效果也很簡單


xml 設置效果

如果想要代碼動態(tài)寫出上面的布局栗竖,就需要使用 LayoutParams 這個關鍵類了,LayoutParams 是 ViewGroup 的一個內部類,這是一個基類粟矿,例如 FrameLayout污尉、LinearLayout 等等膀哲,內部都有自己的 LayoutParams。

一被碗、使用 LayoutParams 設置寬高

LayoutParams 的作用是:子控件告訴父控件某宪,自己要如何布局

代碼實現(xiàn):

public class LayoutParamsFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        LinearLayout ll = new LinearLayout(getContext());
        // ll的父容器是MainActivity中的FrameLayout
        ll.setLayoutParams(new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        ll.setGravity(Gravity.CENTER);
        ll.setBackgroundResource(android.R.color.holo_blue_bright);

        TextView tv = new TextView(getContext());
        // tv的父容器是LinearLayout
        LinearLayout.LayoutParams layoutParams = new LinearLayout.LayoutParams(160, 160);
        tv.setLayoutParams(layoutParams);// ①
        tv.setBackgroundResource(android.R.color.holo_red_dark);
        tv.setText(getText(R.string.tv));

        ll.addView(tv);// ②
        return ll;
    }
}

我們對 LinearLayout 和 TextView 的 LayoutParams 都進行了設置锐朴,效果圖和上面 xml 的是一模一樣的兴喂。

ll.setLayoutParams 設置的是其父布局 FrameLayout 的 LayoutParams,并且告訴父布局焚志,寬高設置為 MATCH_PARENT衣迷。
vt.setLayoutParams 設置的也是其父布局 LinearLayout 的 LayoutParams,并且告訴父布局娩嚼,寬高設置為 160px蘑险。

上面 ①、② 兩行代碼可以簡化為一行岳悟,替換為 addView(View child, LayoutParams params) 這個重載方法佃迄,在添加到父布局時泼差,設置 LayoutParams,通知父布局如何擺放自己呵俏。

ll.addView(vt, layoutParams);// 在添加到父布局的時候

二堆缘、不設置 LayoutParams

public class LayoutParamsFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        LinearLayout ll = new LinearLayout(getContext());
        ll.setGravity(Gravity.CENTER);
        ll.setBackgroundResource(android.R.color.holo_blue_bright);

        TextView tv = new TextView(getContext());
        tv.setBackgroundResource(android.R.color.holo_red_dark);
        tv.setText(getText(R.string.tv));

        ll.addView(tv);
        return ll;
    }
}

效果如下:


不設置 LayoutParams 寬高

我們發(fā)現(xiàn),在對 LinearLayout 和 TextView 的 都不設置 LayoutParams 的情況下普碎,LinearLayout 使用 MATCH_PARENT吼肥,而 TextView 使用 WRAP_CONTENT,至于為什么麻车,我們要分析一下源碼缀皱。

1. TextView 的 LayoutParams

進入 addView 看一下,不存在 LayoutParams 時动猬,會調用 generateDefaultLayoutParams() 進行創(chuàng)建啤斗。

public void addView(View child, int index) {
    if (child == null) {
        throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup");
    }
    LayoutParams params = child.getLayoutParams();
    if (params == null) {
        params = generateDefaultLayoutParams();
        if (params == null) {
            throw new IllegalArgumentException("generateDefaultLayoutParams() cannot return null");
        }
    }
    addView(child, index, params);
}

找到 LinearLayout 中 generateDefaultLayoutParams(),注意不是 ViewGroup 中的赁咙。

@Override
protected LayoutParams generateDefaultLayoutParams() {
    if (mOrientation == HORIZONTAL) {
        return new LayoutParams(LayoutParams.WRAP_CONTENT, LayoutParams.WRAP_CONTENT);
    } else if (mOrientation == VERTICAL) {
        return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT);
    }
    return null;
}

顯而易見钮莲,由于我們沒有指定方向,mOrientation 默認為 0彼水,也就是 HORIZONTAL崔拥,所以 TextView 設置為 WRAP_CONTENT,為了證實猜想凤覆,我們設置 LinearLayout 的方向為 VERTICAL链瓦。

ll.setOrientation(LinearLayout.VERTICAL);

效果跟代碼看到的一樣,寬度為 MATCH_PARENT叛赚,高度為 WRAP_CONTENT:


LinearLayout 方向為 VERTICAL

2. LinearLayout 的 LayoutParams

和上面 TextView 一樣澡绩,這個要進入 FrameLayout 中查看 generateDefaultLayoutParams()

@Override
protected LayoutParams generateDefaultLayoutParams() {
    return new LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT);
}

所以俺附,在 FrameLayout 中的 LinearLayout 的寬高就是 MATCH_PARENT

三溪掀、getLayoutParams 的使用

在不使用代碼動態(tài)布局的情況下事镣,大都是先通過 getLayoutParams() 獲取 LayoutParams ,然后進行賦值揪胃,最后通過 setLayoutParams()設回控件璃哟,值得注意的是,獲取 LayoutParams 務必要強轉為父控件的類型喊递,才會有該父控件特有的方法随闪。

public class LayoutParamsFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        LinearLayout ll = new LinearLayout(getContext());
        // ll的父容器是MainActivity中的FrameLayout
        FrameLayout.LayoutParams fl_params = (FrameLayout.LayoutParams) ll.getLayoutParams();// ①
        fl_params.width = ViewGroup.LayoutParams.MATCH_PARENT;
        fl_params.height = ViewGroup.LayoutParams.MATCH_PARENT;
        ll.setLayoutParams(fl_params);
        ll.setGravity(Gravity.CENTER);
        ll.setBackgroundResource(android.R.color.holo_blue_bright);

        TextView tv = new TextView(getContext());
        // tv的父容器是LinearLayout
        LinearLayout.LayoutParams ll_params = (LinearLayout.LayoutParams) tv.getLayoutParams();// ②
        ll_params.width = 160;
        ll_params.height = 160;
        tv.setLayoutParams(ll_params);
        tv.setBackgroundResource(android.R.color.holo_red_dark);
        tv.setText(getText(R.string.tv));

        ll.addView(tv);
        return ll;
    }
}

上面代碼是有問題的,①骚勘、②處都會返回 null铐伴,導致空指針撮奏。
①處:此時還沒有將 LinearLayout 作為返回值返回,也就沒有添加到布局中当宴,自然不存在 LayoutParams畜吊。
②處:此時還沒有將 TextView 添加到 LinearLayout 中,也不存在 LayoutParams户矢。

下面才是正確的示例:

public class LayoutParamsFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        LinearLayout ll = new LinearLayout(getContext());
        // ll的父容器是MainActivity中的FrameLayout
        ll.setLayoutParams(new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        ll.setGravity(Gravity.CENTER);// 子控件居中
        ll.setBackgroundResource(android.R.color.holo_blue_bright);

        TextView tv = new TextView(getContext());
        ll.addView(tv);// 添加到父控件玲献,此時會構造一個LayoutParams出來。

        LinearLayout.LayoutParams ll_params = (LinearLayout.LayoutParams) tv.getLayoutParams();
        ll_params.width = 160;
        ll_params.height = 160;
        tv.setLayoutParams(ll_params);
        tv.setBackgroundResource(android.R.color.holo_red_dark);
        tv.setText(getText(R.string.tv));

        return ll;
    }
}

四梯浪、setLayoutParams 的作用

這里拋出一個問題:

上面代碼中 getLayoutParams() 得到了 LayoutParams 的引用 ll_params捌年,直接對 widthheight 屬性賦值,那么 setLayoutParams() 是不是不需要調用了挂洛?

這就需要看看 setLayoutParams() 里面干了什么

public void setLayoutParams(ViewGroup.LayoutParams params) {
    if (params == null) {
        throw new NullPointerException("Layout parameters cannot be null");
    }
    mLayoutParams = params;
    resolveLayoutParams();// 根據(jù)已解析的布局方向解析布局參數(shù)
    if (mParent instanceof ViewGroup) {
        ((ViewGroup) mParent).onSetLayoutParams(this, params);
    }
    requestLayout();
}

關鍵的最后一行 requestLayout() 延窜,這個方法簡單來說,就是重新執(zhí)行 onMeasure()onLayout()抹锄,而 onDraw() 需要適情況而定逆瑞,這里就不具體展開說了。

現(xiàn)在就可以回答上面的問題了伙单,在上面 onCreateView() 中的 setLayoutParams() 確實是多余的获高,因為在 onCreateView() 之后才會進行 View 的繪制。

當然這并不是說 setLayoutParams() 沒有用吻育,在自定義控件中念秧,往往需要在 View 繪制后修改 LayoutParams 的值,那么這種場景下布疼,如果不調用 setLayoutParams() 就會出現(xiàn)摊趾,設置不生效的問題。

總結:

  • 在 LayoutParams 賦值后游两,如果確定還沒有完成 View 的繪制砾层,可以省略 setLayoutParams() ,在后面繪制期間贱案,會取到前面的賦值肛炮,并使之生效。
  • 如果已經完成了 View 的繪制宝踪,那么必須要調用 setLayoutParams() 侨糟,重新進行繪制。
  • 不確定的情況下就 setLayoutParams() 瘩燥,反正不會出問題秕重。

五、使用 setWidth/setHeight 設置寬高

在設置控件寬高時厉膀,有些人為了方便溶耘,沒有使用 LayoutParams 二拐,直接通過 set 方法設置,但這種方式并不靠譜汰具。

tv.setWidth(160);
tv.setHeight(160);

我這里對 TextView 和 Button 分別設置寬高為 160px卓鹿。

public class LayoutParamsFragment extends Fragment {

    @Nullable
    @Override
    public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container,
                             @Nullable Bundle savedInstanceState) {
        LinearLayout ll = new LinearLayout(getContext());
        // ll的父容器是MainActivity中的FrameLayout
        ll.setLayoutParams(new FrameLayout.LayoutParams(
                ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
        ll.setGravity(Gravity.CENTER);
        ll.setBackgroundResource(android.R.color.holo_blue_bright);

        TextView tv = new TextView(getContext());
        tv.setWidth(160);
        tv.setHeight(160);
        tv.setBackgroundResource(android.R.color.holo_red_dark);
        tv.setText(getText(R.string.tv));
        ll.addView(tv);

        Button bt = new Button(getContext());
        bt.setWidth(160);
        bt.setHeight(160);
        bt.setBackgroundResource(android.R.color.holo_green_dark);
        bt.setText(getText(R.string.tv));
        ll.addView(bt);
        return ll;
    }
}

TextView 設置寬高成功,Button 只在高度上生效留荔,效果如下:


setWidth/setHeight 設置寬高

可以打印下控件寬高看下結果:

tv Width: 160
tv Height: 160
bt Width: 264
bt Height: 160

Button 也是繼承 TextView吟孙,為什么會出現(xiàn)設置失效?進入 setWidth 方法聚蝶,看到在這里只是設置了控件的最大值和最小值:

public void setWidth(int pixels) {
    mMaxWidth = mMinWidth = pixels;
    mMaxWidthMode = mMinWidthMode = PIXELS;

    requestLayout();
    invalidate();
}

LayoutParams 設置的寬高才是真正的寬高:

public LayoutParams(int width, int height) {
    this.width = width;
    this.height = height;
}

再看下onMeasure中杰妓,這里面設置 width 時,有很多類似下面判斷:

if (mMaxWidthMode == EMS) {
    width = Math.min(width, mMaxWidth * getLineHeight());
} else {
    width = Math.min(width, mMaxWidth);
}

if (mMinWidthMode == EMS) {
    width = Math.max(width, mMinWidth * getLineHeight());
} else {
    width = Math.max(width, mMinWidth);
}

所以 setWidth()/setHeight 只代表想設置的寬高碘勉,并不是實際設定值巷挥。這就很好理解,當 set 的值大于 Button 最小寬度/高度時生效验靡,在小于 Button 最小寬度/高度時倍宾,不能起到作用。

參考資料

Android LayoutParams詳解

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
  • 序言:七十年代末胜嗓,一起剝皮案震驚了整個濱河市高职,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌辞州,老刑警劉巖怔锌,帶你破解...
    沈念sama閱讀 211,348評論 6 491
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異变过,居然都是意外死亡埃元,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,122評論 2 385
  • 文/潘曉璐 我一進店門媚狰,熙熙樓的掌柜王于貴愁眉苦臉地迎上來岛杀,“玉大人,你說我怎么就攤上這事哈雏±慵” “怎么了?”我有些...
    開封第一講書人閱讀 156,936評論 0 347
  • 文/不壞的土叔 我叫張陵裳瘪,是天一觀的道長。 經常有香客問我罪针,道長彭羹,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,427評論 1 283
  • 正文 為了忘掉前任泪酱,我火速辦了婚禮派殷,結果婚禮上还最,老公的妹妹穿的比我還像新娘。我一直安慰自己毡惜,他們只是感情好拓轻,可當我...
    茶點故事閱讀 65,467評論 6 385
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著经伙,像睡著了一般扶叉。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上帕膜,一...
    開封第一講書人閱讀 49,785評論 1 290
  • 那天枣氧,我揣著相機與錄音,去河邊找鬼垮刹。 笑死达吞,一個胖子當著我的面吹牛,可吹牛的內容都是我干的荒典。 我是一名探鬼主播酪劫,決...
    沈念sama閱讀 38,931評論 3 406
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼寺董!你這毒婦竟也來了覆糟?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 37,696評論 0 266
  • 序言:老撾萬榮一對情侶失蹤螃征,失蹤者是張志新(化名)和其女友劉穎搪桂,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盯滚,經...
    沈念sama閱讀 44,141評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡踢械,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 36,483評論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了魄藕。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片内列。...
    茶點故事閱讀 38,625評論 1 340
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖背率,靈堂內的尸體忽然破棺而出话瞧,到底是詐尸還是另有隱情,我是刑警寧澤寝姿,帶...
    沈念sama閱讀 34,291評論 4 329
  • 正文 年R本政府宣布交排,位于F島的核電站,受9級特大地震影響饵筑,放射性物質發(fā)生泄漏埃篓。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,892評論 3 312
  • 文/蒙蒙 一根资、第九天 我趴在偏房一處隱蔽的房頂上張望架专。 院中可真熱鬧同窘,春花似錦、人聲如沸部脚。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,741評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽委刘。三九已至丧没,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間钱雷,已是汗流浹背骂铁。 一陣腳步聲響...
    開封第一講書人閱讀 31,977評論 1 265
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留罩抗,地道東北人拉庵。 一個月前我還...
    沈念sama閱讀 46,324評論 2 360
  • 正文 我出身青樓,卻偏偏與公主長得像套蒂,于是被迫代替她去往敵國和親钞支。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,492評論 2 348

推薦閱讀更多精彩內容