Skip to content

页面、控件与布局 ​

先搭页面骨架 ​

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 开发记录'),
                    ),
                  ),
                ),
              ],
            ),
          ),
        ),
      ),
    );
  }
}

这里 Expanded 让列表占据搜索栏以下的剩余高度;maxWidth 限制宽屏上的阅读宽度。更复杂的双栏布局可在 LayoutBuilder 中按可用约束选择结构,断点由内容是否放得下决定。

主题与资源 ​

颜色和文字样式优先从 Theme.of(context) 获取,例如 colorScheme.primary 和 textTheme.titleMedium,便于同时适配明暗主题。

把图片文件放到 assets/images/,并在现有 pubspec.yaml 的 flutter: 节点中增加资源配置:

yaml
flutter:
  uses-material-design: true
  assets:
    - assets/images/

运行 flutter pub get 后,可在组件树中使用 Image.asset('assets/images/notes.png')。路径、大小写与文件名必须一致;子目录需要按资源规则另行声明。字体同样要在 pubspec.yaml 中注册,再通过主题使用。参见 官方资源指南。

练习与验收 ​

把一条标题改成很长的中文,再测试窄屏、横屏、较大的系统字体和键盘弹出场景。列表应可滚动,搜索栏不被挤出可见区域,正文在宽屏上保持合适宽度。

持续记录,持续理解。