前面的文章中着重讲解了 View 的测量流程。其中我提到了一句非常重要的话:View 的测量匡高是由父控件的 MeasureSpec 和 View 自身的 `LayoutParams 共同决定的。我们在前面的 每日一问:谈谈对 MeasureSpec 的理解 把 MeasureSpec 的重点进行了讲解,其实另外一个 LayoutParams 同样是非常非常重要。

从概念讲起

LayoutParams,顾名思义,就是布局参数。而且大多数人对此都是司空见惯,我们 XML 文件里面的每一个 View 都会接触到 layout_xxx 这样的属性,这实际上就是对布局参数的描述。大概大家也就清楚了,layout_ 这样开头的东西都不属于 View,而是控制具体显示在哪里。

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() 这样的方式的赋值方法。

实际上,ViewGroup 的子类的 LayoutParams 类拥有更多的构造方法,感兴趣的自己翻阅源码查看。在这里我想更加强调一下我上面提到的 MarginLayoutParams

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_marginLeftlayout_marginRight 等属性的值。

实际上,事实上,绝大部分容器控件都是直接继承 ViewGroup.MarginLayoutParams 而非 ViewGroup.LayoutParams。所以我们再自定义 LayoutParams 的时候记得继承 ViewGroup.MarginLayoutParams

在代码里面使用 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 进行处理。

原文较多地参考自:https://blog.csdn.net/yisizhu/article/details/51582622

每日一问:LayoutParams 你知道多少?的更多相关文章

  1. 每日一问:Android 消息机制,我有必要再讲一次!

    坚持原创日更,短平快的 Android 进阶系列,敬请直接在微信公众号搜索:nanchen,直接关注并设为星标,精彩不容错过. 我 17 年的 面试系列,曾写过一篇名为:Android 面试(五):探 ...

  2. 每日一问:谈谈 volatile 关键字

    这是 wanAndroid 每日一问中的一道题,下面我们来尝试解答一下. 讲讲并发专题 volatile,synchronize,CAS,happens before, lost wake up 为了 ...

  3. 每日一问:讲讲 Java 虚拟机的垃圾回收

    昨天我们用比较精简的文字讲了 Java 虚拟机结构,没看过的可以直接从这里查看: 每日一问:你了解 Java 虚拟机结构么? 今天我们必须来看看 Java 虚拟机的垃圾回收算法是怎样的.不过在开始之前 ...

  4. 每日一问:你了解 Java 虚拟机结构么?

    对于从事 C/C++ 程序员开发的小伙伴来说,在内存管理领域非常头疼,因为他们总是需要对每一个 new 操作去写配对的 delete/free 代码.而对于我们 Android 乃至 Java 程序员 ...

  5. 每日一问:简述 View 的绘制流程

    Android 开发中经常需要用一些自定义 View 去满足产品和设计的脑洞,所以 View 的绘制流程至关重要.网上目前有非常多这方面的资料,但最好的方式还是直接跟着源码进行解读,每日一问系列一直追 ...

  6. android 深入浅出 群内“每日一问” 问答总结

    永远不变的就是变. 俗话说的好,环境改变人生. 常常面对的是一群积极奋进的人,那么你的心态和生活也会变的充满斗志.青春在于折腾,趁我们还年轻,拿出你的激情.踏着泪水载着梦,才干拥有自己的一片天空. 上 ...

  7. 每日一问:面试结束时面试官问"你有什么问题需要问我呢",该如何回答?

    面试结束时面试官问"你有什么问题需要问我呢",该如何回答?

  8. Python【每日一问】15

    问:简述with方法打开处理文件实际上做了哪些工作 答: filename= "test.txt" with open(filename, "w", encod ...

  9. Python【每日一问】08

    问:请解释一下装饰器的本质.功能 答: 1.装饰器的本质:闭包函数 2.装饰器的功能:在不改变函数本体结构.调用方法的情况下,给函数添加额外的功能 3.装饰器的实现方式 装饰器的实现方式一般是: de ...

随机推荐

  1. python 做词云图

    #导入需要模块 import jieba import numpy as np import matplotlib.pyplot as plt from PIL import Image from w ...

  2. .net Core 学习笔记(实体字段映射,IOC注入)

    https://github.com/wj1034184751/ADO.NetCore.git 先简单的做一个学习架子, EF层用的是 EFCore(2.1.0)+ Pomelo.EntityFram ...

  3. EF Core反向导航属性解决多对一关系

    多对一是一种很常见的关系,例如:一个班级有一个学生集合属性,同时,班级有班长.语文课代表.数学课代表等单个学生属性,如果定义2个实体类,班级SchoolClass和学生Student,那么,班级Sch ...

  4. C#通过字符串分割字符串Split

    string[] strArr = str.Split(new[] {"****==="},StringSplitOptions.None); 更多内容关注公众号 洛水梅家

  5. [转]ASP.NET Core Web API 最佳实践指南

    原文地址: ASP.NET-Core-Web-API-Best-Practices-Guide 转自 介绍# 当我们编写一个项目的时候,我们的主要目标是使它能如期运行,并尽可能地满足所有用户需求. 但 ...

  6. CTS,CLS,CLR解释

    问题阐述 CTS.CLS和 CLR分别是什么意思? 专家解答 CTS.CLS和 CLR是.NET框架的 3个核心部分,下面分别对它们进行介绍. (1)CTS CTS即通用类型系统,它定义了如何在.NE ...

  7. 华为方舟编译器 下载 和 LiteOS Studio Setup 2019-04-16.exe SDK下载

    华为方舟编译器是首个取代Android虚拟机模式的静态编译器,可供开发者在开发环境中一次性将高级语言编译为机器码.此外,方舟编译器未来将支持多语言统一编译,可大幅提高开发效率. 编译器下载 [Ark] ...

  8. desktoplayer.exe病毒及d:\w7rtm\base\wcp\sil\merged\ntu\ntsystem.cpp的解决方案

    1 前言 该病毒,使用360普通杀毒杀不出来,而且会伴随以下问题: a.电脑蓝屏问题[多图] b.fsc/scannow CbS.log d:\w7rtm\base\wcp\sil\merged\nt ...

  9. 前端H5知识总结

    两年大专生活匆匆而过,身边的朋友也都各奔东西,9月份开始实习感觉自己的前端功底有所欠缺,这个暑假除了打工我还有一个半月的学习时间希望自己能够充分利用这段时间.7月3号所学知识在此做以下总结以便自己复习 ...

  10. 分析mybatis中 #{} 和${}的区别

    分析方法: 在 GenericTokenParser这个类的parse方法的这一行下个断点调试一下就明白了 builder.append(handler.handleToken(content)); ...