交互是移动应用的核心功能之一,而按钮和各种交互组件则是实现用户交互的基础。在 Flutter 中,提供了丰富的内置交互组件,能够满足各种场景下的用户交互需求。本节课将详细介绍 Flutter 中常用的按钮组件、其他交互控件以及手势识别机制,帮助你构建响应式的用户界面。
一、常用按钮组件
Flutter 提供了几种预定义的按钮组件,它们遵循 Material Design 设计规范,适用于不同的界面场景。
1. ElevatedButton(悬浮按钮)
ElevatedButton
是带有阴影和背景色的按钮,点击时会有悬浮效果,适合用于主要操作。
dart
ElevatedButton(
// 按钮点击回调
onPressed: () {
print("ElevatedButton 被点击了");
},
// 按钮文本内容
child: const Text("提交"),
// 按钮样式
style: ElevatedButton.styleFrom(
// 背景颜色
backgroundColor: Colors.blue,
// 文本颜色
foregroundColor: Colors.white,
// 按钮内边距
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
// 字体大小
textStyle: const TextStyle(fontSize: 16),
// 圆角
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
// 阴影
elevation: 4,
),
)
当 onPressed
为 null
时,按钮会处于禁用状态:
dart
ElevatedButton(
onPressed: null, // 禁用按钮
child: const Text("禁用状态"),
)
2. TextButton(文本按钮)
TextButton
是没有背景色和阴影的文本按钮,仅包含文本和点击效果,适合用于次要操作。
dart
TextButton(
onPressed: () {
print("TextButton 被点击了");
},
child: const Text("取消"),
style: TextButton.styleFrom(
foregroundColor: Colors.grey[700], // 文本颜色
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
textStyle: const TextStyle(fontSize: 14),
),
)
3. OutlinedButton(轮廓按钮)
OutlinedButton
是带有边框的按钮,没有背景色,适合用于 tertiary(第三级)操作。
dart
OutlinedButton(
onPressed: () {
print("OutlinedButton 被点击了");
},
child: const Text("查看详情"),
style: OutlinedButton.styleFrom(
foregroundColor: Colors.blue, // 文本和边框颜色
side: const BorderSide(color: Colors.blue), // 边框样式
padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(4),
),
),
)
4. 带图标的按钮
所有按钮都可以通过 child
属性添加图标,实现图文结合的效果:
dart
// 带图标的 ElevatedButton
ElevatedButton.icon(
onPressed: () {},
icon: const Icon(Icons.save, size: 18), // 图标
label: const Text("保存"), // 文本
)
// 带图标的 TextButton
TextButton.icon(
onPressed: () {},
icon: const Icon(Icons.share, size: 18),
label: const Text("分享"),
)
// 带图标的 OutlinedButton
OutlinedButton.icon(
onPressed: () {},
icon: const Icon(Icons.delete, size: 18),
label: const Text("删除"),
)
5. 按钮主题统一管理
为了保持应用中按钮样式的一致性,可以使用 Theme
或 ButtonTheme
统一配置按钮样式:
dart
Theme(
data: ThemeData(
elevatedButtonTheme: ElevatedButtonThemeData(
style: ElevatedButton.styleFrom(
backgroundColor: Colors.purple,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
),
),
child: Column(
children: [
ElevatedButton(
onPressed: () {},
child: const Text("按钮1"), // 会应用主题样式
),
ElevatedButton(
onPressed: () {},
child: const Text("按钮2"), // 会应用主题样式
),
],
),
)
二、按钮点击事件与状态变化
按钮的核心功能是响应用户的点击操作,通过 onPressed
回调实现。同时,按钮的状态会随着交互发生变化。
1. 基本点击事件
onPressed
是按钮最基本的回调函数,当用户点击按钮时触发:
dart
ElevatedButton(
onPressed: () {
// 处理点击事件
print("按钮被点击");
// 可以在这里更新状态、导航到新页面或执行其他操作
},
child: const Text("点击我"),
)
2. 长按事件
除了点击,按钮还支持长按事件,通过 onLongPress
回调实现:
dart
ElevatedButton(
onPressed: () {
print("点击事件");
},
onLongPress: () {
print("长按事件");
},
child: const Text("长按试试"),
)
3. 按钮状态管理
按钮的状态(启用 / 禁用、加载中)通常需要根据应用状态动态变化:
dart
import 'package:flutter/material.dart';
class ButtonStateExample extends StatefulWidget {
const ButtonStateExample({super.key});
@override
State<ButtonStateExample> createState() => _ButtonStateExampleState();
}
class _ButtonStateExampleState extends State<ButtonStateExample> {
bool _isLoading = false;
bool _isEnabled = true;
void _handleSubmit() async {
// 禁用按钮并显示加载状态
setState(() {
_isLoading = true;
_isEnabled = false;
});
// 模拟网络请求
await Future.delayed(const Duration(seconds: 2));
// 恢复按钮状态
setState(() {
_isLoading = false;
_isEnabled = true;
});
}
@override
Widget build(BuildContext context) {
return ElevatedButton(
onPressed: _isEnabled ? _handleSubmit : null,
child: _isLoading
? const Row(
mainAxisSize: MainAxisSize.min,
children: [
CircularProgressIndicator(color: Colors.white, strokeWidth: 2),
SizedBox(width: 8),
Text("处理中..."),
],
)
: const Text("提交"),
);
}
}
三、其他交互组件
除了按钮,Flutter 还提供了多种常用的交互组件,用于实现表单输入、选择等功能。
1. Checkbox(复选框)
Checkbox
用于多选场景,允许用户选择多个选项:
dart
import 'package:flutter/material.dart';
class CheckboxExample extends StatefulWidget {
const CheckboxExample({super.key});
@override
State<CheckboxExample> createState() => _CheckboxExampleState();
}
class _CheckboxExampleState extends State<CheckboxExample> {
bool _isChecked = false;
@override
Widget build(BuildContext context) {
return Checkbox(
value: _isChecked, // 当前选中状态
onChanged: (value) {
// 状态变化回调
setState(() {
_isChecked = value ?? false;
});
},
activeColor: Colors.blue, // 选中时的颜色
checkColor: Colors.white, // 勾选图标的颜色
);
}
}
通常会与 Text
组合使用,并通过 Row
布局:
dart
Row(
children: [
Checkbox(
value: _isChecked,
onChanged: (value) {
setState(() => _isChecked = value ?? false);
},
),
const Text("我已阅读并同意用户协议"),
],
)
CheckboxListTile
是一个更完整的组件,包含复选框、文本和图标:
dart
CheckboxListTile(
value: _isChecked,
onChanged: (value) {
setState(() => _isChecked = value ?? false);
},
title: const Text("接收推送通知"),
subtitle: const Text("接收最新活动和优惠信息"),
secondary: const Icon(Icons.notifications),
controlAffinity: ListTileControlAffinity.leading, // 复选框位置
)
2. Radio(单选按钮)
Radio
用于单选场景,一组单选按钮中只能有一个被选中:
dart
import 'package:flutter/material.dart';
class RadioExample extends StatefulWidget {
const RadioExample({super.key});
@override
State<RadioExample> createState() => _RadioExampleState();
}
class _RadioExampleState extends State<RadioExample> {
String? _selectedOption;
final List<String> _options = ['选项A', '选项B', '选项C'];
@override
Widget build(BuildContext context) {
return Column(
children: _options.map((option) {
return RadioListTile(
title: Text(option),
value: option,
groupValue: _selectedOption,
onChanged: (value) {
setState(() {
_selectedOption = value;
});
},
);
}).toList(),
);
}
}
RadioListTile
是包含单选按钮和文本的完整组件,比单独使用 Radio
更方便。
3. Switch(开关)
Switch
用于切换两种状态(开启 / 关闭),通常用于设置项:
dart
import 'package:flutter/material.dart';
class SwitchExample extends StatefulWidget {
const SwitchExample({super.key});
@override
State<SwitchExample> createState() => _SwitchExampleState();
}
class _SwitchExampleState extends State<SwitchExample> {
bool _isEnabled = false;
@override
Widget build(BuildContext context) {
return Switch(
value: _isEnabled,
onChanged: (value) {
setState(() {
_isEnabled = value;
});
},
activeColor: Colors.green, // 开启状态的颜色
);
}
}
SwitchListTile
是包含开关和文本的完整组件:
dart
SwitchListTile(
title: const Text("深色模式"),
subtitle: const Text("切换到深色显示模式"),
value: _isDarkMode,
onChanged: (value) {
setState(() => _isDarkMode = value);
},
secondary: const Icon(Icons.dark_mode),
)
4. Slider(滑块)
Slider
用于在一个范围内选择数值,如音量调节、亮度设置等:
dart
import 'package:flutter/material.dart';
class SliderExample extends StatefulWidget {
const SliderExample({super.key});
@override
State<SliderExample> createState() => _SliderExampleState();
}
class _SliderExampleState extends State<SliderExample> {
double _value = 50;
@override
Widget build(BuildContext context) {
return Column(
children: [
Slider(
value: _value,
min: 0, // 最小值
max: 100, // 最大值
divisions: 10, // 分成多少等分
label: "${_value.round()}", // 拖动时显示的标签
onChanged: (value) {
setState(() {
_value = value;
});
},
onChangeStart: (value) {
print("开始拖动: $value");
},
onChangeEnd: (value) {
print("结束拖动: $value");
},
),
Text("当前值: ${_value.round()}"),
],
);
}
}
四、手势识别:GestureDetector
GestureDetector
是一个功能强大的手势识别组件,它可以包裹任何 Widget 并检测各种用户手势,如点击、滑动、长按等。
1. 基本点击手势
dart
GestureDetector(
// 单击
onTap: () {
print("单击");
},
// 双击
onDoubleTap: () {
print("双击");
},
// 长按
onLongPress: () {
print("长按");
},
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: const Center(
child: Text("点我", style: TextStyle(color: Colors.white)),
),
),
)
2. 滑动手势
GestureDetector
可以检测各种滑动手势,包括水平滑动、垂直滑动等:
dart
import 'package:flutter/material.dart';
class SwipeExample extends StatefulWidget {
const SwipeExample({super.key});
@override
State<SwipeExample> createState() => _SwipeExampleState();
}
class _SwipeExampleState extends State<SwipeExample> {
String _direction = "请滑动";
@override
Widget build(BuildContext context) {
return GestureDetector(
// 滑动开始
onPanStart: (details) {
print("滑动开始: ${details.globalPosition}");
},
// 滑动更新
onPanUpdate: (details) {
// 滑动偏移量
print("滑动偏移: ${details.delta}");
},
// 滑动结束
onPanEnd: (details) {
// 滑动速度和方向
final velocity = details.velocity.pixelsPerSecond;
setState(() {
if (velocity.dx.abs() > velocity.dy.abs()) {
// 水平滑动
_direction = velocity.dx > 0 ? "向右滑动" : "向左滑动";
} else {
// 垂直滑动
_direction = velocity.dy > 0 ? "向下滑动" : "向上滑动";
}
});
},
child: Container(
width: 300,
height: 200,
color: Colors.grey[200],
child: Center(
child: Text(
_direction,
style: const TextStyle(fontSize: 18),
),
),
),
);
}
}
3. 缩放手势
GestureDetector
还可以检测缩放手势,实现图片缩放等功能:
dart
import 'package:flutter/material.dart';
class ScaleExample extends StatefulWidget {
const ScaleExample({super.key});
@override
State<ScaleExample> createState() => _ScaleExampleState();
}
class _ScaleExampleState extends State<ScaleExample> {
double _scale = 1.0;
double _previousScale = 1.0;
@override
Widget build(BuildContext context) {
return GestureDetector(
onScaleStart: (details) {
_previousScale = _scale;
},
onScaleUpdate: (details) {
setState(() {
_scale = _previousScale * details.scale;
// 限制缩放范围
_scale = _scale.clamp(0.5, 3.0);
});
},
child: Transform.scale(
scale: _scale,
child: Image.network(
"https://picsum.photos/400/300",
width: 400,
height: 300,
fit: BoxFit.cover,
),
),
);
}
}
4. 手势竞争与优先级
当多个手势可能同时被识别时(如点击和长按),Flutter 有一套手势竞争机制来决定哪个手势应该被响应。可以通过 behavior
属性控制手势检测的行为:
dart
GestureDetector(
behavior: HitTestBehavior.opaque, // 即使子组件透明也能检测手势
onTap: () {
print("容器被点击");
},
child: Container(
width: 100,
height: 100,
// 透明容器也能响应手势
),
)
HitTestBehavior
的取值包括:
deferToChild
:默认值,子组件优先响应手势opaque
:当前组件优先响应手势,即使透明translucent
:当前组件和子组件都可以响应手势