在 Flutter 中,列表是应用中非常常见的一部分,尤其是在展示大量数据时。为了优化用户体验,我们通常需要支持分页加载、下拉刷新和上拉加载等功能。为了应对这些需求,我们可以封装一个通用的列表刷新组件,提供更灵活的自定义能力和更高效的代码复用。
本文将详细介绍如何使用 EasyRefresh
插件封装一个通用的分页刷新列表组件,包括实现分页加载、下拉刷新、上拉加载、空数据展示等功能,并展示如何在实际项目中使用。
一、背景需求分析
开发中我们常常会遇到列表数据较多的情况,加载全部数据可能会导致卡顿或耗费过多的网络带宽。为了避免这些问题,通常采用以下策略:
- 分页加载:将数据分成若干页,每次加载一部分数据,减少一次性加载的数据量。
- 下拉刷新:用户下拉时刷新列表,通常是为了获取最新的数据。
- 上拉加载:用户上拉时加载更多数据,通常用于分页加载更多列表项。
- 空数据展示:当列表没有数据时,展示一个空状态页面。
为了解决这些需求,我们需要设计一个通用的组件,支持分页加载、下拉刷新、上拉加载等功能,并能够根据实际需求自定义列表项的展示。
二、组件设计思路
我们的目标是封装一个高度可复用的列表组件,支持以下功能:
- 分页加载:通过
pageNum
和pageSize
控制分页加载的数据。 - 下拉刷新和上拉加载:利用
EasyRefresh
插件实现下拉刷新和上拉加载。 - 自定义子组件:允许开发者自定义列表项的展示方式。
- 空数据展示:当没有数据时,显示一个提示组件。
接下来,我们将通过代码逐步实现这些功能。
三、RefreshList
组件的代码实现
首先,我们实现一个 RefreshList
组件,它将处理分页加载、下拉刷新和上拉加载的逻辑。
import 'package:flutter/material.dart';
import 'package:flutter_easyrefresh/easy_refresh.dart';
typedef ApiCallback = Future<Map<String, dynamic>> Function(int pageNum, int pageSize);
typedef CustomChildBuilder = Widget Function(BuildContext context, List<dynamic> dataList);
class RefreshList extends StatefulWidget {
final ApiCallback apiCallback; // API请求方法
final Widget Function(BuildContext context, dynamic item)? itemBuilder; // 每一项的构建
final Widget emptyWidget; // 空数据时的展示组件
final CustomChildBuilder? childBuilder; // 自定义 child 构建方法
const RefreshList({
Key? key,
required this.apiCallback,
this.itemBuilder,
this.emptyWidget = const Center(child: Text("暂无数据")),
this.childBuilder,
}) : super(key: key);
@override
RefreshListState createState() => RefreshListState();
}
class RefreshListState extends State<RefreshList> {
int pageNum = 1; // 当前页数
final int pageSize = 10; // 每页大小
int totalPages = 1; // 总页数
bool isLoading = false; // 是否正在加载
List<dynamic> dataList = []; // 数据列表
Future<void> fetchData({bool isRefresh = false}) async {
if (isLoading) return; // 防止重复加载
setState(() {
isLoading = true;
});
try {
final response = await widget.apiCallback(pageNum, pageSize);
final dynamic rawData = response['list'] ?? [];
final List<dynamic> newData =
rawData is List ? rawData : [rawData]; // 转为列表
setState(() {
if (isRefresh) {
dataList = newData;
} else {
dataList.addAll(newData);
}
totalPages = response['pages'] ?? 1; // 设置总页数
});
} catch (e) {
debugPrint("加载数据失败: $e");
} finally {
setState(() {
isLoading = false;
});
}
}
Future<void> onRefresh() async {
dataList.clear();
setState(() {
pageNum = 1;
});
await fetchData(isRefresh: true);
}
Future<void> onLoad() async {
if (pageNum >= totalPages) return;
setState(() {
pageNum++;
});
await fetchData();
}
@override
void initState() {
super.initState();
fetchData(isRefresh: true);
}
@override
Widget build(BuildContext context) {
return EasyRefresh(
header: BallPulseHeader(
backgroundColor: Colors.white,
color: Color(0xFF60D5C7),
),
footer: BallPulseFooter(
backgroundColor: Colors.white,
color: Color(0xFF60D5C7),
),
onRefresh: onRefresh,
onLoad: onLoad,
child: widget.childBuilder != null
? widget.childBuilder!(context, dataList)
: (dataList.isEmpty
? widget.emptyWidget
: ListView.builder(
itemCount: dataList.length,
itemBuilder: (context, index) {
return widget.itemBuilder!(context, dataList[index]);
},
)),
);
}
}
组件解析
1. RefreshList
组件的构造函数
- apiCallback:这个回调函数是我们向 API 请求数据的核心。它需要返回一个包含分页信息的数据结构(例如,
list
和pages
)。 - itemBuilder:这是一个可选的回调函数,用来构建列表项。通过它可以自定义每一项的 UI。
- emptyWidget:用于展示空数据时的提示组件,默认为一个简单的文本“暂无数据”。
- childBuilder:如果传入了这个回调函数,
RefreshList
会使用它来自定义构建整个列表,而不是默认的ListView.builder
。
2. RefreshListState
状态类
- fetchData:核心的 API 请求方法,负责获取数据并更新
dataList
。该方法接受一个isRefresh
参数,指示是否为下拉刷新操作。如果是刷新,它会清空原数据并重新加载;如果是加载更多,它会将新数据追加到现有数据列表中。 - onRefresh 和 onLoad:分别处理下拉刷新和上拉加载操作。
- initState:初始化时自动请求第一页数据。
3. EasyRefresh
插件
EasyRefresh
插件提供了非常简单的 API 来实现下拉刷新和上拉加载功能。我们通过 BallPulseHeader
和 BallPulseFooter
自定义了下拉刷新和上拉加载时的动画效果。
四、如何在项目中使用 RefreshList
组件
在实际项目中,我们可以通过传入 API 请求函数来使用这个组件。以下是一个使用 RefreshList
组件的完整示例。
class MyPage extends StatelessWidget {
// 模拟 API 请求
Future<Map<String, dynamic>> fetchData(int pageNum, int pageSize) async {
await Future.delayed(Duration(seconds: 2)); // 模拟网络延时
return {
'list': List.generate(pageSize, (index) => 'Item ${index + 1}'), // 返回模拟数据
'pages': 5, // 模拟总页数
};
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('分页加载示例')),
body: RefreshList(
apiCallback: fetchData,
itemBuilder: (context, item) {
return ListTile(title: Text(item));
},
emptyWidget: Center(child: Text("没有更多数据")),
),
);
}
}
关键点说明:
apiCallback
:我们实现了fetchData
方法,这个方法模拟了一个网络请求,返回了分页数据。你可以根据实际需求替换为真实的 API 调用。itemBuilder
:我们通过itemBuilder
回调自定义了列表项的显示方式。在这个例子中,简单地使用了ListTile
来展示每一项数据。emptyWidget
:当没有数据时,我们展示一个简单的文本提示“没有更多数据”。
五、总结
通过封装 RefreshList
组件,我们可以快速实现通用的分页加载、下拉刷新和上拉加载的功能,而不需要在每个页面中重复编写这些逻辑。这种方式的好处在于:
- 复用性高:组件化的设计可以在多个页面中复用,减少代码冗余。
- 可定制性强:通过传入不同的回调函数和展示方法,我们可以灵活地控制数据加载逻辑和 UI 展示。
- 易于维护:将常见的列表操作封装到一个组件中,使得项目结构更加清晰,易于维护。
无论是在展示静态数据、动态加载内容,还是处理空数据情况,RefreshList
组件都能很好地满足这些需求,帮助开发者更专注于业务逻辑,而不是重复构建复杂的 UI 组件。