交互型组件
交互型组件让用户能与 App 进行操作——点击按钮、输入文本、选择选项等。
Button — 按钮
Flutter 提供了多种按钮组件,按视觉风格和使用场景区分。
按钮总览
| 组件 | 风格 | 典型场景 |
|---|---|---|
ElevatedButton | 有阴影,突出 | 主要操作 |
FilledButton | 填充色,Material 3 | 主要操作(推荐) |
OutlinedButton | 边框,不填充 | 次要操作 |
TextButton | 无边框,文字按钮 | 文字链接、对话框按钮 |
IconButton | 仅图标 | 工具栏操作 |
FloatingActionButton | 悬浮圆形 | 页面主要操作 |
基本用法
dart
import 'package:flutter/material.dart';
class ButtonExamples extends StatelessWidget {
const ButtonExamples({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('按钮示例')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// ElevatedButton — 有阴影的按钮
// ─── ★ 各按钮类型 ──────────────
ElevatedButton(
onPressed: () {},
child: const Text('Elevated'),
),
// ─── ☆ 各按钮类型 ──────────────
const SizedBox(height: 12),
// FilledButton — 填充按钮(Material 3 推荐)
FilledButton(
onPressed: () {},
child: const Text('Filled'),
),
const SizedBox(height: 12),
// OutlinedButton — 边框按钮
OutlinedButton(
onPressed: () {},
child: const Text('Outlined'),
),
const SizedBox(height: 12),
// TextButton — 文字按钮
TextButton(
onPressed: () {},
child: const Text('Text'),
),
const SizedBox(height: 12),
// IconButton — 图标按钮
IconButton(
onPressed: () {},
icon: const Icon(Icons.favorite),
),
],
),
),
floatingActionButton: FloatingActionButton(
onPressed: () {},
child: const Icon(Icons.add),
),
);
}
}按钮状态
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;
Future<void> _handleSubmit() async {
setState(() => _isLoading = true);
await Future.delayed(const Duration(seconds: 2)); // 模拟网络请求
setState(() => _isLoading = false);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('按钮状态')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 禁用状态
const ElevatedButton(
onPressed: null, // null 表示禁用
child: Text('不可点击'),
),
const SizedBox(height: 16),
// 加载状态
ElevatedButton(
onPressed: _isLoading ? null : _handleSubmit,
child: _isLoading
? const SizedBox(
width: 16, height: 16,
child: CircularProgressIndicator(strokeWidth: 2),
)
: const Text('提交'),
),
],
),
),
);
}
}自定义按钮样式
dart
import 'package:flutter/material.dart';
class CustomButtonExample extends StatelessWidget {
const CustomButtonExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('自定义按钮样式')),
body: Center(
child: ElevatedButton(
onPressed: () {},
style: ElevatedButton.styleFrom(
backgroundColor: Colors.blue,
foregroundColor: Colors.white,
elevation: 4,
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(8),
),
),
child: const Text('自定义按钮'),
),
),
);
}
}WidgetState(按钮状态样式)
dart
import 'package:flutter/material.dart';
class WidgetStateButtonExample extends StatelessWidget {
const WidgetStateButtonExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('WidgetState 按钮样式')),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 方式一:resolveWith
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.resolveWith((states) {
if (states.contains(WidgetState.pressed)) return Colors.blue[700];
if (states.contains(WidgetState.disabled)) return Colors.grey;
return Colors.blue;
}),
),
child: const Text('resolveWith 按钮'),
),
const SizedBox(height: 16),
// 方式二:fromMap(Flutter 3.41+ 推荐,更简洁)
ElevatedButton(
onPressed: () {},
style: ButtonStyle(
backgroundColor: WidgetStateProperty.fromMap({
WidgetState.pressed: Colors.blue[700],
WidgetState.disabled: Colors.grey,
WidgetState.any: Colors.blue,
}),
),
child: const Text('fromMap 按钮'),
),
],
),
),
);
}
}TextField — 文本输入
TextField 是最常用的输入组件,用于接收用户输入的文字。
基本用法
dart
import 'package:flutter/material.dart';
class TextFieldExample extends StatelessWidget {
const TextFieldExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('TextField 示例')),
body: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
decoration: const InputDecoration(
hintText: '请输入用户名',
prefixIcon: Icon(Icons.person),
),
onChanged: (value) {
print('输入内容: $value');
},
),
),
);
}
}InputDecoration — 输入框装饰
dart
import 'package:flutter/material.dart';
class InputDecorationExample extends StatelessWidget {
const InputDecorationExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('InputDecoration 示例')),
body: Padding(
padding: const EdgeInsets.all(16),
child: TextField(
decoration: InputDecoration(
labelText: '用户名',
hintText: '请输入用户名',
prefixIcon: const Icon(Icons.person),
suffixIcon: const Icon(Icons.clear),
border: OutlineInputBorder(
borderRadius: BorderRadius.circular(8),
),
filled: true,
fillColor: Colors.grey[100],
counterText: '0/20',
),
),
),
);
}
}TextEditingController
用 TextEditingController 控制输入框的值:
dart
class MyForm extends StatefulWidget {
@override
State<MyForm> createState() => _MyFormState();
}
class _MyFormState extends State<MyForm> {
final _controller = TextEditingController();
@override
void dispose() {
_controller.dispose(); // 必须释放
super.dispose();
}
void _clear() {
_controller.clear();
}
@override
Widget build(BuildContext context) {
return Column(
children: [
TextField(
controller: _controller,
decoration: InputDecoration(
hintText: '请输入',
suffixIcon: IconButton(
icon: Icon(Icons.clear),
onPressed: _clear,
),
),
),
Text('当前输入: ${_controller.text}'),
],
);
}
}常用属性
| 属性 | 说明 |
|---|---|
controller | 控制器 |
obscureText | 是否隐藏文字(密码) |
maxLines | 最大行数(null 为不限) |
maxLength | 最大字符数 |
keyboardType | 键盘类型 |
textInputAction | 键盘回车键行为 |
readOnly | 是否只读 |
enabled | 是否可用 |
键盘类型
dart
import 'package:flutter/material.dart';
class KeyboardTypeExample extends StatelessWidget {
const KeyboardTypeExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('键盘类型')),
body: const Padding(
padding: EdgeInsets.all(16),
child: Column(
children: [
TextField(keyboardType: TextInputType.text, decoration: InputDecoration(labelText: '默认')),
SizedBox(height: 16),
TextField(keyboardType: TextInputType.number, decoration: InputDecoration(labelText: '数字')),
SizedBox(height: 16),
TextField(keyboardType: TextInputType.emailAddress, decoration: InputDecoration(labelText: '邮箱')),
SizedBox(height: 16),
TextField(keyboardType: TextInputType.phone, decoration: InputDecoration(labelText: '电话')),
],
),
),
);
}
}Form / TextFormField — 表单
Form + TextFormField 提供了完整的表单验证功能。
基本用法
dart
class LoginForm extends StatefulWidget {
@override
State<LoginForm> createState() => _LoginFormState();
}
class _LoginFormState extends State<LoginForm> {
final _formKey = GlobalKey<FormState>();
@override
Widget build(BuildContext context) {
return Form(
key: _formKey,
child: Column(
children: [
TextFormField(
decoration: InputDecoration(labelText: '邮箱'),
validator: (value) {
if (value == null || value.isEmpty) return '请输入邮箱';
if (!value.contains('@')) return '邮箱格式不正确';
return null;
},
),
TextFormField(
decoration: InputDecoration(labelText: '密码'),
obscureText: true,
validator: (value) {
if (value == null || value.length < 6) return '密码至少6位';
return null;
},
),
SizedBox(height: 16),
ElevatedButton(
onPressed: () {
if (_formKey.currentState!.validate()) {
// 验证通过,提交表单
}
},
child: Text('登录'),
),
],
),
);
}
}Dialog — 对话框
AlertDialog
dart
import 'package:flutter/material.dart';
class DialogExample extends StatelessWidget {
const DialogExample({super.key});
void _showDeleteDialog(BuildContext context) {
showDialog(
context: context,
builder: (context) => AlertDialog(
title: const Text('确认删除'),
content: const Text('删除后不可恢复,确定继续吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context),
child: const Text('取消'),
),
FilledButton(
onPressed: () {
Navigator.pop(context);
},
child: const Text('删除'),
),
],
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Dialog 示例')),
body: Center(
child: ElevatedButton(
onPressed: () => _showDeleteDialog(context),
child: const Text('显示对话框'),
),
),
);
}
}简单对话框
dart
import 'package:flutter/material.dart';
class ConfirmDialogExample extends StatelessWidget {
const ConfirmDialogExample({super.key});
Future<void> _showConfirmDialog(BuildContext context) async {
final confirmed = await showDialog<bool>(
context: context,
builder: (context) => AlertDialog(
title: const Text('退出'),
content: const Text('确定退出吗?'),
actions: [
TextButton(
onPressed: () => Navigator.pop(context, false),
child: const Text('取消'),
),
TextButton(
onPressed: () => Navigator.pop(context, true),
child: const Text('确定'),
),
],
),
);
if (confirmed == true) {
// 用户确认
}
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('确认对话框')),
body: Center(
child: ElevatedButton(
onPressed: () => _showConfirmDialog(context),
child: const Text('显示确认对话框'),
),
),
);
}
}BottomSheet — 底部弹窗
dart
import 'package:flutter/material.dart';
class BottomSheetExample extends StatelessWidget {
const BottomSheetExample({super.key});
void _showBottomSheet(BuildContext context) {
showModalBottomSheet(
context: context,
builder: (context) => Padding(
padding: const EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
Text('选择操作', style: Theme.of(context).textTheme.titleMedium),
const SizedBox(height: 16),
ListTile(
leading: const Icon(Icons.camera_alt),
title: const Text('拍照'),
onTap: () => Navigator.pop(context),
),
ListTile(
leading: const Icon(Icons.photo_library),
title: const Text('从相册选择'),
onTap: () => Navigator.pop(context),
),
],
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('BottomSheet 示例')),
body: Center(
child: ElevatedButton(
onPressed: () => _showBottomSheet(context),
child: const Text('显示底部弹窗'),
),
),
);
}
}SnackBar — 提示消息
dart
import 'package:flutter/material.dart';
class SnackBarExample extends StatelessWidget {
const SnackBarExample({super.key});
void _showSnackBar(BuildContext context) {
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: const Text('操作成功'),
duration: const Duration(seconds: 2),
action: SnackBarAction(
label: '撤销',
onPressed: () { /* 撤销操作 */ },
),
),
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('SnackBar 示例')),
body: Center(
child: ElevatedButton(
onPressed: () => _showSnackBar(context),
child: const Text('显示 SnackBar'),
),
),
);
}
}Flutter 3.38+ 变更
带 action 的 SnackBar 不再自动消失,用户必须手动操作。无 action 的 SnackBar 仍按 duration 自动消失。
Selection — 选择类组件
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 _checked = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Checkbox 示例')),
body: Column(
children: [
Checkbox(
value: _checked,
onChanged: (value) => setState(() => _checked = value ?? false),
),
CheckboxListTile(
value: _checked,
onChanged: (value) => setState(() => _checked = value ?? false),
title: const Text('同意协议'),
controlAffinity: ListTileControlAffinity.leading,
),
],
),
);
}
}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 _isOn = false;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Switch 示例')),
body: Column(
children: [
Switch(
value: _isOn,
onChanged: (value) => setState(() => _isOn = value),
),
SwitchListTile(
value: _isOn,
onChanged: (value) => setState(() => _isOn = value),
title: const Text('深色模式'),
),
],
),
);
}
}Radio — 单选
dart
import 'package:flutter/material.dart';
enum Gender { male, female }
class RadioExample extends StatefulWidget {
const RadioExample({super.key});
@override
State<RadioExample> createState() => _RadioExampleState();
}
class _RadioExampleState extends State<RadioExample> {
Gender? _gender;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Radio 示例')),
body: Column(
children: [
Radio<Gender>(
value: Gender.male,
groupValue: _gender,
onChanged: (value) => setState(() => _gender = value),
),
RadioListTile<Gender>(
title: const Text('男'),
value: Gender.male,
groupValue: _gender,
onChanged: (value) => setState(() => _gender = value),
),
RadioListTile<Gender>(
title: const Text('女'),
value: Gender.female,
groupValue: _gender,
onChanged: (value) => setState(() => _gender = value),
),
],
),
);
}
}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 _volume = 50;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('Slider 示例')),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('音量: ${_volume.round()}', style: const TextStyle(fontSize: 24)),
Slider(
value: _volume,
min: 0,
max: 100,
divisions: 10,
label: _volume.round().toString(),
onChanged: (value) => setState(() => _volume = value),
),
],
),
);
}
}GestureDetector — 手势检测
GestureDetector 可以检测各种手势操作。
常用手势
dart
import 'package:flutter/material.dart';
class GestureDetectorExample extends StatelessWidget {
const GestureDetectorExample({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: const Text('GestureDetector 示例')),
body: Center(
child: GestureDetector(
onTap: () => print('点击'),
onDoubleTap: () => print('双击'),
onLongPress: () => print('长按'),
onHorizontalDragEnd: (details) => print('水平拖拽结束'),
child: Container(
color: Colors.blue,
width: 100,
height: 100,
child: const Center(child: Text('点我', style: TextStyle(color: Colors.white))),
),
),
),
);
}
}常用回调
| 回调 | 手势 |
|---|---|
onTap | 单击 |
onDoubleTap | 双击 |
onLongPress | 长按 |
onVerticalDragStart/Update/End | 垂直拖拽 |
onHorizontalDragStart/Update/End | 水平拖拽 |
onScaleStart/Update/End | 缩放/旋转 |
onPanStart/Update/End | 自由拖拽 |
NavigationBar — 底部导航
dart
import 'package:flutter/material.dart';
class NavigationBarExample extends StatefulWidget {
const NavigationBarExample({super.key});
@override
State<NavigationBarExample> createState() => _NavigationBarExampleState();
}
class _NavigationBarExampleState extends State<NavigationBarExample> {
int _currentIndex = 0;
final _pages = const [
Center(child: Text('首页', style: TextStyle(fontSize: 24))),
Center(child: Text('发现', style: TextStyle(fontSize: 24))),
Center(child: Text('我的', style: TextStyle(fontSize: 24))),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: _pages[_currentIndex],
bottomNavigationBar: NavigationBar(
selectedIndex: _currentIndex,
onDestinationSelected: (index) => setState(() => _currentIndex = index),
destinations: const [
NavigationDestination(icon: Icon(Icons.home), label: '首页'),
NavigationDestination(icon: Icon(Icons.explore), label: '发现'),
NavigationDestination(icon: Icon(Icons.person), label: '我的'),
],
),
);
}
}NavigationBar vs BottomNavigationBar
| 组件 | 说明 |
|---|---|
NavigationBar | Material 3 风格(推荐) |
BottomNavigationBar | 旧版 Material 2 风格 |
交互型组件速查
| 组件 | 用途 |
|---|---|
| ElevatedButton / FilledButton | 主要操作按钮 |
| OutlinedButton / TextButton | 次要操作按钮 |
| IconButton | 图标按钮 |
| FloatingActionButton | 悬浮按钮 |
| TextField | 文本输入 |
| Form / TextFormField | 表单与验证 |
| AlertDialog | 对话框 |
| BottomSheet | 底部弹窗 |
| SnackBar | 轻提示 |
| Checkbox / Switch / Radio / Slider | 选择类 |
| GestureDetector | 手势检测 |
| NavigationBar | 底部导航 |
