在我軟的UWP裏面有一個接口ISupportIncrementalLoading 只要你的集合繼承這個,而且實現裏面的方法,就能自動實現加載更多的這個動做。說白了就是UWP裏面UI列表控件跟集合一個契約。git
在Flutter裏面沒有這種相似的東西,可是實際項目裏面會出現大量的列表須要加載更多。 github
FlutterCandies QQ羣:181398081 不哭乖站起來繼續擼代碼,Flutter bug builder 立刻上代碼。無圖無真相,先上一個圖。 json
首先咱們也來定義個 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 方便後面繼承api
3個重要的方法: 用於加載更多markdown
Future<bool> loadMore() async
複製代碼
用於刷新(重置列表)app
Future<bool> onRefresh() async
複製代碼
用於獲取數據,loadmore會調用這個方法,通常咱們override的這個方法,loadmore裏面有一些狀態控制,若是你須要overrdie loadmore方法,注意查看下以前裏面的狀態控制代碼less
Future<bool> loadData() async
複製代碼
3個重要的屬性: hasMore 判斷是否還有更多 isLoading 判斷是否正在獲取數據 indicatorStatus 判斷當前列表的狀態async
_LoadingMoreBloc 能夠經過這個類來通知streambuilder更新UIide
下面是如何繼承使用這個base 類svg
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;
}
}
複製代碼
最後放上 Github loading_more_list,若是你想要其餘效果或者有什麼不明白的地方,都請告訴我。
自此,媽媽不再會擔憂我不會處理Flutter的列表了。Fluter 花樣下拉刷新 + Flutter 輕鬆構建加載更多(loading more) 5分鐘上手Flutter列表