本教程完成一个有加载更多的ListView,最终效果如下图所示:
开始
首先我们只在列表中展示10个整数。
class MyHomePage extends StatefulWidget { @override _MyHomePageState createState() => _MyHomePageState();}class _MyHomePageState extends State{ List 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参数,然后返回他们之间的数。我们将添加延迟时间,这样看起来更像是网络加载。具体代码如下所示:
/// from - 包括, to - 不包括/// 通过这个模拟http请求Future> fakeRequest(int from, int to) async {// 如果对Future不熟悉,可以参考 https://juejin.im/post/5b2c67a351882574a756f2eb return Future.delayed(Duration(seconds: 2), () { return List.generate(to - from, (i) => i + from); });}复制代码
当用户将列表滚动到最底,我们将会调用上面的方法。为了监听列表是否已经滚动到最底,最简单的方式就是给列表添加一个ScrollController
,当列表滚动的时候,就会发出一个请求。但是防止频繁的发送http请求,我们需要添加一个变量isPerformingRequest
,表示是否有请求正在进行。只有当isPerformingRequest
为false时,才能开始一个新的请求。
class _MyHomePageState extends State{ List 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 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, ), ); }}复制代码
如果你现在运行代码,你可以看数据可以动态的加载,但是这个距离我们最终想要的效果还有差距。我们需要添加一些提示,告知用户数据正在加载中。
进度提示
相关的组件是CircularProgressIndicator
,他被Center、Opacity、Padding所包裹。我们将用Opacity
组件控制CircularProgressIndicator
的显示,当请求正在进行中时显示。整个组件的代码如下所示:
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上:
@overrideWidget 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
一些动画。
_getMoreData() async { if (!isPerformingRequest) { setState(() => isPerformingRequest = true); List newEntries = await fakeRequest(items.length, items.length); //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; }); }}复制代码下面是完整的代码:
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{ List 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 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 - exclusiveFuture > fakeRequest(int from, int to) async { return Future.delayed(Duration(seconds: 2), () { return List.generate(to - from, (i) => i + from); });}复制代码
有任何问题,欢迎大家提问