本文来自于我自己学习Flutter时所学习的教程的中文翻译,原文链接INFINITE DYNAMIC LISTVIEW
英语水平有限,内容未必准确
在这篇文章中,我将快速介绍如何做一个无限的列表(ListView),当用户滑动到最底端时可以动态的加载更多数据。最终结果就像下面这样:
让我们开始吧!
起点
让我们从一个简单的包含10个数字的列表开始:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState(); }
class _MyHomePageState extends State<MyHomePage> { List<int> items = List.generate(10, (i) => i);
@override void initState() { super.initState(); }
@override void dispose() { super.dispose(); }
@override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar( title: Text("Infinite ListView"), ), body: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile(title: new Text("Number $index")); }, ), ); } }
|
动态数据加载
首先,我们需要创建一个方法模仿http请求。假设我们通过传递from
和to
两个参数,就可以得到一个在这两个数之间的结果。我们要加些延迟让这个方法更”网络”。方法看起来像下面这样:
1 2 3 4 5 6
| Future<List<int>> fakeRequest(int from, int to) async { return Future.delayed(Duration(seconds: 2), () { return List.generate(to - from, (i) => i + from); }); }
|
我们希望当用户滑动ListView
到最底部的时候能调用这么方法。最简单的方法就是使用ScrollController
。ScrollController
会监听滑动行为,当用户滑动到最底部时我们让它发起一个请求。当正在发送请求时,注意预防我们的应用频繁发送(不等上一个请求返回就再发起一个请求)。我的方案是增加一个标识isPerformingRequest
,当只有这个标识为false的时候才能发起一个请求。这部分的代码就像下面这样:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48
| 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); setState(() { items.addAll(newEntries); isPerformingRequest = false; }); } }
@override Widget build(BuildContext context) { return new Scaffold( appBar: AppBar( title: Text("Infinite ListView"), ), body: ListView.builder( itemCount: items.length, itemBuilder: (context, index) { return ListTile(title: new Text("Number $index")); }, controller: _scrollController, ), ); } }
|
当我们运行app时,我们可以看到数据被动态加载了。然而,这个远没达到满意的效果。我们需要添加某种指示器来告知用户请求完成了。
进度指示器
我们主要的组件应该是CircularProgressIndicator
,它应该被包在Center
,Opacity
和Padding
组件中。当请求被发起时,我们准备使用Opacity
组件显示我们的指示器。整个组件看起来是这样:
1 2 3 4 5 6 7 8 9 10 11
| 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(), ), ), ); }
|
最后一步是把组件添加到ListView中:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| @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, ), ); }
|
最终效果应该像这样:
处理空数据
作为奖励,我将展示一个当没有数据从请求中返回时的简单处理方法。我们需要做的就是用ScrollController
让我们的ListView
来点动画:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| _getMoreData() async { if (!isPerformingRequest) { setState(() => isPerformingRequest = true); List<int> newEntries = await fakeRequest(items.length, items.length); 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; }); } }
|
注意我们是如何检查在响应返回前用户没有向上滚动的。我们用offsetFromBottom
和edge
进行比较还确定这件事。
然后我们就完成了,亲爱的!🙂
在这里可以找到包含了所有类的gist。
如果你对怎样更好的实现有任何问题或者建议,我强烈鼓励你留下评论。
干杯🙂