Appearance
第12章:Flutter 常用组件进阶
12.1 列表组件(ListView)
ListView.builder
ListView.builder 是 Flutter 中最常用的列表组件,适合显示大量数据,具有性能优化功能。
基本使用:
dart
ListView.builder(
itemCount: items.length, // 列表项数量
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
subtitle: Text(items[index].subtitle),
leading: Icon(items[index].icon),
trailing: Icon(Icons.arrow_forward),
onTap: () {
// 点击事件
print('Tapped on item $index');
},
);
},
);列表项点击事件
dart
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return GestureDetector(
onTap: () {
Navigator.push(
context,
MaterialPageRoute(
builder: (context) => DetailPage(item: items[index]),
),
);
},
child: ListTile(
title: Text(items[index].title),
subtitle: Text(items[index].subtitle),
),
);
},
);列表分割线
方法 1:使用 Divider
dart
ListView.separated(
itemCount: items.length,
separatorBuilder: (context, index) => Divider(),
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
);
},
);方法 2:自定义分割线
dart
ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return Column(
children: [
ListTile(
title: Text(items[index].title),
),
if (index < items.length - 1)
Container(
height: 1,
color: Colors.grey[200],
margin: EdgeInsets.symmetric(horizontal: 16),
),
],
);
},
);下拉刷新(RefreshIndicator)
dart
RefreshIndicator(
onRefresh: _refreshData, // 刷新回调
child: ListView.builder(
itemCount: items.length,
itemBuilder: (context, index) {
return ListTile(
title: Text(items[index].title),
);
},
),
);
// 刷新数据的方法
Future<void> _refreshData() async {
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
setState(() {
// 更新数据
items = [...items];
});
}列表上拉加载更多
dart
ListView.builder(
itemCount: items.length + 1, // 增加一个加载项
itemBuilder: (context, index) {
if (index == items.length) {
// 显示加载指示器
return _isLoading
? Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
)
: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: Text('No more data')),
);
}
return ListTile(
title: Text(items[index].title),
);
},
controller: _scrollController,
);
// 滚动控制器
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_loadMoreData();
}
});
}
// 加载更多数据
void _loadMoreData() {
if (!_isLoading) {
setState(() {
_isLoading = true;
});
// 模拟网络请求
Future.delayed(Duration(seconds: 2), () {
setState(() {
items.addAll(newItems);
_isLoading = false;
});
});
}
}12.2 网格组件(GridView)
GridView 用于实现网格布局,如商品列表、图片画廊等。
基本使用
dart
GridView.count(
crossAxisCount: 2, // 列数
mainAxisSpacing: 10, // 行间距
crossAxisSpacing: 10, // 列间距
padding: EdgeInsets.all(10), // 内边距
children: List.generate(
items.length,
(index) {
return Card(
child: Column(
children: [
Image.network(
items[index].imageUrl,
height: 100,
width: double.infinity,
fit: BoxFit.cover,
),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(items[index].title),
),
],
),
);
},
),
);GridView.builder
dart
GridView.builder(
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
mainAxisSpacing: 10,
crossAxisSpacing: 10,
childAspectRatio: 0.8, // 子项宽高比
),
itemCount: items.length,
itemBuilder: (context, index) {
return Card(
child: Column(
children: [
Image.network(items[index].imageUrl),
Padding(
padding: const EdgeInsets.all(8.0),
child: Text(items[index].title),
),
],
),
);
},
);12.3 滚动组件(SingleChildScrollView)
SingleChildScrollView 用于解决页面溢出问题,实现滚动功能。
基本使用
dart
SingleChildScrollView(
child: Column(
children: [
// 大量子组件
for (int i = 0; i < 50; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
height: 50,
color: Colors.blue,
child: Center(child: Text('Item $i')),
),
),
],
),
);水平滚动
dart
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < 20; i++)
Padding(
padding: const EdgeInsets.all(8.0),
child: Container(
width: 100,
height: 100,
color: Colors.blue,
child: Center(child: Text('Item $i')),
),
),
],
),
);嵌套滚动
dart
SingleChildScrollView(
child: Column(
children: [
Container(
height: 200,
color: Colors.red,
),
SingleChildScrollView(
scrollDirection: Axis.horizontal,
child: Row(
children: [
for (int i = 0; i < 10; i++)
Container(
width: 100,
height: 100,
color: Colors.blue,
margin: EdgeInsets.all(10),
),
],
),
),
Container(
height: 500,
color: Colors.green,
),
],
),
);12.4 对话框组件(AlertDialog)
普通对话框
dart
void _showAlertDialog() {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Alert Dialog'),
content: Text('This is an alert dialog.'),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
TextButton(
onPressed: () {
// 执行操作
Navigator.of(context).pop();
},
child: Text('OK'),
),
],
);
},
);
}输入对话框
dart
void _showInputDialog() {
TextEditingController _controller = TextEditingController();
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text('Input Dialog'),
content: TextField(
controller: _controller,
decoration: InputDecoration(labelText: 'Enter your name'),
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
TextButton(
onPressed: () {
// 获取输入内容
String name = _controller.text;
print('Name: $name');
Navigator.of(context).pop();
},
child: Text('OK'),
),
],
);
},
);
}12.5 底部弹窗(showModalBottomSheet)
dart
void _showBottomSheet() {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.share),
title: Text('Share'),
onTap: () {
Navigator.of(context).pop();
// 执行分享操作
},
),
ListTile(
leading: Icon(Icons.copy),
title: Text('Copy'),
onTap: () {
Navigator.of(context).pop();
// 执行复制操作
},
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Delete'),
onTap: () {
Navigator.of(context).pop();
// 执行删除操作
},
),
],
),
);
},
);
}自定义底部弹窗
dart
void _showCustomBottomSheet() {
showModalBottomSheet(
context: context,
isScrollControlled: true, // 全屏模式
builder: (BuildContext context) {
return DraggableScrollableSheet(
initialChildSize: 0.5, // 初始高度
maxChildSize: 0.8, // 最大高度
minChildSize: 0.3, // 最小高度
builder: (BuildContext context, ScrollController scrollController) {
return Container(
color: Colors.white,
child: ListView.builder(
controller: scrollController,
itemCount: 20,
itemBuilder: (context, index) {
return ListTile(
title: Text('Item $index'),
);
},
),
);
},
);
},
);
}12.6 实操案例:实现动态列表、下拉刷新、上拉加载、对话框交互
目标
创建一个应用,实现动态列表展示、下拉刷新、上拉加载更多功能,并添加对话框交互。
步骤 1:创建数据模型
dart
class Item {
final int id;
final String title;
final String subtitle;
final String imageUrl;
Item({
required this.id,
required this.title,
required this.subtitle,
required this.imageUrl,
});
}步骤 2:创建主页面
dart
import 'package:flutter/material.dart';
class ListDemoPage extends StatefulWidget {
@override
_ListDemoPageState createState() => _ListDemoPageState();
}
class _ListDemoPageState extends State<ListDemoPage> {
List<Item> _items = [];
bool _isLoading = false;
bool _isLoadingMore = false;
int _page = 1;
final _scrollController = ScrollController();
@override
void initState() {
super.initState();
_loadData();
_scrollController.addListener(() {
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
_loadMoreData();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Future<void> _loadData() async {
setState(() {
_isLoading = true;
});
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
setState(() {
_items = List.generate(10, (index) => Item(
id: index + 1,
title: 'Item ${index + 1}',
subtitle: 'This is item ${index + 1}',
imageUrl: 'https://picsum.photos/200/300?random=${index + 1}',
));
_isLoading = false;
_page = 1;
});
}
Future<void> _loadMoreData() async {
if (!_isLoadingMore) {
setState(() {
_isLoadingMore = true;
});
// 模拟网络请求
await Future.delayed(Duration(seconds: 2));
setState(() {
_page++;
_items.addAll(List.generate(10, (index) => Item(
id: (_page - 1) * 10 + index + 1,
title: 'Item ${(_page - 1) * 10 + index + 1}',
subtitle: 'This is item ${(_page - 1) * 10 + index + 1}',
imageUrl: 'https://picsum.photos/200/300?random=${(_page - 1) * 10 + index + 1}',
)));
_isLoadingMore = false;
});
}
}
void _showItemDialog(Item item) {
showDialog(
context: context,
builder: (BuildContext context) {
return AlertDialog(
title: Text(item.title),
content: Column(
mainAxisSize: MainAxisSize.min,
children: [
Image.network(item.imageUrl, height: 200),
SizedBox(height: 10),
Text(item.subtitle),
],
),
actions: [
TextButton(
onPressed: () {
Navigator.of(context).pop();
},
child: Text('Cancel'),
),
TextButton(
onPressed: () {
Navigator.of(context).pop();
// 执行操作
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Action performed on ${item.title}')),
);
},
child: Text('OK'),
),
],
);
},
);
}
void _showBottomSheet(Item item) {
showModalBottomSheet(
context: context,
builder: (BuildContext context) {
return Container(
padding: EdgeInsets.all(16),
child: Column(
mainAxisSize: MainAxisSize.min,
children: [
ListTile(
leading: Icon(Icons.share),
title: Text('Share ${item.title}'),
onTap: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Shared ${item.title}')),
);
},
),
ListTile(
leading: Icon(Icons.copy),
title: Text('Copy ${item.title}'),
onTap: () {
Navigator.of(context).pop();
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Copied ${item.title}')),
);
},
),
ListTile(
leading: Icon(Icons.delete),
title: Text('Delete ${item.title}'),
onTap: () {
Navigator.of(context).pop();
setState(() {
_items.remove(item);
});
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(content: Text('Deleted ${item.title}')),
);
},
),
],
),
);
},
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('List Demo'),
),
body: _isLoading
? Center(child: CircularProgressIndicator())
: RefreshIndicator(
onRefresh: _loadData,
child: ListView.builder(
controller: _scrollController,
itemCount: _items.length + 1,
itemBuilder: (context, index) {
if (index == _items.length) {
return _isLoadingMore
? Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: CircularProgressIndicator()),
)
: Padding(
padding: const EdgeInsets.all(16.0),
child: Center(child: Text('No more data')),
);
}
final item = _items[index];
return GestureDetector(
onTap: () => _showItemDialog(item),
onLongPress: () => _showBottomSheet(item),
child: Card(
margin: EdgeInsets.symmetric(horizontal: 16, vertical: 8),
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Row(
children: [
Image.network(
item.imageUrl,
width: 80,
height: 80,
fit: BoxFit.cover,
),
SizedBox(width: 16),
Expanded(
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(item.title, style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)),
SizedBox(height: 8),
Text(item.subtitle, style: TextStyle(color: Colors.grey)),
],
),
),
Icon(Icons.arrow_forward),
],
),
),
),
);
},
),
),
);
}
}步骤 3:运行应用
- 启动模拟器或连接真机
- 运行项目
- 测试下拉刷新功能
- 测试上拉加载更多功能
- 点击列表项测试对话框功能
- 长按列表项测试底部弹窗功能
12.7 常用组件最佳实践
1. 列表组件
- 使用 ListView.builder:对于大量数据,使用
ListView.builder提高性能 - 合理设置 itemCount:确保
itemCount与数据长度一致 - 避免嵌套滚动:尽量避免在 ListView 中嵌套其他滚动组件
- 使用 key:为列表项提供唯一的 key,提高性能
2. 网格组件
- 选择合适的构造器:根据数据量选择
GridView.count或GridView.builder - 合理设置间距:通过
mainAxisSpacing和crossAxisSpacing控制间距 - 设置合适的宽高比:通过
childAspectRatio控制子项比例
3. 滚动组件
- 避免过度使用:只在必要时使用
SingleChildScrollView - 设置 physics:根据需要设置滚动行为
- 处理键盘:在包含输入框时,使用
SingleChildScrollView避免键盘遮挡
4. 对话框组件
- 保持简洁:对话框内容不要过于复杂
- 提供明确的操作:确保用户知道如何关闭对话框
- 处理返回键:默认情况下,点击返回键会关闭对话框
5. 底部弹窗
- 使用 isScrollControlled:需要更大空间时设置为 true
- 使用 DraggableScrollableSheet:实现可拖动的底部弹窗
- 保持内容简洁:底部弹窗内容应该清晰明了
12.8 小结
本章介绍了 Flutter 的常用组件进阶,包括列表组件、网格组件、滚动组件、对话框组件、底部弹窗等内容。这些组件是 Flutter 开发中高频使用的,掌握它们对于构建功能丰富的应用至关重要。
我们学习了:
- ListView:实现列表展示,支持下拉刷新和上拉加载
- GridView:实现网格布局,适合展示商品、图片等
- SingleChildScrollView:解决页面溢出问题,实现滚动功能
- AlertDialog:实现对话框交互,包括普通对话框和输入对话框
- showModalBottomSheet:实现底部弹窗,提供快捷操作
通过实操案例,我们实现了一个包含动态列表、下拉刷新、上拉加载、对话框交互和底部弹窗的应用,体验了这些组件的完整使用流程。在实际开发中,你应该根据应用的需求选择合适的组件,并遵循最佳实践,提升应用的用户体验。
在接下来的章节中,我们将进入综合实战部分,通过实际项目来巩固所学的知识。
