3.2 按钮
📚 章节概览
按钮是用户交互的核心组件。Flutter 的 Material 库提供了多种按钮组件,本章节将学习:
- ElevatedButton - 漂浮按钮(带阴影和背景)
- TextButton - 文本按钮(透明背景)
- OutlinedButton - 边框按钮
- IconButton - 图标按钮
- 带图标的按钮 -
.icon构造函数 - 按钮样式自定义 -
ButtonStyle和styleFrom - 按钮状态管理 - 启用、禁用、加载中
🎯 核心知识点
Material 按钮的共同特点
所有 Material 库中的按钮都有以下共同点:
- 水波动画:点击时会出现水波扩散的涟漪动画
onPressed回调 :设置点击事件,为null时按钮禁用- 继承自
RawMaterialButton:大多数属性都相同
dart
// 启用状态
ElevatedButton(
onPressed: () {
print('按钮被点击');
},
child: Text('启用'),
)
// 禁用状态
ElevatedButton(
onPressed: null, // null = 禁用
child: Text('禁用'),
)
1️⃣ ElevatedButton(漂浮按钮)
特点
- 默认外观:带阴影和背景色(主题色)
- 交互效果:按下时阴影变大
- 适用场景:主要操作、强调的按钮
基本用法
dart
ElevatedButton(
onPressed: () {
print('ElevatedButton 被点击');
},
child: Text('Normal'),
)
自定义样式
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.red, // 背景色
foregroundColor: Colors.white, // 前景色(文字/图标)
elevation: 5, // 阴影高度
shadowColor: Colors.black, // 阴影颜色
padding: EdgeInsets.symmetric(
horizontal: 24,
vertical: 12,
),
shape: RoundedRectangleBorder( // 形状
borderRadius: BorderRadius.circular(8),
),
),
child: Text('自定义样式'),
)
2️⃣ TextButton(文本按钮)
特点
- 默认外观:透明背景,无阴影
- 交互效果:按下时出现背景色
- 适用场景:次要操作、对话框按钮
基本用法
dart
TextButton(
onPressed: () {
print('TextButton 被点击');
},
child: Text('Normal'),
)
常见用例:对话框
dart
AlertDialog(
title: Text('提示'),
content: Text('确定要删除吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
TextButton(
onPressed: () {
// 执行删除
Navigator.pop(context);
},
child: Text('确定', style: TextStyle(color: Colors.red)),
),
],
)
3️⃣ OutlinedButton(边框按钮)
特点
- 默认外观:带边框,透明背景,无阴影
- 交互效果:按下时边框变亮,出现背景和阴影
- 适用场景:中等强调的操作
基本用法
dart
OutlinedButton(
onPressed: () {
print('OutlinedButton 被点击');
},
child: Text('Normal'),
)
自定义边框
dart
OutlinedButton(
onPressed: () {},
style: OutlinedButton.styleFrom(
side: BorderSide(
color: Colors.red, // 边框颜色
width: 2, // 边框宽度
),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(20),
),
),
child: Text('自定义边框'),
)
4️⃣ IconButton(图标按钮)
特点
- 默认外观:只包含图标,无背景
- 交互效果:点击时出现圆形背景
- 适用场景:工具栏、AppBar、紧凑布局
基本用法
dart
IconButton(
icon: Icon(Icons.thumb_up),
onPressed: () {
print('IconButton 被点击');
},
tooltip: '点赞', // 长按显示的提示
)
实用示例:收藏功能
dart
class FavoriteButton extends StatefulWidget {
@override
_FavoriteButtonState createState() => _FavoriteButtonState();
}
class _FavoriteButtonState extends State<FavoriteButton> {
bool _isFavorite = false;
@override
Widget build(BuildContext context) {
return IconButton(
icon: Icon(
_isFavorite ? Icons.favorite : Icons.favorite_border,
),
color: _isFavorite ? Colors.red : Colors.grey,
onPressed: () {
setState(() {
_isFavorite = !_isFavorite;
});
},
tooltip: _isFavorite ? '取消收藏' : '收藏',
);
}
}
5️⃣ 带图标的按钮
.icon 构造函数
所有主要按钮类型都提供了 .icon 构造函数,可以轻松创建带图标的按钮。
dart
// ElevatedButton.icon
ElevatedButton.icon(
icon: Icon(Icons.send),
label: Text('发送'),
onPressed: () {},
)
// TextButton.icon
TextButton.icon(
icon: Icon(Icons.info),
label: Text('详情'),
onPressed: () {},
)
// OutlinedButton.icon
OutlinedButton.icon(
icon: Icon(Icons.add),
label: Text('添加'),
onPressed: () {},
)
图标位置
默认情况下,图标在左侧。如果需要图标在右侧,可以自定义布局:
dart
ElevatedButton(
onPressed: () {},
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text('下载'),
SizedBox(width: 8),
Icon(Icons.download),
],
),
)
🎨 按钮样式系统
ButtonStyle vs styleFrom
Flutter 提供了两种方式自定义按钮样式:
方法1:styleFrom(推荐)
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 5,
padding: EdgeInsets.all(16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: Text('按钮'),
)
方法2:ButtonStyle(更灵活)
dart
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.all(Colors.blue),
foregroundColor: MaterialStateProperty.all(Colors.white),
elevation: MaterialStateProperty.all(5),
padding: MaterialStateProperty.all(
EdgeInsets.all(16),
),
shape: MaterialStateProperty.all(
RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
child: Text('按钮'),
)
MaterialStateProperty
ButtonStyle 使用 MaterialStateProperty 来根据按钮状态设置不同的样式。
dart
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: MaterialStateProperty.resolveWith<Color>(
(Set<MaterialState> states) {
if (states.contains(MaterialState.pressed)) {
return Colors.red; // 按下时红色
}
if (states.contains(MaterialState.hovered)) {
return Colors.orange; // 悬停时橙色
}
if (states.contains(MaterialState.disabled)) {
return Colors.grey; // 禁用时灰色
}
return Colors.blue; // 默认蓝色
},
),
),
child: Text('状态按钮'),
)
常见状态
| 状态 | 说明 |
|---|---|
MaterialState.pressed |
按下状态 |
MaterialState.hovered |
鼠标悬停(Web/Desktop) |
MaterialState.focused |
获得焦点 |
MaterialState.disabled |
禁用状态 |
MaterialState.selected |
选中状态 |
📐 按钮尺寸控制
方法1:padding
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
padding: EdgeInsets.symmetric(
horizontal: 32,
vertical: 20,
),
),
child: Text('大按钮'),
)
方法2:minimumSize
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
minimumSize: Size(200, 50), // 最小宽200,高50
),
child: Text('固定尺寸'),
)
方法3:外层包裹 SizedBox
dart
// 全宽按钮
SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {},
child: Text('全宽按钮'),
),
)
// 固定宽度按钮
SizedBox(
width: 200,
child: ElevatedButton(
onPressed: () {},
child: Text('200宽按钮'),
),
)
🔄 按钮状态管理
启用/禁用状态
dart
class ToggleButton extends StatefulWidget {
@override
_ToggleButtonState createState() => _ToggleButtonState();
}
class _ToggleButtonState extends State<ToggleButton> {
bool _isEnabled = true;
@override
Widget build(BuildContext context) {
return Column(
children: [
Switch(
value: _isEnabled,
onChanged: (value) {
setState(() {
_isEnabled = value;
});
},
),
ElevatedButton(
onPressed: _isEnabled ? () {} : null, // null = 禁用
child: Text(_isEnabled ? '启用' : '禁用'),
),
],
);
}
}
加载状态
dart
class LoadingButton extends StatefulWidget {
@override
_LoadingButtonState createState() => _LoadingButtonState();
}
class _LoadingButtonState extends State<LoadingButton> {
bool _isLoading = false;
Future<void> _handleSubmit() async {
setState(() {
_isLoading = true;
});
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
setState(() {
_isLoading = false;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _isLoading ? null : _handleSubmit,
child: _isLoading
? SizedBox(
height: 20,
width: 20,
child: CircularProgressIndicator(
strokeWidth: 2,
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
),
)
: Text('提交'),
);
}
}
🎨 高级样式技巧
1. 渐变背景按钮
dart
Ink(
decoration: BoxDecoration(
gradient: LinearGradient(
colors: [Colors.blue, Colors.purple],
),
borderRadius: BorderRadius.circular(8),
),
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.transparent,
shadowColor: Colors.transparent,
),
child: Text('渐变按钮'),
),
)
2. 圆形按钮
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
shape: CircleBorder(),
padding: EdgeInsets.all(20),
),
child: Icon(Icons.favorite),
)
3. 描边文字按钮
dart
TextButton(
onPressed: () {},
child: Text(
'描边文字',
style: TextStyle(
foreground: Paint()
..style = PaintingStyle.stroke
..strokeWidth = 2
..color = Colors.blue,
),
),
)
4. 不同圆角的按钮
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.only(
topLeft: Radius.circular(20),
topRight: Radius.circular(5),
bottomLeft: Radius.circular(5),
bottomRight: Radius.circular(20),
),
),
),
child: Text('不规则圆角'),
)
💡 最佳实践
1. 按钮选择指南
| 场景 | 推荐按钮 | 理由 |
|---|---|---|
| 主要操作(如提交、保存) | ElevatedButton |
视觉突出,强调重要性 |
| 次要操作(如取消、返回) | TextButton |
低调,不抢主按钮风头 |
| 中等强调(如添加、编辑) | OutlinedButton |
有边界感,但不过于突出 |
| 工具栏、列表操作 | IconButton |
节省空间,简洁 |
2. 按钮组合布局
dart
// 对话框按钮组
Row(
mainAxisAlignment: MainAxisAlignment.end,
children: [
TextButton(
onPressed: () => Navigator.pop(context),
child: Text('取消'),
),
SizedBox(width: 8),
ElevatedButton(
onPressed: () {
// 确认操作
},
child: Text('确定'),
),
],
)
// 底部操作栏
Row(
children: [
Expanded(
child: OutlinedButton(
onPressed: () {},
child: Text('取消'),
),
),
SizedBox(width: 16),
Expanded(
child: ElevatedButton(
onPressed: () {},
child: Text('确定'),
),
),
],
)
3. 使用全局主题
dart
MaterialApp(
theme: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 5,
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
textButtonTheme: TextButtonThemeData(
style: TextButton.styleFrom(
foregroundColor: Colors.blue,
),
),
),
home: MyHomePage(),
)
4. 避免常见错误
❌ 错误:按钮文字过长导致溢出
dart
// 不好
ElevatedButton(
onPressed: () {},
child: Text('这是一个非常非常非常长的按钮文字'),
)
✅ 正确:限制文字或使用自适应布局
dart
// 好
ElevatedButton(
onPressed: () {},
child: Text(
'这是一个非常非常非常长的按钮文字',
overflow: TextOverflow.ellipsis,
maxLines: 1,
),
)
🤔 常见问题(FAQ)
Q1: 如何去除按钮的水波动画?
A: 使用 splashFactory 设置为 NoSplash.splashFactory:
dart
ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
splashFactory: NoSplash.splashFactory, // 禁用水波
),
child: Text('无水波'),
)
Q2: 如何让按钮宽度自适应内容?
A: 默认按钮就是自适应内容的。如果被外层组件约束,可以使用 Wrap 或设置 mainAxisSize:
dart
Row(
mainAxisSize: MainAxisSize.min, // 自适应
children: [
ElevatedButton(
onPressed: () {},
child: Text('短'),
),
],
)
Q3: 为什么我的按钮点击没反应?
A: 检查以下几点:
onPressed是否为null(会导致禁用)- 是否被其他 Widget 遮挡(如透明的 Overlay)
- 是否在
IgnorePointer内部
dart
// 检查是否禁用
ElevatedButton(
onPressed: null, // ❌ 禁用状态
child: Text('按钮'),
)
// 正确
ElevatedButton(
onPressed: () {
print('点击了');
}, // ✅ 启用状态
child: Text('按钮'),
)
Q4: 如何实现按钮防抖(防止重复点击)?
A: 使用状态管理或延迟:
dart
class DebounceButton extends StatefulWidget {
@override
_DebounceButtonState createState() => _DebounceButtonState();
}
class _DebounceButtonState extends State<DebounceButton> {
bool _isProcessing = false;
Future<void> _handleClick() async {
if (_isProcessing) return; // 防止重复点击
setState(() {
_isProcessing = true;
});
// 执行操作
await Future.delayed(Duration(seconds: 2));
setState(() {
_isProcessing = false;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _isProcessing ? null : _handleClick,
child: Text(_isProcessing ? '处理中...' : '提交'),
);
}
}
Q5: 如何实现按钮长按效果?
A: 使用 GestureDetector 或 InkWell 包裹:
dart
GestureDetector(
onLongPress: () {
print('长按了');
},
child: ElevatedButton(
onPressed: () {
print('普通点击');
},
child: Text('长按试试'),
),
)
🎯 跟着做练习
练习1:实现一个全宽的提交按钮
目标: 创建一个全宽的绿色提交按钮,带圆角,点击后显示 SnackBar
步骤:
- 使用
SizedBox设置全宽 - 使用
ElevatedButton.styleFrom自定义颜色和圆角 - 点击时显示
ScaffoldMessenger.of(context).showSnackBar
💡 查看答案
dart
class FullWidthButton extends StatelessWidget {
const FullWidthButton({super.key});
@override
Widget build(BuildContext context) {
return SizedBox(
width: double.infinity,
child: ElevatedButton(
onPressed: () {
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(
content: Text('提交成功!'),
duration: Duration(seconds: 2),
),
);
},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.green,
foregroundColor: Colors.white,
padding: const EdgeInsets.symmetric(vertical: 16),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(12),
),
),
child: const Text(
'提交',
style: TextStyle(fontSize: 16, fontWeight: FontWeight.bold),
),
),
);
}
}
练习2:实现一个计数按钮
目标: 按钮显示点击次数,每次点击次数+1,点击5次后禁用
步骤:
- 使用
StatefulWidget管理计数状态 - 在
onPressed中更新计数 - 当计数达到5时,
onPressed设为null
💡 查看答案
dart
class CounterButton extends StatefulWidget {
const CounterButton({super.key});
@override
State<CounterButton> createState() => _CounterButtonState();
}
class _CounterButtonState extends State<CounterButton> {
int _count = 0;
final int _maxCount = 5;
void _incrementCounter() {
if (_count < _maxCount) {
setState(() {
_count++;
});
}
}
@override
Widget build(BuildContext context) {
return Column(
mainAxisSize: MainAxisSize.min,
children: [
ElevatedButton(
onPressed: _count < _maxCount ? _incrementCounter : null,
child: Text('点击次数: $_count'),
),
const SizedBox(height: 8),
if (_count >= _maxCount)
const Text(
'已达到最大点击次数',
style: TextStyle(color: Colors.red, fontSize: 12),
),
if (_count < _maxCount)
Text(
'还可以点击 ${_maxCount - _count} 次',
style: const TextStyle(color: Colors.grey, fontSize: 12),
),
],
);
}
}
练习3:实现一个多功能工具栏
目标: 创建一行图标按钮,包括:编辑、删除、分享、更多
步骤:
- 使用
Row布局 - 创建4个
IconButton - 每个按钮点击时打印不同的信息
💡 查看答案
dart
class Toolbar extends StatelessWidget {
final VoidCallback? onEdit;
final VoidCallback? onDelete;
final VoidCallback? onShare;
final VoidCallback? onMore;
const Toolbar({
super.key,
this.onEdit,
this.onDelete,
this.onShare,
this.onMore,
});
@override
Widget build(BuildContext context) {
return Card(
child: Padding(
padding: const EdgeInsets.symmetric(horizontal: 8, vertical: 4),
child: Row(
mainAxisSize: MainAxisSize.min,
children: [
IconButton(
icon: const Icon(Icons.edit),
onPressed: onEdit ??
() {
print('编辑');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('编辑功能')),
);
},
tooltip: '编辑',
color: Colors.blue,
),
IconButton(
icon: const Icon(Icons.delete),
onPressed: onDelete ??
() {
print('删除');
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认'),
content: const Text('确定要删除吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
TextButton(
onPressed: () {
print('已删除');
Navigator.pop(context);
},
child: const Text('确定'),
),
],
),
);
},
tooltip: '删除',
color: Colors.red,
),
IconButton(
icon: const Icon(Icons.share),
onPressed: onShare ??
() {
print('分享');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('分享功能')),
);
},
tooltip: '分享',
color: Colors.green,
),
IconButton(
icon: const Icon(Icons.more_vert),
onPressed: onMore ??
() {
print('更多');
ScaffoldMessenger.of(context).showSnackBar(
const SnackBar(content: Text('更多功能')),
);
},
tooltip: '更多',
),
],
),
),
);
}
}
// 使用示例
class ToolbarDemo extends StatelessWidget {
const ToolbarDemo({super.key});
@override
Widget build(BuildContext context) {
return Center(
child: Toolbar(
onEdit: () => print('自定义编辑'),
onDelete: () => print('自定义删除'),
),
);
}
}
📋 小结
核心要点
| 按钮类型 | 默认外观 | 适用场景 |
|---|---|---|
| ElevatedButton | 带阴影和背景 | 主要操作 |
| TextButton | 透明背景 | 次要操作、对话框 |
| OutlinedButton | 边框,透明背景 | 中等强调 |
| IconButton | 仅图标 | 工具栏、紧凑布局 |
样式自定义
- 推荐 :
styleFrom- 简洁直观 - 高级 :
ButtonStyle+MaterialStateProperty- 根据状态设置样式
状态管理
- 启用/禁用 :
onPressed为null时禁用 - 加载状态 :显示
CircularProgressIndicator+ 禁用按钮