分页列表父类-快速搭建页面
2023-05-14 本文已影响0人
小城哇哇
每次复制一份代码
看着重复的代码发呆
是时候拿起重构的武器了
前言
编写了一个PagingListWidget
组件,提取了分页列表通用逻辑,其他业务相关的采用虚方法,由子类来实现。
通用逻辑包括:(1)下拉刷新。(2)上拉加载。(3)空数据展示。
其他业务逻辑:(1)导航栏信息。(2)获取列表信息。(3)列表项的视图。
使用方法,直接继承PagingListWidget
并实现虚方法。
一、 起因
作为业务开发工程师,每次新需求来,看到都是列表展示,毫无技术难度。直接ctrl + c 然后ctrl + v,一顿接口修改,开发完成。
一直觉得这已经挺良心了。可突然腻了。
想着是不是可以再懒一点,想多点自由时间来研究怎么能够更懒。
二、 提取
暴力开启,直接拷贝了一个列表页面的代码,然后将业务相关代码删除,仅仅留下通用的逻辑代码。如下:
/// 分页列表的页面
/// navigationTitle返回标题,页面展示一个title,一个列表。
/// navigationTitle返回null。页面仅仅为一个列表。此时可以重载build添加其他widgets。
abstract class PagingListWidget extends StatefulWidget {
const PagingListWidget({Key? key}) : super(key: key);
@override
PagingListWidgetState createState();
}
abstract class PagingListWidgetState<T extends PagingListWidget, S> extends State<T> {
// 实现类 获取数据时使用
final int pageSize = 20;
int page = 1;
final List<S> dataList = [];
bool showLoadingMore = false;
int total = 0;
bool hasInitialed = false;
bool _disposed = false;
final ScrollController _scrollController = ScrollController();
final GlobalKey<RefreshIndicatorState> _refreshKey = GlobalKey();
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (showLoadingMore) {
return;
}
if (_scrollController.position.pixels > (_scrollController.position.maxScrollExtent - 20) && total > dataList.length) {
setState(() {
showLoadingMore = true;
});
_loadMore();
}
});
Future.delayed(const Duration(seconds: 0), () {
_onRefresh();
});
}
@override
void dispose() {
_disposed = true;
super.dispose();
}
@override
Widget build(BuildContext context) {
var title = navigationTitle();
if (null == title) {
return _contentWidget();
}
return Scaffold(
appBar: AppBar(
title: Text(title),
),
body: _contentWidget(),
);
}
// Widget __START__
Widget _contentWidget() {
return SafeArea(
child: !hasInitialed
? const MyCircularProgress()
: RefreshIndicator(
key: _refreshKey,
onRefresh: _onRefresh,
child: dataList.isEmpty
? const NoData()
: ListView.builder(
itemBuilder: (context, index) {
if (index == dataList.length) {
return showLoadingMore
? Container(
margin: const EdgeInsets.all(10),
child: const Align(
child: CircularProgressIndicator(
color: MyColors.mainColor,
),
),
)
: total <= dataList.length
? Container(
margin: const EdgeInsets.all(10),
alignment: Alignment.center,
child: const Text("没有更多数据"),
)
: const SizedBox(
height: 44,
);
}
return listItem(index);
},
physics: const AlwaysScrollableScrollPhysics(),
itemCount: dataList.length + 1,
controller: _scrollController,
),
),
);
}
// Widget __END__
// Network __START__
Future<void> _onRefresh() async {
dataList.clear();
page = 1;
if (_disposed) {
return;
}
fetchData();
}
Future<void> _loadMore() async {
page++;
if (_disposed) {
return;
}
fetchData();
}
// Network __END__
// 虚方法 __START__
// 标题
// 返回为null 则说明此页面不要包含导航栏,仅仅返回列表页面。
// 返回标题时,那么直接展示完整的页面
String? navigationTitle();
// 获取列表的数据。
Future<void> fetchData();
// 列表视图
Widget listItem(int index);
// 虚方法 __END__
}
三、 思路
1. 为了使用尽量方便,直接继承StatefulWidget
或ConsumerStatefulWidget
。
2. 将所有的值都放在父类,子类通过super
来访问。
3. 列表信息的data model,采用泛型传入。abstract class PagingListWidgetState<T extends PagingListWidget, S>
避免类型强转。
4. 提高复用性。根据navigationTitle
来判断是否是带导航栏。
5. 提供自定义的部分,采用虚方法,让子类来实现。
四、样例
将原来的页面采用PagingListWidget
重写下,代码量减少了一半。并且以后编写分页列表的新页面,开发时间减少90%吧。爽了。
举一个例子,如下:
class MyList extends PagingListWidget {
const MyList({super.key});
@override
PagingListWidgetState<PagingListWidget, dynamic> createState() => _MyListState();
}
class _MyListState extends PagingListWidgetState<MyList, String> {
var random = Random();
@override
Future<void> fetchData() async {
return Future.delayed(const Duration(seconds: 3), () {
super.total = 100000;
super.dataList.addAll(List.generate(super.pageSize, (index) => "测试下 ${random.nextInt(10000000)}"));
setState(() {
super.showLoadingMore = false;
super.hasInitialed = true;
});
});
}
@override
Widget listItem(int index) {
return Container(
color: MyColors.randomColor(),
height: 44,
padding: const EdgeInsets.only(top: 8, left: 8),
child: Text(super.dataList[index], style: TextStyle(color: MyColors.randomColor(), fontSize: 16),),
);
}
@override
String? navigationTitle() {
return "测试分页列表";
}
}
1. 创建子类。
2. 实现虚方法。
3. 完成
五、结尾
思路突然打开了。重复的代码以后就用这个方式重构下,代码量和开发量下降好多。
抛砖引玉。请赐教更好的思路。