前面的文章中着重講解了 View 的測量流程。其中我提到了一句很是重要的話:View 的測量匡高是由父控件的 MeasureSpec
和 View 自身的 `LayoutParams 共同決定的。咱們在前面的 每日一問:談談對 MeasureSpec 的理解 把 MeasureSpec 的重點進行了講解,其實另一個 LayoutParams 一樣是很是很是重要。java
LayoutParams
,顧名思義,就是佈局參數。並且大多數人對此都是司空見慣,咱們 XML 文件裏面的每個 View 都會接觸到 layout_xxx
這樣的屬性,這實際上就是對佈局參數的描述。大概你們也就清楚了,layout_
這樣開頭的東西都不屬於 View,而是控制具體顯示在哪裏。android
一般來講,咱們都會把咱們的控件放在 XML 文件中,即便咱們有時候須要對屏幕作比較「取巧」的適配,會直接經過 View.getLayoutParams()
這樣的方法獲取 LayoutParams
的實例,但咱們接觸的少並不表明它的初始化方法不重要。git
實際上,用代碼寫出來的 View 加載效率要比在 XML 中加載快上大約 1 倍。只是在現在手機配置都比較高的狀況下,咱們經常忽略了這種方式。github
咱們來看看 ViewGroup.LayoutParams
到底有哪些構造方法。ide
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
作處理的方法外,咱們在 ViewGroup
中還有 3 個構造方法。他們分別負責給 XML 處理、直接讓用戶指定寬高、還有相似集合的 addAll()
這樣的方式的賦值方法。佈局
實際上,ViewGroup
的子類的 LayoutParams
類擁有更多的構造方法,感興趣的本身翻閱源碼查看。在這裏我想更增強調一下我上面提到的 MarginLayoutParams
。flex
MarginLayoutParams
繼承於 ViewGroup.LayoutParams
。this
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
等屬性的值。google
實際上,事實上,絕大部分容器控件都是直接繼承
ViewGroup.MarginLayoutParams
而非ViewGroup.LayoutParams
。因此咱們再自定義LayoutParams
的時候記得繼承ViewGroup.MarginLayoutParams
。flexbox
前面介紹了 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
進行處理。
原文較多地參考自:https://blog.csdn.net/yisizhu/article/details/51582622