从概念讲起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 进行处理。