在我软的UWP里面有一个接口ISupportIncrementalLoading 只要你的集合继承这个,并且实现里面的方法,就能自动实现加载更多的这个动作。说白了就是UWP里面UI列表控件跟集合一个契约。
在Flutter里面没有这种类似的东西,但是实际项目里面会出现大量的列表需要加载更多。
无图无真相,先上一个图。
首先我们也来定义个 LoadingMoreBase 契约类
class LoadingMoreBase<T> extends ListBase<T>
with _LoadingMoreBloc<T>, RefreshBase {
var _array = <T>[];
@override
T operator [](int index) {
// TODO: implement []
return _array[index];
}
@override
void operator []=(int index, T value) {
// TODO: implement []=
_array[index] = value;
}
bool get hasMore => true;
bool isLoading = false;
IndicatorStatus indicatorStatus = IndicatorStatus.None;
Future<bool> loadMore() async {
if (isLoading || !hasMore) return true;
// TODO: implement loadMore
var preStatus = indicatorStatus;
indicatorStatus = this.length == 0
? IndicatorStatus.FullScreenBusying
: IndicatorStatus.LoadingMoreBusying;
if (preStatus == IndicatorStatus.Error) {
onStateChanged(this);
}
isLoading = true;
var isSuccess = await loadData();
isLoading = false;
if (isSuccess) {
if (this.length == 0) indicatorStatus = IndicatorStatus.Empty;
} else {
indicatorStatus = IndicatorStatus.Error;
}
onStateChanged(this);
return isSuccess;
}
Future<bool> loadData() async {
return true;
}
@override
Future<bool> onRefresh() async {
// TODO: implement OnRefresh
}
@override
int get length => _array.length;
set length(int newLength) => _array.length = newLength;
@override
void onStateChanged(LoadingMoreBase<T> source) {
// TODO: implement notice
super.onStateChanged(source);
}
}
class _LoadingMoreBloc<T> {
final _rebuild = new StreamController<LoadingMoreBase<T>>.broadcast();
Stream<LoadingMoreBase<T>> get rebuild => _rebuild.stream;
void onStateChanged(LoadingMoreBase<T> source) {
if (!_rebuild?.isClosed) _rebuild.sink.add(source);
}
void dispose() {
_rebuild?.close();
}
}
复制代码
继承于ListBase 方便后面继承
3个重要的方法: 用于加载更多
Future<bool> loadMore() async
复制代码
用于刷新(重置列表)
Future<bool> onRefresh() async
复制代码
用于获取数据,loadmore会调用这个方法,一般我们override的这个方法,loadmore里面有一些状态控制,如果你需要overrdie loadmore方法,注意查看下之前里面的状态控制代码
Future<bool> loadData() async
复制代码
3个重要的属性: hasMore 判断是否还有更多 isLoading 判断是否正在获取数据 indicatorStatus 判断当前列表的状态
_LoadingMoreBloc 可以通过这个类来通知streambuilder更新UI
下面是如何继承使用这个base 类
class TuChongRepository extends LoadingMoreBase<TuChongItem> {
int pageindex = 1;
@override
// TODO: implement hasMore
bool _hasMore = true;
bool get hasMore => _hasMore && length < 20;
@override
Future<bool> onRefresh() async {
// TODO: implement onRefresh
pageindex = 1;
return loadMore();
}
@override
Future<bool> loadData() async {
// TODO: implement getData
String url = "";
if (this.length == 0) {
url = "https://api.tuchong.com/feed-app";
} else {
int lastPostId = this[this.length - 1].post_id;
url =
"https://api.tuchong.com/feed-app?post_id=${lastPostId}&page=${pageindex}&type=loadmore";
}
bool isSuccess = false;
try {
//to show loading more clearly, in your app,remove this
await Future.delayed(Duration(milliseconds: 500, seconds: 1));
var result = await HttpFactory.getInstance().getHttpClient().get(url);
var source = TuChongSource.fromJson(json.decode(result.body));
if (pageindex == 1) {
this.clear();
}
source.feedList.forEach((item) {
if (item.hasImage && !this.contains(item) && hasMore) {
this.add(item);
}
});
_hasMore = source.feedList.length != 0;
pageindex++;
isSuccess = true;
} catch (exception) {
isSuccess = false;
print(exception);
}
return isSuccess;
}
}
复制代码
将你请求列表的代码加到getData方法里面,这样数据源的准备就好了。
下面说说UI组件 这一部分分为ListView/GridView 和SliverList/SliverGrid
ListView/GridView
LoadingMoreList 里面的部分代码,StreamBuilder为更新UI,NotificationListener为了监听滑动状态
class LoadingMoreList<T> extends StatelessWidget {
final ListConfig<T> listConfig;
LoadingMoreList(this.listConfig,{Key key})
: super(key: key);
@override
Widget build(BuildContext context) {
return StreamBuilder<LoadingMoreBase>(
builder: (d, s) {
return NotificationListener<ScrollNotification>(
//key: _key,
onNotification: _handleScrollNotification,
child: NotificationListener<OverscrollIndicatorNotification>(
onNotification: _handleGlowNotification,
child: listConfig.buildContent(context, s.data)),
);
},
stream: listConfig.sourceList?.rebuild,
);
}
}
复制代码
ListConfig 里面提供了ListView/GridView的全部参数,这里我也提供了去掉滚动越界效果(就是列表滚不动的时候出现的水波纹效果)的2个属性showGlowLeading/showGlowTrailing。
final Axis scrollDirection;
final bool reverse;
final ScrollController controller;
final bool primary;
final ScrollPhysics physics;
final bool shrinkWrap;
final EdgeInsetsGeometry padding;
final double itemExtent;
final int itemCount;
final bool addAutomaticKeepAlives;
final bool addRepaintBoundaries;
final bool addSemanticIndexes;
final double cacheExtent;
final int semanticChildCount;
/// Whether to show the overscroll glow on the side with negative scroll
/// offsets.
final bool showGlowLeading;
/// Whether to show the overscroll glow on the side with positive scroll
/// offsets.
final bool showGlowTrailing;
ListConfig(
@required itemBuilder,
@required sourceList, {
this.showGlowLeading: true,
this.showGlowTrailing: true,
LoadingMoreIndicatorBuilder indicatorBuilder,
SliverGridDelegate gridDelegate,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.primary,
this.physics,
this.shrinkWrap = false,
this.padding,
this.itemExtent,
this.itemCount,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.cacheExtent,
this.semanticChildCount,
}) : super(itemBuilder, sourceList,
indicatorBuilder: indicatorBuilder, gridDelegate: gridDelegate);
复制代码
sourceList 就是之前我们完成的loadingmore 数据源 itemBuilder 是每个item长什么样子
Demo code
class ListViewDemo extends StatefulWidget {
@override
_ListViewDemoState createState() => _ListViewDemoState();
}
class _ListViewDemoState extends State<ListViewDemo> {
TuChongRepository listSourceRepository;
@override
void initState() {
// TODO: implement initState
listSourceRepository = new TuChongRepository();
super.initState();
}
@override
void dispose() {
listSourceRepository?.dispose();
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
AppBar(
title: Text("ListViewDemo"),
),
Expanded(
child: LoadingMoreList(
ListConfig<TuChongItem>(
ItemBuilder.itemBuilder, listSourceRepository,
// showGlowLeading: false,
// showGlowTrailing: false,
padding: EdgeInsets.all(0.0)),),
)
],
),
);
}
}
复制代码
这样子实现了一个加载更多的ListView,如果是GridView的话请给gridDelegate赋值.
SliverList/SliverGrid
支持多个loadmore列表 SliverListConfig 里面包含了SliverList/SliverGrid里面的参数
//config for SliverList and SliverGrid
class SliverListConfig<T> extends LoadingMoreListConfig<T> {
//whether show no more .
bool showNoMore = true;
//whether show fullscreenLoading for multiple sliver
bool showFullScreenLoading = true;
final bool addAutomaticKeepAlives;
final bool addRepaintBoundaries;
final bool addSemanticIndexes;
final SemanticIndexCallback semanticIndexCallback;
final int semanticIndexOffset;
final int childCount;
SliverListConfig(
@required itemBuilder,
@required sourceList, {
LoadingMoreIndicatorBuilder indicatorBuilder,
SliverGridDelegate gridDelegate,
this.addAutomaticKeepAlives = true,
this.addRepaintBoundaries = true,
this.addSemanticIndexes = true,
this.semanticIndexCallback = _kDefaultSemanticIndexCallback,
this.semanticIndexOffset = 0,
this.childCount,
}) : super(itemBuilder, sourceList,
indicatorBuilder: indicatorBuilder, gridDelegate: gridDelegate);
复制代码
LoadingMoreCustomScrollView使用来创建Sliver组件,它包括了CustomScrollView的属性以及showGlowLeading/showGlowTrailing
//support for LoadingMoreSliverList
class LoadingMoreCustomScrollView extends StatefulWidget {
final List<Widget> slivers;
final Axis scrollDirection;
final bool reverse;
final ScrollController controller;
final bool primary;
final ScrollPhysics physics;
final bool shrinkWrap;
final double cacheExtent;
final int semanticChildCount;
/// Whether to show the overscroll glow on the side with negative scroll
/// offsets.
final bool showGlowLeading;
/// Whether to show the overscroll glow on the side with positive scroll
/// offsets.
final bool showGlowTrailing;
LoadingMoreCustomScrollView({
Key key,
this.scrollDirection = Axis.vertical,
this.reverse = false,
this.controller,
this.primary,
this.physics,
this.shrinkWrap = false,
this.cacheExtent,
this.slivers = const <Widget>[],
this.semanticChildCount,
this.showGlowLeading: true,
this.showGlowTrailing: true,
}) : assert(slivers != null),
super(key: key);
复制代码
Demo code
简单的一个Sliver
class SliverListDemo extends StatefulWidget {
@override
_SliverListDemoState createState() => _SliverListDemoState();
}
class _SliverListDemoState extends State<SliverListDemo> {
TuChongRepository listSourceRepository;
@override
void initState() {
// TODO: implement initState
listSourceRepository = new TuChongRepository();
super.initState();
}
@override
void dispose() {
listSourceRepository?.dispose();
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
child: LoadingMoreCustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
title: Text("SliverListDemo"),
),
LoadingMoreSliverList(
SliverListConfig<TuChongItem>(
ItemBuilder.itemBuilder, listSourceRepository,
//isLastOne: false
))
],
),
);
}
}
复制代码
多个Sliver
class MultipleSliverDemo extends StatefulWidget {
@override
_MultipleSliverDemoState createState() => _MultipleSliverDemoState();
}
class _MultipleSliverDemoState extends State<MultipleSliverDemo> {
TuChongRepository listSourceRepository;
TuChongRepository listSourceRepository1;
@override
void initState() {
// TODO: implement initState
listSourceRepository = new TuChongRepository();
listSourceRepository1 = new TuChongRepository();
super.initState();
}
@override
void dispose() {
listSourceRepository?.dispose();
listSourceRepository1?.dispose();
// TODO: implement dispose
super.dispose();
}
@override
Widget build(BuildContext context) {
return Material(
child: LoadingMoreCustomScrollView(
slivers: <Widget>[
SliverAppBar(
pinned: true,
title: Text("MultipleSliverDemo"),
),
LoadingMoreSliverList(SliverListConfig<TuChongItem>(
ItemBuilder.itemBuilder,
listSourceRepository,
)),
SliverToBoxAdapter(
child: Container(
alignment: Alignment.center,
child: Text("Next list"),
color: Colors.blue,
height: 100.0,
),
),
SliverPersistentHeader(
delegate: CommonSliverPersistentHeaderDelegate(
Container(
alignment: Alignment.center,
child: Text("Pinned Content"),
color: Colors.red,
),
100.0),
pinned: true,
),
LoadingMoreSliverList(SliverListConfig<TuChongItem>(
ItemBuilder.itemBuilder,
listSourceRepository1,
gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
crossAxisCount: 2,
crossAxisSpacing: 3.0,
mainAxisSpacing: 3.0,
// childAspectRatio: 0.5
),
))
],
),
);
}
}
复制代码
那么怎么自定义这些状态的显示内容呢? 我在config里面提供了indicatorBuilder,你可以根据当前list的状态自定义显示效果
demo code
@override
Widget build(BuildContext context) {
return Material(
child: Column(
children: <Widget>[
AppBar(
title: Text("CustomIndicatorDemo"),
),
Expanded(
child: LoadingMoreList(
ListConfig<TuChongItem>(
ItemBuilder.itemBuilder, listSourceRepository,
indicatorBuilder: _buildIndicator,
padding: EdgeInsets.all(0.0),
),
),
)
],
),
);
}
//you can use IndicatorWidget or build yourself widget
//in this demo, we define all status.
Widget _buildIndicator(BuildContext context, IndicatorStatus status) {
Widget widget;
bool full = (status == IndicatorStatus.FullScreenBusying);
double height = 35.0;
switch (status) {
case IndicatorStatus.None:
widget = Container(height: 0.0);
height = 0.0;
break;
case IndicatorStatus.LoadingMoreBusying:
case IndicatorStatus.FullScreenBusying:
double indicatorSize = full ? 30.0 : 15.0;
widget = Row(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: <Widget>[
Container(
margin: EdgeInsets.only(right: 15.0),
height: indicatorSize,
width: indicatorSize,
child: getIndicator(context)),
(!full
? Text(
"正在加载...慌什么慌",
)
: Text("正在加载...慌什么慌",
style: TextStyle(
fontWeight: FontWeight.bold, fontSize: 28.0))),
],
);
break;
case IndicatorStatus.Error:
widget = Text(
"加载失败,搞个川川",
);
break;
case IndicatorStatus.NoMoreLoad:
widget = Text("没有了,不要拖了");
break;
case IndicatorStatus.Empty:
widget = EmptyWidget(
"这里只有空",
);
break;
}
widget = Container(
width: double.infinity,
height: full ? double.infinity : height,
child: widget,
color: Colors.grey[200],
alignment: Alignment.center);
// if (isSliver) {
// if (status == IndicatorStatus.FullScreenBusying) {
// widget = SliverFillRemaining(
// child: widget,
// );
// } else if (status == IndicatorStatus.Empty) {
// widget = SliverToBoxAdapter(
// child: widget,
// );
// }
// }
if (status == IndicatorStatus.Error) {
widget = GestureDetector(
onTap: () {
listSourceRepository.loadMore();
},
child: widget,
);
}
return widget;
}
}
复制代码
如果你想要其他效果或者有什么不明白的地方,都请告诉我。
自此,妈妈再也不会担心我不会处理Flutter的列表了。