0%

Flutter-ListView组件下拉刷新+滚动底部加载+缓存封装

Flutter的ListView组件,虽然很好用,但是数据量大的时候,在低配置的机器上会奇卡无比,所以我封装了一个ListView组件,对数据进行截断,并使用 keframe 插件进行流畅性优化,提升低配置设备的用户体验。

具体可直接查看代码示例,配有相关注释。

效果预览

以下为一台低配置的 Android6.0 设备上的预览效果,实际应该会更好。

大家也可以使用adb命令对设备进行录屏:adb shell screenrecord /sdcard/test.mp4,再转换为 gif 文件,推荐使用https://ezgif.com/video-to-gif

组件代码

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
/*
* @Author: hxb
*/
import 'package:flutter/cupertino.dart';
import 'package:flutter/material.dart';
import 'package:flutter_easyloading/flutter_easyloading.dart';//后续去除
import 'package:keframe/size_cache_widget.dart';

/// 可缓存+下拉刷新+滚动到底部自动加载的ListView组件(返回数据定义复杂是方便局部刷新)
class CacheTableList extends StatefulWidget {
List tableList;
double height;
ScrollController controller;
String emptyMessage;
String noMoreMessage;
Function onRefresh; //需返回完整的待渲染tablelist,方便组件局部刷新。
Function createList; //自定义的item建立事件
Function getMoreData; //需返回完整的待渲染tablelist,返回null表示无更多数据,方便组件局部刷新。
int pageCount;

CacheTableList(
{Key key,
@required this.createList,
this.tableList,
this.onRefresh,
this.controller,
this.height,
this.emptyMessage,
this.noMoreMessage,
this.pageCount,
this.getMoreData})
/// pageCount为静态数据懒加载,需配合tableList使用。getMoreData为动态加载数据,他与前者只能存在一种模式。
: assert(getMoreData != null || pageCount != null),
assert(pageCount == null || tableList != null),
assert(pageCount != null || tableList == null), //tableList与pageCount同在
super(key: key);
@override
_CacheTableListState createState() => _CacheTableListState();
}

class _CacheTableListState extends State<CacheTableList> {
List orginalTableList = [];
List _tableList = [];
int _pageCount = 0;
bool _hasMore = true;
double _height;
ScrollController _controller;

@override
void initState() {
super.initState();
orginalTableList = widget.tableList ?? [];
_controller = widget.controller ?? new ScrollController();
if (widget.getMoreData != null || widget.pageCount != 0) {
_controller.addListener(() {
if (_controller.position.pixels == _controller.position.maxScrollExtent) {
_getMoreData();
}
});
}
_height = widget.height ?? 300; //double.infinity,
}

@override
void dispose() {
super.dispose();
_controller.removeListener(() {});
}

@override
Widget build(BuildContext context) {
return Container(
height: _height,
decoration: new BoxDecoration(color: Colors.white),
child: SizeCacheWidget(
estimateCount: _tableList.length,
child:
widget.onRefresh != null
? RefreshIndicator(
onRefresh: _onRefresh, //下拉刷新回调
displacement: 10, //指示器显示时距顶部位置
color: Colors.white, //指示器颜色,默认ThemeData.accentColor
backgroundColor: Colors.blueAccent, //指示器背景颜色,默认ThemeData.canvasColor
notificationPredicate: defaultScrollNotificationPredicate, //是否应处理滚动通知的检查(是否通知下拉刷新动作)
child: Scrollbar(
child: _createList(),
),
)
: Scrollbar(
child: _createList(),
),
),
);
}

/// 创建list
Widget _createList() {
if (_tableList.isEmpty && _pageCount == 0) {
_getMoreData();
return CupertinoActivityIndicator();
}
return _tableList.isEmpty
? Container(
height: _height - 20,
alignment: Alignment.topCenter,
margin: EdgeInsets.only(top: 20),
child: Text(
widget.emptyMessage ?? '暂无数据',
style: TextStyle(fontSize: 15, color: Colors.black38, fontWeight: FontWeight.bold),
),
)
: ListView.builder(
controller: _controller,
cacheExtent: 1000,
itemCount: _tableList.length,
itemBuilder: (_, i) {
if (i == _tableList.length - 1 && _hasMore) {
return Padding(
padding: EdgeInsets.all(10.0),
child: Center(
child: CupertinoActivityIndicator(),
),
);
}
return widget.createList(i);//自定义创建item事件
},
);
}

/// 加载更多数据
void _getMoreData() async {
if (!_hasMore) {
EasyLoading.showToast(widget.noMoreMessage ?? '已经到底了');
// ScaffoldMessenger.of(context).showSnackBar(
// SnackBar(
// content: Text(widget.noMoreMessage ?? '已经到底了'),
// ),
// );
return;
}
if (widget.getMoreData != null) {
_pageCount = -1; //使用自定义方法加载数据,除第一次加载数据后则无需pageCount。
var tempData = await widget.getMoreData();
if (tempData == null) {
_hasMore = false;
} else {
_tableList = tempData;
}
setState(() {});
return;
}
int nowLength = orginalTableList.length;
if (_pageCount <= nowLength) {
_pageCount += widget.pageCount;
Future.delayed(Duration(microseconds: 500)).then((e) {
setState(() {
if (_pageCount <= nowLength) {
_tableList = orginalTableList.sublist(0, _pageCount);
} else {
_tableList = orginalTableList;
_hasMore = false;
}
});
});
}
}

/// 刷新时候的事件,需返回Future。
Future _onRefresh() {
return Future.sync(() async {
_tableList = await widget.onRefresh();
_pageCount = 0;
_hasMore = true;
setState(() {});
});
}
}

使用示例

静态加载数据模式-简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
CacheTableList(
tableList: ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"],
pageCount: 3,//上面6笔静态数据,会以每三笔的方式加载。注意高度设定需符合要求
createList: (index) {
return FrameSeparateWidget(//使用keframe进行单帧动画优化,提升低配置设备流畅性。
index: index,
placeHolder: Container(//占位组件,尽量简单。
height: 36,
width: double.infinity,
margin: EdgeInsets.only(bottom: 5, top: 5),
child: Text("......"),
alignment: Alignment.center,
),
child: ListTile(title: Text("列表第$index项"))
);
},
onRefresh: () {
//下拉事件->重新渲染列表
return Future.delayed(Duration(milliseconds: 1000), () {
return ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"];
});
}
);

动态加载数据模式-简单示例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// _tableList = ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"];
CacheTableList(
createList: (index) {
return FrameSeparateWidget(//使用 keframe 进行单帧动画优化,提升低配置设备流畅性。
index: index,
placeHolder: Container(//占位组件,尽量简单。
height: 36,
width: double.infinity,
margin: EdgeInsets.only(bottom: 5, top: 5),
child: Text("......"),
alignment: Alignment.center,
),
child: ListTile(title: Text(_tableList[index]))
);
},
getMoreData: () {
_tableList.addAll(_tableList);
return Future.delayed(Duration(milliseconds: 1000), () {
return _tableList.length > 100 ? null : _tableList; //为null表示数据到极限不再加载
});
}
/// onRefresh不配置时则不会有下拉事件
// onRefresh: () {
// //下拉事件->重新渲染列表
// return Future.delayed(Duration(milliseconds: 1000), () {
// return ["列表内容1","列表内容2","列表内容3","列表内容4","列表内容5","列表内容6"];
// });
// }
);
bulb