Android 基础补强 B06|读懂 XML 布局:控件尺寸、父容器与 ViewBinding
摘要:从一张传统文章卡片出发,理解尺寸与布局参数、控件更新、资源复用和 ViewBinding,学会定位长标题及横屏布局问题。
标签:Android、XML、ViewBinding、布局、第一行代码
这一篇对应《第一行代码》第 3 版第 4 章,也是 28 天课程 D17 的基础补强。主项目使用 Compose,并不意味着存量 View 代码可以跳过。维护传统页面时,首先要看懂 XML 如何生成对象、父容器如何安排子控件,以及数据变化后谁来更新显示。文中布局与 Kotlin 片段未在现有工程编译,需放入独立练习页面验证。
1. 子控件的尺寸是交给父布局解释的
layout_width和layout_height描述子控件向父容器提出的尺寸要求,match_parent与wrap_content也要结合父容器约束理解。布局参数并不是每种容器都通用:LinearLayout 的权重、ConstraintLayout 的约束规则属于不同的父布局协议,不能看到0dp就一律理解为“不显示”。
例如水平 LinearLayout 中,标题宽度设为零并给权重,可以让它使用分配的剩余空间;若在没有相应规则的容器中机械复制,标题可能真的没有可用宽度。定位时先看父布局类型,再看尺寸、间距和约束,最后看文本自身的行数与截断设置。Android 布局指南
padding位于控件内部,影响内容与控件边界;layout_margin位于控件外部,由父布局布局时考虑。gravity常控制容器内部内容对齐,layout_gravity则要看父容器如何解释子控件位置。这些属性名字相似,但调整错误的一项会出现“怎么改都没反应”。
2. 用最小卡片观察布局变化
下面保存为res/layout/activity_article_practice.xml。字符串资源需在strings.xml定义,bookmark_action可为“收藏”,sample_article_title为示例标题。它只是练习布局,不包含项目主题、窗口边距等完整配置。
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"android:layout_width="match_parent"android:layout_height="match_parent"android:orientation="horizontal"android:gravity="center_vertical"android:padding="16dp"><TextViewandroid:id="@+id/article_title"android:layout_width="0dp"android:layout_height="wrap_content"android:layout_weight="1"android:layout_marginEnd="12dp"android:maxLines="2"android:ellipsize="end"android:text="@string/sample_article_title"android:textSize="18sp"/><Buttonandroid:id="@+id/bookmark_button"android:layout_width="wrap_content"android:layout_height="wrap_content"android:text="@string/bookmark_action"/></LinearLayout>标题使用剩余宽度,按钮按自身需要占位。文字单位使用sp,布局尺寸使用dp;用户放大字体后可用行数和高度需求会改变,不能只在默认字体下看截图。长标题超过两行时截断属于当前练习的产品选择,完整内容可以放在详情页,不应假装所有文章天然都很短。
资源也是结构的一部分。把文案放进字符串资源,才能集中管理翻译与格式;颜色、尺寸和样式需要复用时也应抽取。不要把“减少 XML 行数”当作唯一目标,过早嵌套很多通用布局同样可能增加理解成本。
3. ViewBinding 负责引用,不负责业务状态
模块启用buildFeatures { viewBinding = true }后,布局会生成相应 Binding 类。对于上面的文件,类名为ActivityArticlePracticeBinding,控件 ID 对应可访问字段。Activity 中先 inflate,再把同一个 root 交给setContentView,之后更新它的字段即可。ViewBinding 官方说明
下面放在练习 Activity 的onCreate中,需补齐 AppCompat 或 ComponentActivity 基类和布局资源。onBookmarkClicked代表事件入口,应该交给状态持有者,而不是在此永久维护业务数据。
valbinding=ActivityArticlePracticeBinding.inflate(layoutInflater)setContentView(binding.root)binding.articleTitle.text=article.title binding.bookmarkButton.isSelected=article.isBookmarked binding.bookmarkButton.setOnClickListener{onBookmarkClicked(article.id)}ViewBinding 使控件访问具有生成的类型信息,但不会把普通变量变成可观察状态,也不会自动保存旋转后的收藏。状态仍要从 ViewModel 或 Repository 读取,再由渲染函数同步到控件。若把新布局 inflate 后只更新新 Binding,却仍然显示旧 root,就会出现“赋值执行了,屏幕没变”的对象错位。
Activity 与 Fragment 也不能完全复制同一套成员管理方式。Fragment 的 View 可先于 Fragment 销毁,需要在onDestroyView清理 Binding;B08 将详细解释。不能因为引用有类型,就认为它在任何生命周期时刻都有效。
4. 三个能定位问题的布局实验
第一组把短标题换成六十个字,并开启较大系统字体。预期按钮仍能操作,标题最多两行;如果按钮被挤出,应检查标题是否错误地使用全宽。记录横竖屏分别的现象,不只截最漂亮的一种尺寸。
第二组暂时去掉标题权重,但保留零宽度。预期标题区域不能再获得原来的剩余空间,从而直观理解零宽度必须配合父容器规则。恢复后把外边距改为内边距,观察两控件之间的距离与文字位置是否按预期变化。
第三组故意 inflate 两份布局,一份显示、一份赋值。预期第二份的内容变化不会出现在第一份屏幕上。用对象标识或调试器确认当前 root,随后恢复只持有显示树的实现。这些实验用于理解对象关系,不应作为保留在正式页面的代码。
5. 原创面试问答与追问
问一:为什么同样是 0dp,在不同布局里效果不同?尺寸由父容器规则解释,权重或约束可能会再分配空间。追问:排错先看什么?先看父布局与对应 LayoutParams,而非只改数字。
问二:ViewBinding 能替代状态管理吗?不能,它提供布局控件引用,业务状态与生命周期仍要设计。追问:按钮变色会自动持久化吗?不会,需要把事件写入相应数据来源。
问三:代码更新文字但页面没变化有哪些可能?更新了未显示的 View、后续渲染覆盖、状态未被观察等。追问:怎样缩小范围?确认当前 root、赋值时机与模型来源,再检查渲染顺序。
以上为围绕书本知识原创的自测,不是面试鸭原题摘录。
6. 本专题验收
不看答案重建标题与按钮布局,说明每一个尺寸属性由谁解释;在长标题、大字体与横屏下检查行为;能把 ViewBinding 的引用便利和 ViewModel 的状态职责分开说明。完成这些基础后,再进入 D17 的传统列表与 Compose 互操作,就不会把所有问题都归因于框架不同。