是什么
在 Flutter 里,BottomSheet 是一个 从屏幕底部滑出的面板。常见有两种用法:
Persistent BottomSheet(持久化底部栏)
- 使用
Scaffold.of(context).showBottomSheet()或ScaffoldState.showBottomSheet()创建。 - 一旦显示,会固定在屏幕底部,不会遮罩背景。
- 常用于 持久显示某些操作按钮或输入区域。
- 使用
Modal BottomSheet(模态底部弹窗)
- 使用
showModalBottomSheet()创建。 - 遮罩背景,阻止用户点击弹窗之外区域。
- 适合 临时选择或操作界面。
- 使用
BottomSheet组件本身是一个 抽象类,通常我们用 Scaffold 提供的方法 来实例化它。
常用属性
如果你直接使用 BottomSheet Widget(而不是 showModalBottomSheet),可以配置:
| 属性 | 含义 |
|---|---|
onClosing |
持久化 BottomSheet 关闭时回调 |
builder |
返回 BottomSheet 内容的 Widget |
enableDrag |
是否允许拖拽关闭 |
backgroundColor |
背景颜色 |
elevation |
阴影高度 |
shape |
弹窗形状(圆角等) |
clipBehavior |
裁剪行为 |
animationController |
自定义动画控制器 |
Persistent vs Modal 区别
| 特性 | Persistent | Modal |
|---|---|---|
| 背景遮罩 | ❌ | ✅ |
| 阻止底层交互 | ❌ | ✅ |
| 手动关闭 | 可 | 可 |
| 常用场景 | 底部操作栏、工具栏 | 临时选择界面、弹窗 |
- Persistent BottomSheet → 固定在底部,可一直显示,不阻塞交互
- Modal BottomSheet → 模态弹窗,覆盖底部,阻止背景交互,适合选择/操作
- showModalBottomSheet() 是最常用的方式,底层就是
BottomSheet
案例
import 'package:flutter/material.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
home: Scaffold(body: const BottomSheetDirectDemo()),
);
}
}
class BottomSheetDirectDemo extends StatefulWidget {
const BottomSheetDirectDemo({super.key});
@override
State<BottomSheetDirectDemo> createState() => _BottomSheetDirectDemoState();
}
class _BottomSheetDirectDemoState extends State<BottomSheetDirectDemo>
with SingleTickerProviderStateMixin {
PersistentBottomSheetController? _controller;
late AnimationController _animationController;
String _result = "未选择";
@override
void initState() {
super.initState();
_animationController = AnimationController(
vsync: this,
duration: const Duration(milliseconds: 300),
);
}
@override
void dispose() {
_animationController.dispose();
super.dispose();
}
void _showBottomSheet() {
_controller = Scaffold.of(context).showBottomSheet((context) {
return BottomSheet(
onClosing: () {},
animationController: _animationController,
enableDrag: true,
showDragHandle: true,
backgroundColor: Colors.transparent,
builder: (context) {
return Container(
decoration: BoxDecoration(
color: Colors.white,
borderRadius: BorderRadius.vertical(top: Radius.circular(16)),
boxShadow: const [
BoxShadow(color: Colors.black26, blurRadius: 8),
],
),
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text(
"请选择一个选项",
style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold),
),
const SizedBox(height: 10),
ListView.builder(
shrinkWrap: true,
itemCount: 5,
itemBuilder: (context, index) {
return ListTile(
title: Text("选项 ${index + 1}"),
onTap: () {
setState(() {
_result = "选项 ${index + 1}";
});
// 点击选项时,关闭弹窗
_controller?.close();
},
);
},
),
const SizedBox(height: 10),
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () {
// 关闭弹窗
_controller?.close();
},
child: const Text("取消"),
),
TextButton(
onPressed: () {
// 关闭弹窗
_controller?.close();
},
child: const Text("确认"),
),
],
),
],
),
);
},
);
});
_controller?.closed.then((_) {
debugPrint("BottomSheet 已关闭,选择结果:$_result");
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text("BottomSheet 直接使用 Demo")),
body: Builder(
builder: (context) {
return Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text("选择结果:$_result", style: const TextStyle(fontSize: 18)),
const SizedBox(height: 20),
ElevatedButton(
onPressed: _showBottomSheet,
child: const Text("打开 BottomSheet"),
),
],
),
);
},
),
);
}
}
- 直接使用
BottomSheet,而不是showModalBottomSheet。 - 动画控制:通过
_animationController自定义动画。 - 拖拽关闭:
enableDrag: true,并显示拖拽手柄showDragHandle: true。 - 圆角 + 阴影:通过
Container+BoxDecoration设置。 - 确认/取消按钮:自定义按钮,不会影响 BottomSheet 的关闭逻辑。
- 监听关闭:
_controller?.closed.then(...)可以在关闭时获取状态。
AnimationController
AnimationController 是一个 特殊的 AnimationAnimation<double>。它的常用功能如下:
_controller = AnimationController(
vsync: this, // 避免屏幕不显示时仍然消耗资源
duration: const Duration(seconds: 2), // 动画时长
);
// 启动动画(从 0 → 1)
_controller.forward();
// 倒放动画(从 1 → 0)
_controller.reverse();
// 无限重复(可选是否反向)
_controller.repeat(reverse: true);
// 立即停止
_controller.stop();
在 Flutter 里,BottomSheet 默认就是自带动画的,如果你不给它传入 animationController,它会自动内部创建一个。
BottomSheet本质上是一个 可动画展开/收起的面板。- 如果你 不传
animationController,它会用ScaffoldState内部的_showBottomSheet方法来创建一个AnimationController,并且自己管理它的生命周期。 - 如果你 手动传入
animationController,那么BottomSheet就会用你提供的控制器。此时 生命周期管理也需要你来处理(比如dispose)。