Scaffold
Scaffold 是 Material 应用的页面骨架,提供了 AppBar、Drawer、BottomNavigationBar、FAB 等槽位。
构造函数
Scaffold
dart
Scaffold({
Key? key, // 组件标识
PreferredSizeWidget? appBar, // 顶部应用栏
Widget? body, // 页面主体内容
Widget? floatingActionButton, // 浮动操作按钮
FloatingActionButtonLocation? floatingActionButtonLocation, // FAB 位置
FloatingActionButtonAnimator? floatingActionButtonAnimator, // FAB 动画
List<Widget>? persistentFooterButtons, // 底部固定按钮组
Widget? drawer, // 左侧抽屉菜单
DrawerCallback? onDrawerChanged, // 抽屉开关回调
Widget? endDrawer, // 右侧抽屉菜单
DrawerCallback? onEndDrawerChanged, // 右侧抽屉开关回调
Widget? bottomNavigationBar, // 底部导航栏
Widget? bottomSheet, // 底部持久化 Sheet
Color? backgroundColor, // 背景色
bool? resizeToAvoidBottomInset, // 是否避开键盘(默认 true)
bool primary = true, // 是否将 body 延伸到状态栏
DragStartBehavior drawerDragStartBehavior = DragStartBehavior.start, // 抽屉拖拽行为
double? drawerEdgeDragWidth, // 抽屉边缘拖拽区域宽度
bool drawerEnableOpenDragGesture = true, // 左抽屉是否可拖拽打开
bool endDrawerEnableOpenDragGesture = true, // 右抽屉是否可拖拽打开
String? restorationId, // 状态恢复 ID
})AppBar
dart
AppBar({
Key? key, // 组件标识
Widget? leading, // 左侧组件(通常为返回按钮)
bool automaticallyImplyLeading = true, // 是否自动添加 leading(有 Drawer 时自动加菜单图标)
Widget? title, // 标题组件
List<Widget>? actions, // 右侧操作按钮组
Widget? flexibleSpace, // 弹性空间(可实现渐变 AppBar)
PreferredSizeWidget? bottom, // 底部组件(通常为 TabBar)
double? elevation, // 阴影高度
Color? shadowColor, // 阴影颜色
ShapeBorder? shape, // 形状(圆角等)
Color? backgroundColor, // 背景色
Color? foregroundColor, // 前景色(标题、图标颜色)
IconThemeData? iconTheme, // 图标主题
double? iconThemeData, // 图标大小
TextStyle? toolbarTextStyle, // 工具栏文字样式
TextStyle? titleTextStyle, // 标题文字样式
bool centerTitle, // 标题是否居中(iOS 默认 true,Android 默认 false)
double titleSpacing = NavigationToolbar.kMiddleSpacing, // 标题间距
double toolbarHeight = kToolbarHeight, // 工具栏高度(默认 56)
double? leadingWidth, // leading 区域宽度
EdgeInsetsGeometry? toolbarOpacityCause, // 透明度
bool primary = true, // 是否留出状态栏空间
})Drawer
dart
Drawer({
Key? key, // 组件标识
double? elevation, // 阴影高度(默认 16)
Widget? child, // 抽屉内容
Color? backgroundColor, // 背景色
String? semanticLabel, // 语义标签
})属性速查
Scaffold 属性速查
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
appBar | PreferredSizeWidget? | null | 顶部应用栏 |
body | Widget? | null | 页面主体 |
floatingActionButton | Widget? | null | 浮动操作按钮 |
floatingActionButtonLocation | FloatingActionButtonLocation? | null | FAB 位置 |
drawer | Widget? | null | 左侧抽屉 |
endDrawer | Widget? | null | 右侧抽屉 |
bottomNavigationBar | Widget? | null | 底部导航栏 |
bottomSheet | Widget? | null | 底部持久 Sheet |
backgroundColor | Color? | 白色 | 背景色 |
resizeToAvoidBottomInset | bool? | true | 是否避开键盘 |
AppBar 属性速查
| 属性 | 类型 | 默认值 | 说明 |
|---|---|---|---|
leading | Widget? | 自动 | 左侧组件(有 Drawer 时为菜单图标,有路由时为返回箭头) |
title | Widget? | null | 标题 |
actions | List<Widget>? | null | 右侧操作按钮列表 |
bottom | PreferredSizeWidget? | null | 底部组件(TabBar) |
elevation | double? | 4 | 阴影高度,0 为无阴影 |
backgroundColor | Color? | 主题色 | 背景色 |
centerTitle | bool | 平台相关 | 标题是否居中 |
toolbarHeight | double | 56 | 工具栏高度 |
flexibleSpace | Widget? | null | 弹性空间(渐变背景等) |
FAB 位置速查
| 值 | 位置 |
|---|---|
FloatingActionButtonLocation.endFloat | 右下角(默认) |
FloatingActionButtonLocation.centerFloat | 底部居中 |
FloatingActionButtonLocation.startFloat | 左下角 |
FloatingActionButtonLocation.endDocked | 右下角嵌入 BottomAppBar |
FloatingActionButtonLocation.centerDocked | 底部居中嵌入 BottomAppBar |
快速示例
基本页面结构
dart
import 'package:flutter/material.dart';
class ScaffoldBasicExample extends StatelessWidget {
const ScaffoldBasicExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('页面标题'),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () {},
),
// ─── ★ 右侧操作按钮 ──────────────
actions: [
IconButton(icon: Icon(Icons.search), onPressed: () {}),
IconButton(icon: Icon(Icons.more_vert), onPressed: () {}),
],
// ─── ☆ 右侧操作按钮 ──────────────
),
body: Center(child: Text('内容区')),
// ─── ★ FAB ──────────────
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: Icon(Icons.add),
),
// ─── ☆ FAB ──────────────
// ─── ★ 底部导航 ──────────────
bottomNavigationBar: BottomNavigationBar(
items: [
BottomNavigationBarItem(icon: Icon(Icons.home), label: '首页'),
BottomNavigationBarItem(icon: Icon(Icons.person), label: '我的'),
],
),
// ─── ☆ 底部导航 ──────────────
);
}
}带 Drawer 的页面
dart
import 'package:flutter/material.dart';
class ScaffoldDrawerExample extends StatelessWidget {
const ScaffoldDrawerExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('带抽屉的页面')),
// ─── ★ Drawer ──────────────
drawer: Drawer(
child: ListView(
padding: EdgeInsets.zero,
children: [
DrawerHeader(
decoration: BoxDecoration(color: Colors.blue),
child: Text('用户名', style: TextStyle(color: Colors.white, fontSize: 24)),
),
ListTile(leading: Icon(Icons.home), title: Text('首页'), onTap: () {}),
ListTile(leading: Icon(Icons.settings), title: Text('设置'), onTap: () {}),
Divider(),
ListTile(leading: Icon(Icons.logout), title: Text('退出'), onTap: () {}),
],
),
),
// ─── ☆ Drawer ──────────────
body: Center(child: Text('内容')),
)带 TabBar 的 AppBar
dart
DefaultTabController(
length: 3,
child: Scaffold(
appBar: AppBar(
title: Text('Tab 示例'),
bottom: TabBar(
tabs: [
Tab(icon: Icon(Icons.phone), text: '电话'),
Tab(icon: Icon(Icons.favorite), text: '收藏'),
Tab(icon: Icon(Icons.person), text: '通讯录'),
],
),
),
body: TabBarView(
children: [
Center(child: Text('电话')),
Center(child: Text('收藏')),
Center(child: Text('通讯录')),
],
),
),
)渐变 AppBar
dart
AppBar(
title: Text('渐变 AppBar'),
flexibleSpace: Container(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
begin: Alignment.topLeft,
end: Alignment.bottomRight,
),
),
),
)手动控制 Drawer
dart
// 获取 ScaffoldState 控制 Drawer
final scaffoldKey = GlobalKey<ScaffoldState>();
Scaffold(
key: scaffoldKey,
appBar: AppBar(
title: Text('手动控制'),
leading: IconButton(
icon: Icon(Icons.menu),
onPressed: () => scaffoldKey.currentState!.openDrawer(),
),
),
drawer: Drawer(...),
)
// 关闭抽屉
Navigator.pop(context);ScaffoldMessenger
ScaffoldMessenger 是 SnackBar 的管理器,负责在 Scaffold 底部显示/隐藏 SnackBar。它解决了旧 API Scaffold.of(context).showSnackBar() 的 context 问题。
为什么用 ScaffoldMessenger 而不是 Scaffold.of
| 方式 | 问题 |
|---|---|
Scaffold.of(context).showSnackBar() | body 内部的 context 找不到外层 Scaffold,需要 Builder 包裹 |
ScaffoldMessenger.of(context).showSnackBar() | ✅ 直接使用,无需 Builder,推荐 |
ScaffoldMessengerState 方法速查
| 方法 | 说明 |
|---|---|
showSnackBar(SnackBar) | 显示 SnackBar |
hideCurrentSnackBar([reason]) | 隐藏当前 SnackBar |
removeCurrentSnackBar([reason]) | 移除当前 SnackBar(无动画) |
clearSnackBars() | 清空所有排队的 SnackBar |
SnackBarClosedReason 速查
| 值 | 说明 |
|---|---|
SnackBarClosedReason.action | 用户点击了 action 按钮 |
SnackBarClosedReason.dismiss | 用户手动关闭(滑掉或点击外部) |
SnackBarClosedReason.hide | 调用 hideCurrentSnackBar |
SnackBarClosedReason.remove | 调用 removeCurrentSnackBar |
SnackBarClosedReason.timeout | 显示时长结束自动关闭 |
全局配置 ScaffoldMessenger
dart
// 在 MaterialApp 中配置,所有页面共享同一个 ScaffoldMessenger
MaterialApp(
scaffoldMessengerKey: scaffoldMessengerKey, // 全局 key
home: HomePage(),
)
// 在任何地方使用
final scaffoldMessengerKey = GlobalKey<ScaffoldMessengerState>();
scaffoldMessengerKey.currentState?.showSnackBar(
SnackBar(content: Text('全局通知')),
);ScaffoldMessenger 示例
dart
// 1. 基本 SnackBar
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('操作成功')),
);
// 2. 带操作按钮
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('已删除 1 项'),
duration: Duration(seconds: 5),
action: SnackBarAction(
label: '撤销',
onPressed: () {
// 撤销操作
},
),
),
);
// 3. 浮动样式
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('保存成功'),
behavior: SnackBarBehavior.floating,
margin: EdgeInsets.fromLTRB(16, 0, 16, 16),
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
),
);
// 4. 监听 SnackBar 关闭原因
final scaffoldMessenger = ScaffoldMessenger.of(context);
final snackBar = SnackBar(content: Text('可撤销'));
scaffoldMessenger.showSnackBar(snackBar).closed.then((reason) {
if (reason == SnackBarClosedReason.action) {
print('用户点击了操作按钮');
} else if (reason == SnackBarClosedReason.timeout) {
print('自动超时关闭');
}
});
// 5. 避免排队:先清除再显示
ScaffoldMessenger.of(context).clearSnackBars();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('最新消息')),
);
// 6. 自定义 SnackBar(带进度条)
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
duration: Duration(seconds: 10),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('上传中...'),
SizedBox(height: 8),
LinearProgressIndicator(),
],
),
),
);常见错误
1. Scaffold 内嵌 Scaffold
dart
// ❌ 错误:Scaffold 内嵌 Scaffold 会导致多个 AppBar 和布局混乱
Scaffold(
body: Scaffold( // 不要嵌套!
appBar: AppBar(...),
body: ...,
),
)
// ✅ 修复:外层用 Scaffold,内层只用内容
Scaffold(
appBar: AppBar(...),
body: YourContent(),
)2. 键盘弹出时布局溢出
dart
// ✅ 修复:设置 resizeToAvoidBottomInset
Scaffold(
resizeToAvoidBottomInset: true, // 默认 true,确保未设为 false
body: SingleChildScrollView( // 内容可滚动
child: Column(children: [...]),
),
)3. body 中使用 SnackBar 的 context 问题
dart
// ❌ 旧方式:body 中调用 Scaffold.of(context).showSnackBar() 会报错
Scaffold(
body: Builder(
builder: (context) {
Scaffold.of(context).showSnackBar(...); // 可能报错
},
),
)
// ✅ 推荐方式:使用 ScaffoldMessenger
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('操作成功')),
);