如何为无尽的listview实现项目延迟加载?当用户滚动到列表视图的末尾时,我想通过网络加载更多项目。
答案 0 :(得分:32)
您可以收听ScrollController
。
ScrollController
包含一些有用的信息,例如scrolloffset和ScrollPosition
列表。
在您的情况下,有趣的部分位于controller.position
,这是当前可见的ScrollPosition
。它代表可滚动的一部分。
ScrollPosition
包含有关它在可滚动内部的位置的信息。例如extentBefore
和extentAfter
。或者它的大小,extentInside
。
考虑到这一点,您可以根据extentAfter
触发服务器调用,该调用表示剩余的可用滚动空间。
这是使用我所说的基本示例。
class MyHome extends StatefulWidget {
@override
_MyHomeState createState() => new _MyHomeState();
}
class _MyHomeState extends State<MyHome> {
ScrollController controller;
List<String> items = new List.generate(100, (index) => 'Hello $index');
@override
void initState() {
super.initState();
controller = new ScrollController()..addListener(_scrollListener);
}
@override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
@override
Widget build(BuildContext context) {
return new Scaffold(
body: new Scrollbar(
child: new ListView.builder(
controller: controller,
itemBuilder: (context, index) {
return new Text(items[index]);
},
itemCount: items.length,
),
),
);
}
void _scrollListener() {
print(controller.position.extentAfter);
if (controller.position.extentAfter < 500) {
setState(() {
items.addAll(new List.generate(42, (index) => 'Inserted $index'));
});
}
}
}
您可以清楚地看到,当到达滚动结尾时,由于加载了更多项目,滚动条会消耗掉。
答案 1 :(得分:10)
感谢RémiRousselet的方法,但并不能解决所有问题。特别是当ListView滚动到底部时,它仍会多次调用scrollListener。更好的方法是将Notification Listener与Remi的方法结合使用。这是我的解决方案:
bool _handleScrollNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
if (_controller.position.extentAfter == 0) {
loadMore();
}
}
return false;
}
@override
Widget build(BuildContext context) {
final Widget gridWithScrollNotification = NotificationListener<
ScrollNotification>(
onNotification: _handleScrollNotification,
child: GridView.count(
controller: _controller,
padding: EdgeInsets.all(4.0),
// Create a grid with 2 columns. If you change the scrollDirection to
// horizontal, this would produce 2 rows.
crossAxisCount: 2,
crossAxisSpacing: 2.0,
mainAxisSpacing: 2.0,
// Generate 100 Widgets that display their index in the List
children: _documents.map((doc) {
return GridPhotoItem(
doc: doc,
);
}).toList()));
return new Scaffold(
key: _scaffoldKey,
body: RefreshIndicator(
onRefresh: _handleRefresh, child: gridWithScrollNotification));
}
答案 2 :(得分:2)
这是我的方法,灵感来自上述答案,
NotificationListener(onNotification: _onScrollNotification, child: GridView.builder())
bool _onScrollNotification(ScrollNotification notification) {
if (notification is ScrollEndNotification) {
final before = notification.metrics.extentBefore;
final max = notification.metrics.maxScrollExtent;
if (before == max) {
// load next page
// code here will be called only if scrolled to the very bottom
}
}
return false;
}
答案 3 :(得分:1)
该解决方案使用ScrollController,我看到了有关页面的评论。
我想分享一下我对增量增量包的发现_loading_listview
https://github.com/MaikuB/incrementally_loading_listview。
如包装所述:这可用于加载从API请求接收的分页数据。
基本上,当ListView构建最后一个项目时,这意味着用户已向下滚动到底部。
希望它可以帮助有类似问题的人。
出于演示目的,我更改了示例以使一个页面仅包含一项
并添加一个CircularProgressIndicator。
...
bool _loadingMore;
bool _hasMoreItems;
int _maxItems = 30;
int _numItemsPage = 1;
...
_hasMoreItems = items.length < _maxItems;
...
return IncrementallyLoadingListView(
hasMore: () => _hasMoreItems,
itemCount: () => items.length,
loadMore: () async {
// can shorten to "loadMore: _loadMoreItems" but this syntax is used to demonstrate that
// functions with parameters can also be invoked if needed
await _loadMoreItems();
},
onLoadMore: () {
setState(() {
_loadingMore = true;
});
},
onLoadMoreFinished: () {
setState(() {
_loadingMore = false;
});
},
loadMoreOffsetFromBottom: 0,
itemBuilder: (context, index) {
final item = items[index];
if ((_loadingMore ?? false) && index == items.length - 1) {
return Column(
children: <Widget>[
ItemCard(item: item),
Card(
child: Padding(
padding: const EdgeInsets.all(16.0),
child: Column(
children: <Widget>[
Row(
crossAxisAlignment:
CrossAxisAlignment.start,
children: <Widget>[
Container(
width: 60.0,
height: 60.0,
color: Colors.grey,
),
Padding(
padding: const EdgeInsets.fromLTRB(
8.0, 0.0, 0.0, 0.0),
child: Container(
color: Colors.grey,
child: Text(
item.name,
style: TextStyle(
color: Colors.transparent),
),
),
)
],
),
Padding(
padding: const EdgeInsets.fromLTRB(
0.0, 8.0, 0.0, 0.0),
child: Container(
color: Colors.grey,
child: Text(
item.message,
style: TextStyle(
color: Colors.transparent),
),
),
)
],
),
),
),
Center(child: CircularProgressIndicator())
],
);
}
return ItemCard(item: item);
},
);
完整示例https://github.com/MaikuB/incrementally_loading_listview/blob/master/example/lib/main.dart
包装使用ListView索引=最后一项,并使用loadMoreOffsetFromBottom来检测何时加载更多商品。
itemBuilder: (itemBuilderContext, index) {
if (!_loadingMore &&
index ==
widget.itemCount() -
widget.loadMoreOffsetFromBottom -
1 &&
widget.hasMore()) {
_loadingMore = true;
_loadingMoreSubject.add(true);
}
答案 4 :(得分:1)
如果要实现上下方向的延迟加载,则发布的解决方案无法解决问题。滚动会跳到此处,请参见this thread。
如果您想上下方向进行延迟加载,则库bidirectional_listview可能会有所帮助。
示例(Source):
static const double kItemHeight = 30.0;
BidirectionalScrollController controller;
double oldScrollPosition = 0.0;
@override
void initState() {
super.initState();
for (int i = -10; i <= 10; i++) {
items[i] = "Item " + i.toString();
}
controller = new BidirectionalScrollController()
..addListener(_scrollListener);
}
@override
void dispose() {
controller.removeListener(_scrollListener);
super.dispose();
}
@override
void build() {
// ...
List<int> keys = items.keys.toList();
keys.sort();
return new BidirectionalListView.builder(
controller: controller,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index) {
return Container(
child: Text(items[index]),
height: kItemHeight,
},
itemCount: keys.first,
negativeItemCount: keys.last.abs(),
);
// ...
}
// Reload new items in up and down direction and update scroll boundaries
void _scrollListener() {
bool scrollingDown = oldScrollPosition < controller.position.pixels;
List<int> keys = items.keys.toList();
keys.sort();
int negativeItemCount = keys.first.abs();
int itemCount = keys.last;
double positiveReloadBorder = (itemCount * kItemHeight - 3 * kItemHeight);
double negativeReloadBorder =
(-(negativeItemCount * kItemHeight - 3 * kItemHeight));
// reload items
bool rebuildNecessary = false;
if (scrollingDown && controller.position.pixels > positiveReloadBorder)
{
for (int i = itemCount + 1; i <= itemCount + 20; i++) {
items[i] = "Item " + i.toString();
}
rebuildNecessary = true;
} else if (!scrollingDown &&
controller.position.pixels < negativeReloadBorder) {
for (int i = -negativeItemCount - 20; i < -negativeItemCount; i++) {
items[i] = "Item " + i.toString();
}
rebuildNecessary = true;
}
// set new scroll boundaries
try {
BidirectionalScrollPosition pos = controller.position;
pos.setMinMaxExtent(
-negativeItemCount * kItemHeight, itemCount * kItemHeight);
} catch (error) {
print(error.toString());
}
if (rebuildNecessary) {
setState(({});
}
oldScrollPosition = controller.position.pixels;
}
我希望这对一些人有帮助:-)
答案 5 :(得分:1)
这是我找到listView结尾的解决方案
_scrollController.addListener(scrollListenerMilli);
if (_scrollController.position.pixels == _scrollController.position.maxScrollExtent) {
getMoreData();
}
如果您想在1/2或3/4之后延迟加载,请像这样更改。
if (_scrollController.position.pixels == (_scrollController.position.maxScrollExtent * .75)) {//.5
getMoreData();
}
答案 6 :(得分:1)
接受的答案是正确的,但您也可以执行以下操作,
Timer _timer;
Widget chatMessages() {
_timer = new Timer(const Duration(milliseconds: 300), () {
_scrollController.animateTo(
_scrollController.position.maxScrollExtent,
curve: Curves.easeOut,
duration: const Duration(milliseconds: 300),
);
});
return StreamBuilder(
stream: chats,
builder: (context, snapshot) {
return snapshot.hasData
? ListView.builder(
// physics: NeverScrollableScrollPhysics(),
controller: _scrollController,
shrinkWrap: true,
reverse: false,
itemCount: snapshot.data.documents.length,
itemBuilder: (context, index) {
return MessageTile(
message: snapshot.data.documents[index].data["message"],
sendByMe: widget.sendByid ==
snapshot.data.documents[index].data["sendBy"],
);
})
: Container();
},
);
}
答案 7 :(得分:0)
我使用https://pub.dev/packages/loadany处理更多的加载,因为他支持ListView,GridView,ScrollView
答案 8 :(得分:0)
答案 9 :(得分:-1)
使用lazy_load_scrollview:1.0.0软件包,在熊猫世界在这里回答的幕后使用了相同的概念。该软件包使其更易于实现。