Flutter的ListView组件,虽然很好用,但是数据量大的时候,在低配置的机器上会奇卡无比,所以我封装了一个ListView组件,对数据进行截断,并使用 keframe 插件进行流畅性优化,提升低配置设备的用户体验。
具体可直接查看代码示例,配有相关注释。
Sorry, your browser does not support the video tag.
大家也可以使用adb命令对设备进行录屏:
adb shell screenrecord /sdcard/test.mp4
,再转换为 gif 文件,推荐使用https://ezgif.com/video-to-gif。
组件代码
代码语言:javascript复制/*
* @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(() {});
});
}
}
使用示例
静态加载数据模式-简单示例
代码语言:javascript复制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"];
});
}
);
动态加载数据模式-简单示例
代码语言:javascript复制// _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"];
// });
// }
);