外观
页面、控件与布局
先搭页面骨架
MaterialApp 提供导航、主题等应用级配置;Scaffold 组织页面上的应用栏、正文、抽屉和底部区域;SafeArea 让内容避开系统侵入区域。普通业务页面通常放在应用级 MaterialApp 之下,不必每页再包一个。
| 需求 | 常用组件 | 注意点 |
|---|---|---|
| 文本与图片 | Text、Image、Icon | 长文本溢出、图片尺寸和语义说明 |
| 操作与输入 | FilledButton、TextField、Form | 禁用态、焦点、校验和控制器释放 |
| 间距与装饰 | Padding、SizedBox、DecoratedBox | 区分布局尺寸与外观装饰 |
| 横纵排列 | Row、Column、Expanded、Flexible | 主轴约束是否有界 |
| 自动换行与叠放 | Wrap、Stack、Positioned | 换行方向、定位与点击区域 |
| 滚动内容 | ListView.builder、GridView.builder、CustomScrollView | 长列表按需构建,混合滚动使用 Sliver |
| 页面导航栏 | AppBar、NavigationBar、TabBar | 选中项、页面状态与返回行为 |
理解约束传递
父节点先给出可用尺寸范围,子节点在范围内确定尺寸,再由父节点安排位置。width: 100 是参与布局的输入,最终尺寸仍受父级约束影响。完整示例参见 官方约束指南。
先问三个问题:当前节点从父级拿到什么约束?它向子级传什么约束?子级的尺寸如何影响父级?这比连续添加 Container 更容易找到原因。
常见布局问题
| 现象 | 原因线索 | 处理方式 |
|---|---|---|
Row 出现黄黑溢出条 | 文本按自身宽度展开,总宽度超过父级 | 给文本包 Expanded,再设置换行或省略 |
Column 内列表报无限高度 | 列表需要视口,父级未给有限高度 | 在有界的 Column 内用 Expanded 包住列表 |
滚动区域里的 Expanded 报错 | 主轴可用空间没有上限,无法分配剩余空间 | 调整结构或明确高度,避免无限主轴上的 Flex 扩展 |
| 内外两层滚动手势不自然 | 同方向嵌套滚动各自竞争视口 | 优先合并为一个 CustomScrollView |
| 组件固定宽度不生效 | 父级施加了紧约束 | 通过 Align 等组件调整传给子级的约束 |
shrinkWrap: true 会改变滚动组件的尺寸计算方式,也有额外开销;先判断是否真的需要嵌套滚动,不要把它作为所有高度错误的通用修复。
实例:搜索栏与笔记列表
在 首个应用 中,把 home 改为 const NotesPage(),将下面的类追加到同一文件。搜索栏先用于展示,过滤逻辑见 状态管理。
dart
class NotesPage extends StatelessWidget {
const NotesPage({super.key});
static const titles = ['布局约束', '状态管理', '路由导航', '网络请求'];
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('个人笔记')),
body: SafeArea(
child: Center(
child: ConstrainedBox(
constraints: const BoxConstraints(maxWidth: 840),
child: Column(
children: [
const Padding(
padding: EdgeInsets.all(16),
child: TextField(
decoration: InputDecoration(
labelText: '搜索笔记',
prefixIcon: Icon(Icons.search),
border: OutlineInputBorder(),
),
),
),
Expanded(
child: ListView.builder(
itemCount: titles.length,
itemBuilder: (context, index) => ListTile(
leading: const Icon(Icons.article_outlined),
title: Text(titles[index]),
subtitle: const Text('Flutter 开发记录'),
),
),
),
],
),
),
),
),
);
}
}1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
这里 Expanded 让列表占据搜索栏以下的剩余高度;maxWidth 限制宽屏上的阅读宽度。更复杂的双栏布局可在 LayoutBuilder 中按可用约束选择结构,断点由内容是否放得下决定。
主题与资源
颜色和文字样式优先从 Theme.of(context) 获取,例如 colorScheme.primary 和 textTheme.titleMedium,便于同时适配明暗主题。
把图片文件放到 assets/images/,并在现有 pubspec.yaml 的 flutter: 节点中增加资源配置:
yaml
flutter:
uses-material-design: true
assets:
- assets/images/1
2
3
4
2
3
4
运行 flutter pub get 后,可在组件树中使用 Image.asset('assets/images/notes.png')。路径、大小写与文件名必须一致;子目录需要按资源规则另行声明。字体同样要在 pubspec.yaml 中注册,再通过主题使用。参见 官方资源指南。
练习与验收
把一条标题改成很长的中文,再测试窄屏、横屏、较大的系统字体和键盘弹出场景。列表应可滚动,搜索栏不被挤出可见区域,正文在宽屏上保持合适宽度。