使用布局与部件创建用户界面

使用布局与部件创建用户界面:保留第四版正文机制,以所有者—状态—结果合同、事件轨迹和章专属故障完成可重放验收。

学习目标

  • 能沿“使用布局与部件创建用户界面”的用户事件解释Android组件、状态所有者、线程与销毁边界。
  • 能围绕“掌握 ConstraintLayout 约束布局、常用 UI 部件、输入类型与 IME 操作,能搭建复杂但高效的用户界面。”改出一个可运行结果,并用前后状态而非组件数量验收。
  • 能在旋转、进程重建、拒权、离线或无效输入中选择适用反例,定位首个状态分叉。
  • 能用双视口、大字体、焦点顺序、状态语义和对比度截图独立重放结论,并标明第四版机制与现代targetSdk政策的边界。

为什么排个按钮位置这么讲究?

你写过一个登录界面:账号输入框、密码输入框、登录按钮。在你自己手机上看效果完美。然后你换了一台屏幕大一号的手机——三个控件全挤在左上角,底下大片空白。这就是直接写死坐标(绝对位置)的结果。

Android 的布局系统不让你写"放在 x=200, y=300"——因为不同手机的屏幕大小、分辨率、方向都不一样。它让你写的是关系:"登录按钮放在密码输入框的正下方"、"密码输入框的宽度和账号输入框保持一致"。关系不随屏幕变,所以布局在任何设备上都稳。

布局的核心思想是:不是告诉控件它该在哪,而是告诉控件它该和谁是邻居。本章先用 ConstraintLayout 搭建这种"关系网",然后带你过一遍常用的 UI 部件工具箱。

ConstraintLayout:用"关系"排布界面

什么是约束

就是一条规则,告诉控件:"你的左边跟 A 的右边对齐"、"你的底部黏在父布局的底部"。每个控件至少需要一条水平约束和一条垂直约束——没有约束的控件会被"推到"左上角(坐标 0,0),这就是你常见的"控件全缩在角落里"的原因。

偏差:当你不想要"居中"的时候

把控件左右两边都约束到父布局上,默认它就居中。但如果你想让它在中间偏左 30%,怎么办?

就是控制这个"偏一点"的旋钮。水平偏差 0.3 的意思是"待在左边占 30%、右边占 70% 的位置"。偏差是一个 0 到 1 的小数,0.5 就是正中间。

链:一组控件"手拉手"

你有三个按钮,想让它们等间距一字排开——每个按钮左右互相约束首尾相连,这就形成了一条。链的好处是一组控件可以像绑在一起一样整体移动——你改变链头的位置,全队一起动。

辅助线:Guidelines 和 Barriers

有些约束你不想绑在具体的控件上——比如"所有按钮的左边都对齐屏幕的 30% 位置"。这时候你可以在 ConstraintLayout 里放一条——一条看不见的垂直/水平参考线,控件约束绑在这条线上而不是某个具体控件上。

则更聪明——它会自动对齐"参考控件组里最宽/最高的那个"。如果你有几个长度不同的输入框,Barrier 能让下面的按钮始终对齐最长的那个输入框的右边。

分步1 / 4

① 识别嵌套 LinearLayout 结构

找出布局中多层嵌套的 LinearLayout——每一层嵌套都会触发额外的 measure/layout 遍历,3 层嵌套意味着每个控件被 measure 至少 3 次。标记最外层的根布局和每个嵌套层的方向(vertical/horizontal)及权重(layout_weight)。

从零开始搭建一个表单界面

猜一猜:只拖拽控件不设约束会发生什么?每个控件的实际位置由什么决定?

下面用 Stepper 分步展示如何用 ConstraintLayout 搭一个嫌疑人录入表单(姓名、日期、是否已结案复选框、保存按钮):

  1. 添加 EditText 输入框:设置水平约束(左右绑父布局)、垂直约束(顶部绑父布局),宽度设为 0dp (match_constraint)
  2. 添加第二个 EditText:垂直约束顶部绑第一个输入框底部,水平同上——这就是"关系"而非"位置"
  3. 添加 CheckBox 和保存 Button:用水平链让它们在父布局里等分宽度
  4. 检查:预览不同屏幕尺寸——所有控件自动适配,不会堆在一起

代码逐段拆解

ConstraintLayout 完整示例:嫌疑人录入表单

<androidx.constraintlayout.widget.ConstraintLayout
    xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:padding="16dp">
 
 
</androidx.constraintlayout.widget.ConstraintLayout>

关键属性解读

  • layout_width="0dp" 在 ConstraintLayout 里不是 0 像素——而是 "让约束来决定宽度"(match_constraint)。前提是左右都有约束
  • android:inputType 控制键盘类型和输入行为:textMultiLine 允许多行、textCapWords 自动大写每个单词首字母
  • android:imeOptions="actionDone" 把键盘上的回车键变成"完成"按钮,比默认的换行符用户友好得多

常用 UI 部件速查表

部件典型用途关键属性
TextView显示文字text, textSize, textColor
EditText用户输入hint, inputType, imeOptions
Button触发操作text, onClick
ImageView显示图片src, scaleType
CheckBox多选开关text, checked
RadioButton单选(配 RadioGroup)text, checked
Switch开关控件text, checked
SeekBar连续滑动取值max, progress

ScrollView:内容超出屏幕时

 
视图层级:ViewGroup 套 View 的一棵树容器(ViewGroup)能装子节点并负责测量/布局;部件(View)是可见的叶子控件ConstraintLayout根容器:测量/布局所有子节点TextView标题文字(叶子部件)LinearLayout子容器:把内部控件竖向排开EditText输入框(叶子部件)CheckBox复选框(叶子部件)ImageView图片(叶子部件)Button按钮(叶子部件)实心点 = ViewGroup(容器,装子 View,负责测量/布局)空心方块 = View(叶子部件,可见 UI,不再装子节点)约束:告诉控件「和谁的哪条边对齐」parent@id/crime_titlesave_buttonapp:layout_constraintTop_toBottomOf=@id/crime_titleapp:layout_constraintStart_toStartOf=parentapp:layout_constraintEnd_toEndOf=parent
每个界面都是一棵树:ViewGroup(容器)负责测量/布局自己的子节点,View(叶子部件)只管把自己画出来。在 ConstraintLayout 里,子控件靠 app:layout_constraintStart_toStartOf / Top_toBottomOf 这类约束钉到父布局或兄弟控件的某条边上——不写坐标,写「和谁是邻居」。

ScrollViewConstraintLayoutLinearLayout 这样能装子节点的叫 ViewGroup(容器);像 TextViewButtonImageView 这样只负责把自己画出来、不再装子节点的叫 View(叶子部件)。整个界面就是一棵 ViewGroup 套 View 的树——容器负责测量和布局它的子节点,叶子部件只管显示。约束(app:layout_constraint...)则是在这棵树里告诉某个控件「该和父布局或兄弟控件的哪条边对齐」。

Tools 命名空间:布局预览神器

 

tools:text 只在 Android Studio 预览窗口生效,运行时会被真实数据覆盖。同样的 tools:src 可以用来预览图片,tools:listitem 可以预览 RecyclerView 的条目——编译后全部消失,零性能开销。

容易踩的坑

小结

  • ConstraintLayout 通过"约束关系"排控件——写关系不写坐标,天然适配各种屏幕尺寸
  • layout_width="0dp" + 左右约束 = match_constraint(让约束决定宽度),是 ConstraintLayout 最高效的宽度策略
  • 部件属性决定交互体验:inputType 控制键盘类型、imeOptions 控制键盘上的回车动作
  • ScrollView 包裹超出屏幕的内容;Tools 命名空间提供零成本的预览数据

练习

问题 1:“第10章 Creating User Interfaces with Layouts and Widgets”覆盖哪些正式节点和项目主线?

问题 2:怎样建立本页最小可执行实验?

问题 3:为什么只在正常点击路径运行不能证明完成?

问题 4:怎样设计能推翻当前实现的反例?

问题 5:从第4版语境迁移到现代目标SDK时如何控制变量?

问题 6:本页达到独立交接标准需要什么?

名词解释

名词解释

本章出现的专业名词,用大白话再讲一遍。

约束

告诉一个控件"你应该在哪"的规则。在 ConstraintLayout 里,你描述的是控件之间的关系("A 的左边跟 B 的右边对齐")而非绝对坐标。

偏差

控件在两个对向约束之间不是居中而是偏向某一侧的程度。值 0 到 1:0.5=居中,0.2=偏左,0.8=偏右。就像天平两边的砝码重量比。

两个以上控件通过双向约束串联成一条"锁链"。链条作为整体可以设置排列方式:等分空间(spread)、等分但去头尾(spread_inside)、挤在一起(packed)。

Guideline

一条只存在于设计阶段不可见的辅助线,可以按百分比或固定 dp 定位。其他控件把约束绑在它身上——就像画格子时用的隐形尺子。

Barrier

一条动态辅助线——它会自动跑到"参考组里最宽或最高控件的边缘"。特别适合处理一排长短不一的输入框下面的按钮对齐问题。

“使用布局与部件创建用户界面”不使用未获授权的纸书正文;InformIT出版信息授权电子版完整目录只用于确认第四版32章、269个正式目录节点和时代语境,第四版官方勘误用于识别工具链变更。下列中文解释、图示、交互、代码与练习均为独立教学重写,平台行为再以Android Developers的一手文档复核。

为什么“使用布局与部件创建用户界面”必须回到可观察状态

“使用布局与部件创建用户界面”的学习结果不是记住类名,而是能预测“掌握 ConstraintLayout 约束布局、常用 UI 部件、输入类型与 IME 操作,能搭建复杂但高效的用户界面。”在一次输入、一次重建和一次失败中的不同状态,并指出哪条Android合同产生差异。

第四版机制逐项深读

10. Creating User Interfaces with Layouts and Widgets

在“使用布局与部件创建用户界面”中,验证“10. Creating User Interfaces with Layouts and Widgets”固定内容,只改变屏宽、字体缩放或方向之一,记录每个View的bounds和首个溢出节点。

Introducing ConstraintLayout

在“使用布局与部件创建用户界面”中,分析“Introducing ConstraintLayout”先区分父约束、子测量和绘制边界,再检查触控目标、基线、RTL与系统栏插入量。

Introducing the Graphical Layout Editor

在“使用布局与部件创建用户界面”中,“Introducing the Graphical Layout Editor”的视觉正确性必须与语义树一致;看得见但读屏无名称、焦点不可达或点击区过小仍判失败。

Using ConstraintLayout

在“使用布局与部件创建用户界面”中,分析“Using ConstraintLayout”先区分父约束、子测量和绘制边界,再检查触控目标、基线、RTL与系统栏插入量。

More on Layout Attributes

在“使用布局与部件创建用户界面”中,分析“More on Layout Attributes”先区分父约束、子测量和绘制边界,再检查触控目标、基线、RTL与系统栏插入量。

For the More Curious: Margins vs Padding

在“使用布局与部件创建用户界面”中,“For the More Curious: Margins vs Padding”把约束、测量与布局落实为View树几何;用小屏、长文本与大字体验证,不能用单一模拟器截图证明适配。

For the More Curious: New Developments in ConstraintLayout

在“使用布局与部件创建用户界面”中,分析“For the More Curious: New Developments in ConstraintLayout”先区分父约束、子测量和绘制边界,再检查触控目标、基线、RTL与系统栏插入量。

Challenge: Formatting the Date

在“使用布局与部件创建用户界面”中,“Challenge: Formatting the Date”让资源匹配器根据locale与配置选择候选;字符串参数、复数、日期、RTL和回退链必须由目标locale实测,不能拼接英语语序。

“使用布局与部件创建用户界面”验收回顾

“使用布局与部件创建用户界面”只有在双视口、大字体、焦点顺序、状态语义和对比度截图能够从相同基线再次得到相同断言时才通过;第四版机制与现代平台政策分别记录,不用新API名称掩盖旧行为。

← 上一页:使用RecyclerView显示列表 · 下一页:数据库与Room库 →

章专属可重放状态实验

先预测“切换小屏、横屏、长文本、大字体与 RTL”发生后,View 树、ConstraintLayout 求解器与资源匹配器应怎样改变约束、尺寸、边距、padding、文字和焦点顺序;再操作三个实验。第四版示例与当前 Android 政策分别记录,实验不把新 API 名称倒填为原书内容。

实验一:所有者—状态—结果合同

选择任一正式目录节点和正常/边界场景,检查它是否真的进入本章状态合同。目录标题只有同时出现在解释、可视状态和交付证据中才算覆盖。

Owner · state · observable result

使用布局与部件创建用户界面:状态合同

把 ConstraintLayout 约束、测量、布局与资源限定符落到多配置几何

验证场景

第四版正式目录节点

layouts-widgets · 正常任务

10. Creating User Interfaces with Layouts and Widgets固定 SDK、设备配置和初始状态,触发“切换小屏、横屏、长文本、大字体与 RTL”

状态所有者View 树、ConstraintLayout 求解器与资源匹配器
受控状态约束、尺寸、边距、padding、文字和焦点顺序
触发事件切换小屏、横屏、长文本、大字体与 RTL

冻结入口:10. Creating User Interfaces with Layouts and Widgets

记录View 树、ConstraintLayout 求解器与资源匹配器的初始约束、尺寸、边距、padding、文字和焦点顺序

观察:配置矩阵、Layout Inspector 几何、截图与可达性断言中的“10. Creating User Interfaces with Layouts and Widgets”轨迹

预期:由View 树、ConstraintLayout 求解器与资源匹配器提交约束、尺寸、边距、padding、文字和焦点顺序,并持续满足“所有目标配置中信息不截断、控件不重叠且主要动作可达”

实验二:事件与生命周期轨迹

沿五次转换逐步执行“切换小屏、横屏、长文本、大字体与 RTL”。每一步只允许View 树、ConstraintLayout 求解器与资源匹配器按职责提交状态,并持续核对“所有目标配置中信息不截断、控件不重叠且主要动作可达”。

Deterministic event replay

使用布局与部件创建用户界面:事件轨迹

选择一次状态转换1 / 5

不变量:所有目标配置中信息不截断、控件不重叠且主要动作可达

交付证据:配置矩阵、Layout Inspector 几何、截图与可达性断言

实验三:章专属反例与同输入恢复

注入“只在单一 Pixel 模拟器检查,德语大字体下按钮被挤出屏幕”,保存第一个偏离点;撤销后以完全相同的 SDK、设备状态和用户事件重放。只有配置矩阵、Layout Inspector 几何、截图与可达性断言一起恢复才算修复。

Fault · cancel · restore

使用布局与部件创建用户界面:反例与恢复

故障:只在单一 Pixel 模拟器检查,德语大字体下按钮被挤出屏幕

1. 冻结输入一致

第 1 次使用相同 SDK、设备配置、初始状态与用户事件

2. 注入边界一致

保持正常输入不变,仅注入“只在单一 Pixel 模拟器检查,德语大字体下按钮被挤出屏幕”

3. 检查所有者一致

所有目标配置中信息不截断、控件不重叠且主要动作可达

4. 核对结果一致

配置矩阵、Layout Inspector 几何、截图与可达性断言

讨论

评论区加载中…