Flutter新手,从这里学到了很多东西。
在通常情况下,人们会从Web服务器请求资源,并且服务器返回对象数组:
[ { "key": "value"}, {"key": "value}...]
我们可以轻松地使用FutureBuilder进行处理。
但是我有一台具有海量数据的服务器,必须以这种方式获取资源:
所以我有一个带有某种修复计数的列表,但是必须从服务器动态加载资源。怎么做?
一些代码。
@override
Widget build(BuildContext context) {
// _max == -1, request in progress.
return _max == -1
? new CircularProgressIndicator()
: ListView.builder(
padding: const EdgeInsets.all(16.0),
itemBuilder: (context, i) {
return new FutureBuilder(
future: _getFollowingContracts(),
builder: (context, snapshot) {
if (i.isOdd) return new Divider();
switch (snapshot.connectionState) {
case ConnectionState.none:
case ConnectionState.waiting:
case ConnectionState.active:
return new Text('loading...');
case ConnectionState.done:
if (i.isOdd)
return new Divider(
height: 2.0,
);
final int index = i ~/ 2;
// when user scroll down here we got exception
// because only 20 records is available.
// how to get another 20 records?
return _buildRow(_contracts[index]);
}
},
);
},
itemCount: _max * 2,
);
}
答案 0 :(得分:2)
要实现此目的,您可以将ScrollController
附加到ListView
上并收听其更改。在此侦听器中,当您处于滚动末尾时,将获取更多数据并更新您的应用状态。
示例:
以下代码摘自这篇经充分说明的博客文章:https://marcinszalek.pl/flutter/infinite-dynamic-listview/。
import 'dart:async';
import 'package:flutter/material.dart';
void main() => runApp(new MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return new MaterialApp(
theme: new ThemeData(primarySwatch: Colors.blue),
home: new MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
List<int> items = List.generate(10, (i) => i);
ScrollController _scrollController = new ScrollController();
bool isPerformingRequest = false;
@override
void initState() {
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
_getMoreData() async {
if (!isPerformingRequest) {
setState(() => isPerformingRequest = true);
List<int> newEntries = await fakeRequest(
items.length, items.length + 10); //returns empty list
if (newEntries.isEmpty) {
double edge = 50.0;
double offsetFromBottom = _scrollController.position.maxScrollExtent -
_scrollController.position.pixels;
if (offsetFromBottom < edge) {
_scrollController.animateTo(
_scrollController.offset - (edge - offsetFromBottom),
duration: new Duration(milliseconds: 500),
curve: Curves.easeOut);
}
}
setState(() {
items.addAll(newEntries);
isPerformingRequest = false;
});
}
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isPerformingRequest ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
@override
Widget build(BuildContext context) {
return new Scaffold(
appBar: AppBar(
title: Text("Infinite ListView"),
),
body: ListView.builder(
itemCount: items.length + 1,
itemBuilder: (context, index) {
if (index == items.length) {
return _buildProgressIndicator();
} else {
return ListTile(title: new Text("Number $index"));
}
},
controller: _scrollController,
),
);
}
}
/// from - inclusive, to - exclusive
Future<List<int>> fakeRequest(int from, int to) async {
return Future.delayed(Duration(seconds: 2), () {
return List.generate(to - from, (i) => i + from);
});
}
您将不得不使此代码适应您的情况(主要是通过更改fakeRequest
方法以及如何渲染图块),但是我认为它将为您提供主要思想。
答案 1 :(得分:1)
您要寻找的是“无限滚动”。有Flutter教程对此进行了详细解释,在这里,我发现一种常见的模式效果很好。
import 'dart:async';
import 'package:flutter/services.dart';
import 'package:flutter/material.dart';
class Feed extends StatefulWidget {
@override
State<StatefulWidget> createState() => new _FeedState();
}
class _FeedState extends State<Feed> {
final GlobalKey<ScaffoldState> _scaffoldKey = new GlobalKey<ScaffoldState>();
final GlobalKey<RefreshIndicatorState> _refreshIndicatorKey =
new GlobalKey<RefreshIndicatorState>();
bool _initialized;
bool _readyToFetchNextPage;
ScrollController _scrollController;
Repository _repository;
int _page;
Map<int, Widget> _cache;
@override
void initState() {
_initialized = false;
_repository = Repository();
_readyToFetchNextPage = true;
_initialize();
super.initState();
}
Future<Null> _initialize() async {
_initialized = false;
_readyToFetchNextPage = false;
final Completer<Null> completer = new Completer<Null>();
// Reset cache
_cache = new Map();
// Reset page
_page = 1;
// Fetch initial data
List results = repository.getPage(_page);
// Add results to the cache
_update(_cache.length, results);
completer.complete();
// Let the refresh indicator know the method has completed
_readyToFetchNextPage = true;
// SetState must be called to dismiss the CircularProgressIndicator
setState(() { _initialized = true; });
return completer.future;
}
@override
Widget build(BuildContext context) {
return Scaffold(
key: _scaffoldKey,
body: new RefreshIndicator(
key: _refreshIndicatorKey,
onRefresh: _initialize,
child: CustomScrollView(
slivers: <Widget>[
AppBar(),
(_initialized) ? new SliverList(
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
return _getItem(index);
}),
) : new SliverFillRemaining(
child: new Center(
child: new CircularProgressIndicator(),
),
),
],
),
),
);
}
Widget _getItem(int index) {
if (_cache.containsKey(index)) {
return _cache[index];
} else {
if (_readyToFetchNextPage) {
_readyToFetchNextPage = false;
_page += 1;
_repository.getPage(_page).then((results) {
setState(() {
_update(_cache.length, results);
});
});
}
return null;
}
}
void _update(int offset, List results) {
for (int i = 0; i < results.length; i += 1) {
int index = i + offset;
_cache.putIfAbsent(
index,
() => new ListTile(
key: new Key('post-$index'),
title: Text(results[i]),
));
}
_readyToFetchNextPage = true;
}
}
其工作原理如下。
Repository
,它会根据页面编号来调用API
例如:repository.getPage(1)=> GET http://api.com?page=1 =>列表
_update
运行它们_update
将所有这些帖子添加到缓存(地图)。ScrollView
使用一个名为getItem(int index)
的函数,该函数必须为给定的索引返回一个Widget
。getItem
函数在缓存中查找该项目,如果找到该项目,则将其返回。Repository
获取下一页并更新缓存,依此类推... 您当然应该考虑使用此模式,因为它将允许您按需获取新页面。
重要”这不是直接的实现。我在此代码示例中使用的Repository
旨在向您显示在哪里进行API调用。它不是现有的类,而是要实现的存储库的抽象表示。此外,您将指定调用Widget
_cache.putIfAbsent(index, () => MyWidget())