先來看一個簡單的布局摊阀,先用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>
效果也很簡單
如果想要代碼動態(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;
}
}
效果如下:
我們發(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:
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
捌年,直接對width
和height
屬性賦值,那么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 只在高度上生效留荔,效果如下:
可以打印下控件寬高看下結果:
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 最小寬度/高度時倍宾,不能起到作用。