從概念講起java
LayoutParams,顧名思義,就是佈局參數。並且大多數人對此都是司空見慣,咱們 XML 文件裏面的每個 View 都會接觸到 layout_xxx 這樣的屬性,這實際上就是對佈局參數的描述。大概你們也就清楚了,layout_ 這樣開頭的東西都不屬於 View,而是控制具體顯示在哪裏。android
LayoutParams 都有哪些初始化方法面試
一般來講,咱們都會把咱們的控件放在 XML 文件中,即便咱們有時候須要對屏幕作比較「取巧」的適配,會直接經過 View.getLayoutParams() 這樣的方法獲取 LayoutParams 的實例,但咱們接觸的少並不表明它的初始化方法不重要。架構
實際上,用代碼寫出來的 View 加載效率要比在 XML 中加載快上大約 1 倍。只是在現在手機配置都比較高的狀況下,咱們經常忽略了這種方式。
咱們來看看 ViewGroup.LayoutParams 到底有哪些構造方法。
public LayoutParams(Context c, AttributeSet attrs) { TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_Layout); setBaseAttributes(a, R.styleable.ViewGroup_Layout_layout_width, R.styleable.ViewGroup_Layout_layout_height); a.recycle(); } public LayoutParams(int width, int height) { this.width = width; this.height = height; } public LayoutParams(LayoutParams source) { this.width = source.width; this.height = source.height; } LayoutParams() { } MarginLayoutParams
除去最後一個放給 MarginLayoutParams 作處理的方法外,咱們在 ViewGroup 中還有 3 個構造方法。他們分別負責給 XML 處理、直接讓用戶指定寬高、還有相似集合的 addAll() 這樣的方式的賦值方法。ide
實際上,ViewGroup 的子類的 LayoutParams 類擁有更多的構造方法,感興趣的本身翻閱源碼查看。在這裏我想更增強調一下我上面提到的佈局
MarginLayoutParams 繼承於 ViewGroup.LayoutParams。學習
public static class MarginLayoutParams extends ViewGroup.LayoutParams { @ViewDebug.ExportedProperty(category = "layout") public int leftMargin; @ViewDebug.ExportedProperty(category = "layout") public int topMargin; @ViewDebug.ExportedProperty(category = "layout") public int rightMargin; @ViewDebug.ExportedProperty(category = "layout") public int bottomMargin; @ViewDebug.ExportedProperty(category = "layout") private int startMargin = DEFAULT_MARGIN_RELATIVE; @ViewDebug.ExportedProperty(category = "layout") private int endMargin = DEFAULT_MARGIN_RELATIVE; public MarginLayoutParams(Context c, AttributeSet attrs) { super(); TypedArray a = c.obtainStyledAttributes(attrs, R.styleable.ViewGroup_MarginLayout); setBaseAttributes(a, R.styleable.ViewGroup_MarginLayout_layout_width, R.styleable.ViewGroup_MarginLayout_layout_height); int margin = a.getDimensionPixelSize( com.android.internal.R.styleable.ViewGroup_MarginLayout_layout_margin, -1); if (margin >= 0) { leftMargin = margin; topMargin = margin; rightMargin= margin; bottomMargin = margin; } else { int horizontalMargin = a.getDimensionPixelSize( R.styleable.ViewGroup_MarginLayout_layout_marginHorizontal, -1); // ... something } // ... something } }
一看代碼,天然就清楚了,爲何咱們之前會發如今 XML 佈局裏, layout_margin 屬性的值會覆蓋 layout_marginLeft 與 layout_marginRight 等屬性的值。this
實際上,事實上,絕大部分容器控件都是直接繼承 ViewGroup.MarginLayoutParams 而非 ViewGroup.LayoutParams。因此咱們再自定義 LayoutParams 的時候記得繼承 ViewGroup.MarginLayoutParams 。code
在代碼裏面使用 LayoutParams視頻
前面介紹了 LayoutParams 的幾種構造方法,咱們下面以 LinearLayout.LayoutParams 來看看幾種簡單的使用方式。
val textView1 = TextView(this) textView1.text = "不指定 LayoutParams" layout.addView(textView1) val textView2 = TextView(this) textView2.text = "手動指定 LayoutParams" textView2.layoutParams = LinearLayout.LayoutParams(LinearLayout.LayoutParams.WRAP_CONTENT, LinearLayout.LayoutParams.WRAP_CONTENT) layout.addView(textView2) val textView3 = TextView(this) textView3.text = "手動傳遞 LayoutParams" textView3.layoutParams = LinearLayout.LayoutParams(ViewGroup.LayoutParams(100, 100)) layout.addView(textView3)
咱們看看 addView() 都作了什麼。
public void addView(View child) { addView(child, -1); } 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); } @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; } public void addView(View child, int index, LayoutParams params) { if (DBG) { System.out.println(this + " addView"); } if (child == null) { throw new IllegalArgumentException("Cannot add a null child view to a ViewGroup"); } requestLayout(); invalidate(true); addViewInner(child, index, params, false); } private void addViewInner(View child, int index, LayoutParams params, boolean preventRequestLayout) { // ... if (!checkLayoutParams(params)) { params = generateLayoutParams(params); } // ... } @Override protected boolean checkLayoutParams(ViewGroup.LayoutParams p) { return p instanceof LinearLayout.LayoutParams; }
看起來 ViewGroup 真是煞費苦心,若是咱們沒有給 View 設置 LayoutParams,則系統會幫咱們根據 orientation 設置默認的 LayoutParams。甚至是咱們即便在 addView() 以前設置了錯誤的 LayoutParams 值,系統也會咱們幫咱們進行糾正。
雖然系統已經作的足夠完善,幫咱們各類矯正錯誤,但在 addView() 以後,咱們還強行設置錯誤的 LayoutParams,那仍是必定會報 ClassCastException 的。
LayoutParams 很重要,每一名 Android 開發都應該盡力地去掌握,只有弄清楚了系統的編寫方式,應對上面相似簡書的流式佈局才能更好處理。
實際上 Google 出的 FlexboxLayout 已經作的至關完美。
固然若是使用的 RecyclerView,還能夠本身寫一個 FlowLayoutManager 進行處理。