前阵子帮一个朋友改页面一个很普通的商品卡片打开布局文件我数了一下LinearLayout 套了七层最里层还塞了个 RelativeLayout改一个边距要顺着缩进找半天跑起来列表滚动还有点掉帧。这类问题几乎每个月都能碰上,追到根上,还是对Android 六大基本布局的理解停留在“会用”的层面没到“用对”。这篇文章就是把这六种容器彻底摊开讲一遍谁还在生产环境挑大梁、谁的测量逻辑藏着性能陷阱、权重的账到底怎么算、层级怎么压、出了问题从哪查。内容偏实战写给已经能写出界面、但想让布局更快更干净的中级开发者也写给刚接触 Android、想少走弯路的同学。看完你至少能做到两件事拿到一个新界面能在三分钟内判断该用哪种容器老代码里的布局膨胀能定位到具体是哪一层在拖后腿。1. 六种布局的定位盘点与选型逻辑1.1 谁在一线干活谁已经进了陈列柜所谓六大基本布局指的是 LinearLayout、RelativeLayout、FrameLayout、TableLayout、AbsoluteLayout、GridLayout。这套分类在早期文档里就定下来了后来 ConstraintLayout 进场把相对定位和链条、 Guideline 这些能力做得更彻底六大基本布局的出场率确实降了不少但它们并没有失效源码还在测量逻辑还在你随便翻一个开源项目FrameLayout 和 LinearLayout 依然是出现次数最多的两个类。真正的情况是这样分布的FrameLayout 现在是 Fragment 容器、自定义 View 组合、遮罩层、骨架屏的主力因为它最简单onMeasure 走一遍所有子 ViewonLayout 按 gravity 堆叠没有额外计算LinearLayout 在大量原生控件内部、列表 item、表单行里高频出现典型例子是 Toolbar 里的标题和按钮以及各种“图标 文字”的横向组合RelativeLayout 在中大型项目里逐渐被 ConstraintLayout 取代但老模块里仍然遍地都是TableLayout 用得少可在纯表格数据、设置页里成组的开关行这种场景它写起来比手搓网格省事GridLayout 属于慢热型API 14 就有了能跨行跨列但语法啰嗦、属性兼容性有历史包袱实际项目里往往被 RecyclerView GridLayoutManager 抢了活。AbsoluteLayout 要单独说一句。它在 API 3 就被标记为废弃理由是绝对坐标无法适配五花八门的屏幕一堆 layout_x、layout_y 写死像素换台机器就错位。你现在还能在代码里引用它但不要在任何一个需要上线的界面里使用遇到老代码里出现直接改成 ConstraintLayout 或者带 margin 的 FrameLayout。1.2 选型看的是测量次数和嵌套深度不是个人喜好很多人选布局靠手感顺手用 LinearLayout 堆堆不下就套一层。这种做法的代价藏在 ViewRootImpl 的 performMeasure 里。每一次嵌套父容器要把测量约束往子容器传一遍子容器测量完再往回算一次层级越深measure 和 layout 的递归就越长。低端机上一次 onMeasure 多花两三毫秒看着不起眼放到 RecyclerView 里滑动一帧只有 16.6 毫秒预算几层嵌套就能把它吃掉。所以判断标准很朴素能一层搞定的就不用两层能在同一层表达关系的就不靠嵌套。具体到容器元素沿一个方向一条线排列且需要按比例分配剩余空间——LinearLayout 最合适权重是它的看家本事。元素之间存在“A 在 B 左边”“C 在 B 下方”这种相对位置关系且层级要求扁平——RelativeLayout 或者 ConstraintLayout。元素是叠放的后加的盖住先加的或者只需要一个居中的浮层——FrameLayout。严格的行列对齐、每列宽度由内容决定——TableLayout 或者 GridLayout。关系复杂、包含多个对齐基准、需要百分比定位——直接上 ConstraintLayout别硬用基本布局拼。有个经验值可以参考官方在性能建议里提到布局层级尽量控制在十层以内超过就值得警惕。这不是硬指标但在中低端设备上十层往上的嵌套基本都会在滑动时露出痕迹。2. 逐个拆开每种容器的脾气与隐藏属性2.1 LinearLayout权重那一笔账必须算清楚LinearLayout 的核心就两个词方向、权重。方向决定子 View 是横向排还是纵向排权重决定剩余空间怎么分。真正把人绕晕的是权重因为它牵扯到两次测量。以横向 LinearLayout 为例宽度 match_parent里面三个子 View宽度都是 0dplayout_weight 分别是 1、2、1父容器可用宽度 1080px。第一次测量时如果子 View 宽度写的是 0dp它测出来的期望宽度就是 0如果写的是 wrap_content就先按内容撑开测一遍。然后父容器算出剩余空间 1080 - 0 - 0 - 0 1080再按权重比 1:2:1 分配三个子 View 分别拿到 270、540、270。这就是宽度写 0dp 的意义把宽度完全交给权重避免内容宽度参与计算导致比例不准。如果子 View 宽度写的是 wrap_content比如第一个是图标 100px第二个是文字 300px第三个是按钮 200px总占用 600px剩余 480px按 1:2:1 分各自加上 120、240、120最终宽度分别是 220、540、320。这时候你会发现实际比例和权重看起来对不上因为权重分的是“剩余空间”不是总空间。这是新手最容易误解的一点。再看剩余空间为负的情况。父容器宽度 500px三个子 View 内容宽度加起来 700px超了 200px。这时候权重的分配方向反过来从每个子 View 身上按比例扣。如果扣完变成负数Android 会把它当 0 处理然后触发挤压界面就花了。出现这种情况要么减小内容宽度要么给父容器加 ScrollView要么用 ellipsize 处理文本。还有几个属性顺带说一下。weightSum 用来显式指定权重总和适合子 View 的权重是动态计算的场景比如只让某一个 View 占剩余空间的 40%那就把 weightSum 设成 100它的 weight 设成 40。baselineAligned 默认是 true意思是横向排列时按文字基线对齐这个属性会让每个子 View 额外做一次基线测量如果子 View 里没有文字或者不需要基线对齐务必设成 false能省一点开销。measureWithLargestChild 在子 View 都带权重时会把最大子 View 的最小尺寸分给所有子 View用得少但知道有这么个东西遇到“权重分配的宽度被内容撑爆”时可以试着打开看看。注意LinearLayout 的 divider 和 showDividers 用来画子 View 之间的分割线比给每个子 View 都加一层 View 当线要省层级写列表项的时候值得用。2.2 RelativeLayout一次测量换来的复杂度RelativeLayout 的设计初衷是扁平化它最吸引人的地方是“子 View 之间用关系定位不需要嵌套”。要实现一个头像在左、名字在头像右边、签名在名字下面这种结构LinearLayout 得套两层RelativeLayout 一层就够。它的测量过程和 LinearLayout 不太一样。RelativeLayout 会先根据子 View 的依赖关系做一次排序被依赖的排在前面然后按这个顺序测量。理论上它只需要一次测量就能把所有子 View 的位置算出来这是它相比多层嵌套 LinearLayout 的优势所在。但现实中如果依赖链写得很长比如 A 依赖 BB 依赖 CC 依赖 D一层层往上传测量的递归深度同样会上去优势就没那么明显了。属性上分两类记最清楚。第一类是相对父容器layout_alignParentTop、layout_alignParentBottom、layout_alignParentLeft、layout_alignParentRight、layout_centerInParent、layout_centerHorizontal、layout_centerVertical。第二类是相对兄弟 Viewlayout_toLeftOf、layout_toRightOf、layout_above、layout_below、layout_alignTop、layout_alignBottom、layout_alignLeft、layout_alignRight。第二类必须配合 id 使用被引用的 View 必须写在前面否则运行时找不到。这里有一个必须知道的坑循环依赖。A 用 layout_toRightOf 指向 BB 又用 layout_toLeftOf 指向 A编译期不报错运行时抛出异常异常信息里会提到循环依赖。排查方法是把所有 layout_toXxxOf、layout_above/below 的关系画成有向图看有没有环。项目里相对布局写多了这种问题一周能碰上两三次。另一个容易忽略的是 layout_alignWithParentIfMissing。当被引用的 View 因为可见性变成 GONE 而“消失”时依赖它的 View 定位会失去参照位置可能跑到左上角。把这个属性打开缺失参照时就改为相对父容器对齐能避免一部分错位。不过更好的做法是在代码里控制 GONE 改为 INVISIBLE或者重新设置约束。实操心得RelativeLayout 里子 View 的 id 命名一定要有语义因为关系是靠 id 串起来的一堆 view1、view2、view3 会让三个月后的你完全看不懂谁靠着谁。2.3 FrameLayout最朴素也最容易踩坑FrameLayout 的逻辑是“所有子 View 都放在同一个位置后加的盖住先加的”。它默认不做任何排列只提供一个 layout_gravity 让子 View 自己决定贴左上、居中还是右下。很多人觉得它太简单没什么好讲的但它是自定义 View 组合、Fragment 容器、悬浮按钮、加载遮罩的首选用得多坑也就多。第一个坑是 measureAllChildren 属性。默认情况下FrameLayout 会跳过 GONE 的子 View但如果设成 trueGONE 的子 View 也会参与测量。这个属性本来是给某些需要预先知道子 View 尺寸的场景用的多数人不会主动设但如果你发现某个已隐藏的 View 还在占位先检查这里。第二个坑是 clipChildren。默认 true子 View 超出父容器边界会被裁剪。做动画的时候比如按钮放大、弹窗从底部滑出、头像从圆形区域溢出很容易被裁掉一半找半天找不到原因其实在父容器上加一句 clipChildren“false” 就行。但要注意关掉裁剪会影响绘制性能能局部关就不要全局关。第三个坑是 foreground。FrameLayout 支持设置前景色或前景图早期经常用它做图片上的半透明蒙层。这个属性在 API 23 之后有变化前景的绘制时机和裁剪行为调整过做兼容的时候需要留意。现在更推荐用一个普通的 View 当遮罩层控制可见性更直观。FrameLayout android:layout_widthmatch_parent android:layout_height200dp android:clipChildrenfalse ImageView android:layout_widthmatch_parent android:layout_heightmatch_parent android:scaleTypecenterCrop / TextView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_gravitybottom|start android:layout_margin12dp android:text标签 / /FrameLayout这段代码就是典型的图片卡底层塞一张铺满的图上层贴一张标签。用 FrameLayout 两行搞定换成 LinearLayout 得反过来堆还得处理图片的宽高比。2.4 TableLayout表格数据的老实解法TableLayout 继承自 LinearLayout本质是一个纵向的线性容器每一行是一个 TableRow行内每个子 View 占一列。列数由子 View 最多的那一行决定每列的宽度取该列所有子 View 里最宽的那个行高取该行最高的那个。它有三个关键属性理解了就能用明白。stretchColumns 指定哪些列可以被拉伸用来填满父容器的剩余宽度shrinkColumns 指定哪些列可以被压缩用来应对内容超宽collapseColumns 指定哪些列直接隐藏适合做列的动态显隐。三个属性的值都可以用逗号分隔的列索引比如 “0,2” 表示第 0 列和第 2 列。表格布局最舒服的场景是设置页。比如一列是图标、一列是标题、一列是开关用 TableLayout 写所有行的列宽自动对齐不会出现“每行开关位置差几个像素”的丑陋效果。用 LinearLayout 写每一行都要手动对齐改一处要改十处。它的问题也很明显不能跨行跨列列宽完全由内容决定想让某一列固定宽度只能靠给子 View 设宽度而且嵌套 TableRow 的写法在复杂表格里会显得层级很深。遇到需要合并单元格的表格直接换 GridLayout 或者用 RecyclerView 自己画。2.5 AbsoluteLayout 与 GridLayout一个退场一个慢热AbsoluteLayout 的用法是在子 View 上写 layout_x 和 layout_y单位是像素。它在 API 3 被标记废弃理由很充分手机屏幕密度、尺寸、比例千差万别写死坐标的界面换台设备就崩。你在任何需要维护的项目里都不该用它看到就改改成 ConstraintLayout 的百分比约束或者用 margin gravity 的组合。GridLayout 是 API 14 引入的思路和 TableLayout 类似但能力强不少可以指定行数、列数可以跨行跨列还可以用 layout_columnWeight、layout_rowWeight 做权重分配。它的问题是写法比较啰嗦每个子 View 都要写 layout_row、layout_column、layout_rowSpan、layout_columnSpan而且 useDefaultMargins 打开后会加上平台默认的间距这个间距在不同版本上略有差异做精细视觉还原时容易对不齐需要手动关掉再自己控制 margin。GridLayout 真正合适的场景是固定网格的仪表盘、九宫格入口、键盘布局。这些界面元素数量固定、行列关系明确用 GridLayout 一层就能画完比嵌套两层 LinearLayout 干净。但如果网格需要动态增减、需要回收复用那还是 RecyclerView 的 GridLayoutManager 更合适。GridLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:columnCount3 android:rowCount2 android:useDefaultMarginsfalse TextView android:layout_width0dp android:layout_heightwrap_content android:layout_columnWeight1 android:layout_columnSpan2 android:text跨两列 / TextView android:layout_width0dp android:layout_heightwrap_content android:layout_columnWeight1 android:text第三列 / /GridLayout注意 layout_columnWeight 要配合宽度 0dp 才有意义和 LinearLayout 的权重是同一个道理。3. 拿一个商品卡片练手三种写法的真实代价3.1 需求拆解与层级草图需求不复杂左侧一张正方形商品图右侧上方是标题两行标题下面是一行标签再下面是价格最底部一行两个按钮并排左边加入购物车右边立即购买。整张卡片左右各 16dp 内边距上下 12dp。拆开看元素之间的关系有两种图片和右侧内容区是横向并列右侧内容区的内部元素是纵向排列。这种结构最容易想到的就是 LinearLayout 横向套 LinearLayout 纵向我见过的大多数代码都是这么写的。先用 LinearLayout 写一遍把层级数出来。LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:padding12dp ImageView android:layout_width96dp android:layout_height96dp android:scaleTypecenterCrop / LinearLayout android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:layout_marginStart12dp android:orientationvertical TextView android:layout_widthmatch_parent android:layout_heightwrap_content android:maxLines2 android:ellipsizeend android:text商品标题占两行 / LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:layout_marginTop6dp android:orientationhorizontal TextView android:layout_widthwrap_content android:layout_heightwrap_content android:text标签A / TextView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginStart6dp android:text标签B / /LinearLayout TextView android:layout_widthwrap_content android:layout_heightwrap_content android:layout_marginTop6dp android:text¥99.00 / /LinearLayout /LinearLayout LinearLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:orientationhorizontal android:paddingStart12dp android:paddingEnd12dp android:paddingBottom12dp Button android:layout_width0dp android:layout_heightwrap_content android:layout_weight1 android:text加入购物车 / Button android:layout_width0dp android:layout_heightwrap_content android:layout_marginStart12dp android:layout_weight1 android:text立即购买 / /LinearLayout数一下层级最外层纵向 LinearLayout1横向内容行2右侧文字区3标签行4。加上卡片本身的父容器和 RecyclerView 的条目包装实际渲染树里这一块会占到六到七层。这还只是一个卡片列表里每一条都是这么一套。这里我用了两个顶层 LinearLayout其实应该合成一个纵向容器只是为了说明“横向并列”和“按钮行”分开写的情况。合并之后层级不会变少因为按钮行必须独立于内容行。3.2 ConstraintLayout 扁平写法与对比结论同样的界面换成 ConstraintLayout所有元素都在一层里靠约束互相拉关系。androidx.constraintlayout.widget.ConstraintLayout android:layout_widthmatch_parent android:layout_heightwrap_content android:padding12dp ImageView android:idid/cover android:layout_width96dp android:layout_height96dp android:scaleTypecenterCrop app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toTopOfparent / TextView android:idid/title android:layout_width0dp android:layout_heightwrap_content android:layout_marginStart12dp android:maxLines2 android:ellipsizeend app:layout_constraintEnd_toEndOfparent app:layout_constraintStart_toEndOfid/cover app:layout_constraintTop_toTopOfid/cover / TextView android:idid/price android:layout_widthwrap_content android:layout_heightwrap_content app:layout_constraintBottom_toBottomOfid/cover app:layout_constraintStart_toStartOfid/title / Button android:idid/btnAdd android:layout_width0dp android:layout_heightwrap_content android:layout_marginTop8dp android:text加入购物车 app:layout_constraintEnd_toStartOfid/btnBuy app:layout_constraintHorizontal_weight1 app:layout_constraintStart_toStartOfparent app:layout_constraintTop_toBottomOfid/cover / Button android:idid/btnBuy android:layout_width0dp android:layout_heightwrap_content android:layout_marginStart12dp android:text立即购买 app:layout_constraintEnd_toEndOfparent app:layout_constraintHorizontal_weight1 app:layout_constraintStart_toEndOfid/btnAdd app:layout_constraintTop_toTopOfid/btnAdd / /androidx.constraintlayout.widget.ConstraintLayout这一版只有一层父容器所有 View 平铺。渲染时的测量树浅了一大截而且约束系统只做一次完整的测量传递不需要子容器再向父容器回传尺寸。代价是阅读门槛。约束关系是分散在各个 View 上的要读懂这个界面得把所有约束在脑子里连起来。层级深的时候 LinearLayout 反而“一眼看明白”这是它至今没被淘汰的真实原因。我的实践结论是单行或单列的元素组合LinearLayout 足够且更直观一旦出现超过两层的嵌套就该考虑换成扁平约束。按这个原则上面这个卡片我会用 ConstraintLayout因为 LinearLayout 版本已经是四层。但如果是 Toolbar 里“返回箭头 标题 右侧按钮”这种一条线上的三个元素我毫不犹豫用横向 LinearLayout写起来三行读起来一目了然为了扁平化硬上约束纯属给自己找麻烦。注意ConstraintLayout 里宽度设 0dp 表示“由约束决定宽度”不是“宽度为 0”。很多人从 LinearLayout 的思维跳过来看到 0dp 就以为写错了。4. 布局瘦身三个复用标签与耗时实测4.1 include、merge、ViewStub 的正确打开方式层级能压就压但业务复杂度上来了重复结构不可避免。这三个标签是老生常谈可用错的地方比用对的地方多。include 用来引入一个公共布局文件最常见的坑是 include 后给根节点设 layout_width 和 layout_height结果不生效。原因是布局文件里的根节点自己也写了宽高两者冲突时以被引入文件里的为准。正确做法是被引入的布局根节点写成 wrap_content 或者 match_parent 之外的值时必须在业务侧覆盖覆盖的方式是把被引入布局的根节点宽高写成具体的、能在 include 时被替换的值并且 include 时同时写宽高。简单点说公共布局的根节点最好别写死宽高。merge 是为了消除 include 带来的多余层级。比如公共布局的根节点是 LinearLayout被 include 到另一个 LinearLayout 里就多了一层。把根节点改成 merge引入时直接就变成子 View层级少一层。但 merge 有两个限制它必须作为布局文件的根标签且 include 时必须指定父容器能识别的宽高不能单独用。ViewStub 是延迟加载占位但不渲染调用 inflate() 之后才真正创建。它适合那些“平时不显示、出现概率低”的模块比如错误提示页、空数据占位、某块需要用户操作后才展开的内容。用 ViewStub 替换掉 GONE 的普通布局能在首屏省掉这部分测量和绘制。但要注意ViewStub inflate 之后就从视图树里移除了取而代之的是真实布局所以第二次想再显示不能再用 ViewStub 的方法得用 findViewById 找到真实布局控制可见性。val stub findViewByIdViewStub(R.id.stub_empty) if (!stub.isInflated) { val emptyView stub.inflate() emptyView.visibility View.VISIBLE }这段判断很有必要因为 inflate 只能调一次重复调会抛异常。4.2 从 Layout Inspector 到手动打点怎么量出布局耗时性能分析不能靠感觉。布局这块有两个现成的工具。Layout Inspector 能看到运行时的完整视图树每一层的属性、尺寸、可见性全都列出来。用来找“不该存在的那一层”非常高效翻一遍树凡是 LinearLayout 下面只有一个子 View 的基本都可以删凡是 FrameLayout 只为了加个背景的都可以用 background 属性代替。我一般改老页面之前先截一张树图改完再截一张对比层级数效果很直观。手动打点适合更精确的场景。在布局根节点的自定义 View 里覆写 onMeasure 和 onLayout记录 System.nanoTime() 的差值跑十次取平均能得到这个界面在这一台设备上的真实耗时。同一段代码在不同机型上差距可能有三四倍低端机上测量耗时超过 8 毫秒就要开始警惕了因为整帧预算只有 16.6 毫秒测量占一半留给绘制和逻辑的就所剩无几。还有一个容易忽略的点是过度绘制。层级叠得多上层不透明的 View 盖住下层下层白画了一遍。开发者选项里打开“调试 GPU 过度绘制”屏幕上的颜色越红说明叠得越狠。一屏出现大面积红色说明有背景被重复绘制把最外层容器的背景删掉、让根布局不去画背景往往能立竿见影。这不是布局结构问题但和层级深度直接相关改布局的时候顺手一起清掉。实操心得布局优化别一次改一整个页面改完一处就滑一遍列表。列表滚动的手感是最诚实的验收标准掉帧一摸就知道。5. 常见问题与排查实录5.1 权重、挤压、错位类问题速查权重相关的问题几乎占了布局问题的三分之一我把高频的整理成表遇到直接对号入座。现象常见原因处理方式权重比例和预期对不上子 View 宽度写了 wrap_content权重分的是剩余空间需要严格比例时宽度改 0dp某个子 View 宽度变成 0剩余空间为负按权重扣除后归零减小内容宽度、加 ellipsize 或外层套滚动容器权重完全不生效父容器对应方向没设 match_parent没有剩余空间可分配检查父容器宽/高是否为 match_parent权重分配后仍被内容撑破子 View 最小宽度大于分配结果用 minWidth 控制或改 wrap_content 配合 maxWidth横向排列文字被截断没有设置 ellipsize且宽度不够加 maxLines 和 ellipsize“end”错位类问题多半出在 gravity 和 layout_gravity 搞混。gravity 是控制自己内部的内容怎么对齐layout_gravity 是控制自己在父容器里怎么对齐。一个 TextView 里文字居中用 gravityTextView 自己在 FrameLayout 里居中用 layout_gravity。写在同一个节点上时两者不冲突但很多人只写一个就以为两个都生效了。另外margin 和 padding 混用也是老问题。padding 是内边距在容器内部影响的是内容到边框的距离margin 是外边距在容器外部影响的是自己和邻居的距离。给父容器加 padding 会让所有子 View 一起往里缩给子 View 加 margin 只影响它自己。想让整块内容离屏幕边缘有距离给根布局加 padding想让某个按钮和旁边的按钮隔开给它加 margin。5.2 遮挡、点击穿透与测量异常遮挡问题先查层级顺序。FrameLayout 和 RelativeLayout 里后写的 View 会盖在先写的上面LinearLayout 里不会重叠除非用了负 margin。View 的 elevation 也会影响绘制顺序z 轴高的会盖住低的这个属性在 API 21 之后才有效做兼容时要注意。点击穿透分成两种情况。一种是上层 View 设置了 clickable“false”触摸事件穿透到下层这在用透明遮罩拦截点击时特别容易出现正确做法是给遮罩设 clickable“true”。另一种是父容器设置了 onClickListener子 View 没设触摸落到父容器上表现就是“点哪里都触发父容器的事件”。排查时用 Layout Inspector 看每个 View 的 clickable 状态一目了然。测量异常的典型表现是“View 明明设了高度实际显示不对”。第一检查是不是在 ScrollView 里写了 match_parent垂直滚动的容器里子 View 高度设 match_parent 会导致高度无法确定通常要改成 wrap_content。第二检查是不是在自定义 View 里没处理 MeasureSpec 的 AT_MOST 模式导致 wrap_content 被当成 match_parent。第三检查是不是 View 的可见性在测量之后被改成了 VISIBLE这时候需要手动调用 requestLayout() 才会重新测量。还有一个隐蔽的问题RecyclerView 的 item 根布局用了 wrap_content 高度但内部有需要按比例计算的元素第一次测量出来的高度和最终绘制的高度不一致表现是列表滚动时 item 高度轻微跳动。处理办法是把 item 根布局改成固定高度或者用 ConstraintLayout 的 ratio 约束让高度在一次测量内确定下来。6. 几条我自己踩出来的经验布局这块的坑文档里写得再全也不如自己改坏几个页面记得牢。分享几条我这些年攒下来的判断。第一看到布局文件里出现三层以上的 LinearLayout 嵌套我会停下来想十秒这十秒通常能省掉后面半小时的性能排查。层级不是不能深但每深一层都要能说出理由说不出来就是可以压的。第二权重只用来处理“剩余空间按比例分配”这一件事不要指望它做等比布局。要让两个按钮各占一半最稳的写法是两个都写 0dp、weight 都写 1要做一个固定宽度加一个填充的布局固定宽度的那个写死另一个写 0dp、weight1。这两种模式覆盖了我遇到的九成权重场景剩下的复杂分配直接上 ConstraintLayout 的 chain。第三公共布局抽出来之前先想清楚它会不会有不同的宽度要求。我吃过这个亏抽了一个带固定宽度的头部布局结果三个页面要用三种宽度最后每个页面都写了一份覆盖等于白抽。公共布局的根节点尽量别写死宽高交给使用方决定。第四FrameLayout 做遮罩层的时候记得加 clickable“true”这个细节漏一次就要被测试提一次单。第五改老布局之前先跑一遍 Layout Inspector 截图改完再截一张对比。人对“层级变少了”这件事没有直观感受但两张树图放一起少掉的那几层清清楚楚也方便给同事解释你为什么这么改。最后说一个后续可以继续挖的方向把本文这六种容器的测量逻辑读到源码层面尤其是 LinearLayout 的 measureHorizontal 里那段权重分配的循环以及 RelativeLayout 的依赖排序实现读完之后再回头看 ConstraintLayout 的约束求解会发现很多设计上的取舍其实是一脉相承的。这条路我自己走了一半收获比看十篇教程都大。