炫意html5
最早CSS3和HTML5移动技术网站之一

Flutter ListView 分页加载更多效果

对于REST API接口,大部分情况下都得处理分页问题。

分页可以让我们把大量数据分割成一个个小段,分次加载。这样可以有效避免因为一次load全部数据而导致客户端变慢的问题。

本文介绍Flutter ListView组件的分页实现,数据来源于HTTP请求。当用户下拉时,APP自动加载更多数据。

1 创建 Stateful Widgets

首先,要为 ListView 绑定为 Stateful 组件,以便能加载新数据。

ListView的状态(state)信息可以在组件创建时异步加载,也能在组件的生命周期里面用setState修改。

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String nextPage = https://swapi.co/api/people;
ScrollController _scrollController = new ScrollController();
bool isLoading = false;
List names = new List();
….
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(Pagination),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomPadding: false,
);
}
}

这里,当我们下拉但是还未收到数据时,resizeToAvoidBottomPadding 可以保留出一个空位,loading效果就能显示在这个空位中。

2 使用dio拉取HTTP接口数据

为模拟实际场景,我们这里从API接口拉取数据,以让我们更好地理解Flutter的分页机制。

这里我们用公开的SWAPI接口,这个接口支持分页返回人名。

2.1 添加依赖

dependencies:
# ....
dio: 2.0.22 # check for latest version.

dio是Dart的Http客户端,支持拦截器、全局配置、表单数据、请求撤销、文件下载和超时等。

2.2 请求数据方法

因为 SWAPI接口返回数据包含下一页URL,所以数据请求方法我们这样写:

  ffinal dio = new Dio();
void _getMoreData() async {
if (!isLoading) {
setState(() {
isLoading = true;
});
final response = await dio.get(nextPage);
nextPage = response.data['next'];
setState(() {
isLoading = false;
names.addAll(response.data['results']);
});
}
}

_getMoreData 方法中,我们通过网络请求来获取列表的更多数据。

3 加载更多数据

接着我们来看看 scrollController 组件。scrollController 是一个滑动监听组件,这里我们用来控制何时加载数据。

当我们滑动界面时,如果没有达到尾页,并且数据不是正在加载中状态,scrollController 就会检查当前视图所在的位置来决定是否加载更多数据。

@override
void initState() {
this._getMoreData();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}

ListView 支持 scrollController 事件绑定,当用户在ListView中滑动时,会出发 scrollController 事件。

现在,当我们滑动到单个 ListView 底部时,_getMoreData() 会获取新数据,并保存到组件的state中。

4 显示ListView加载状态

在ListView builder中,我们使用一个技巧来让最后一行显示加载条(ProgressBar)。

Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 0.0,
child: new CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
//+1 for progressbar
itemCount: names.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == names.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
title: Text((names[index]['name'])),
onTap: () {
print(names[index]);
},
);
}
},
controller: _scrollController,
);
}

5 完整代码

import 'package:flutter/material.dart';
import 'package:dio/dio.dart';
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.<em>blue</em>,
),
home: MyHomePage(),
);
}
}
class MyHomePage extends StatefulWidget {
@override
_MyHomePageState createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
String nextPage = https://swapi.co/api/people;
ScrollController _scrollController = new ScrollController();
bool isLoading = false;
List names = new List();
final dio = new Dio();
void _getMoreData() async {
if (!isLoading) {
setState(() {
isLoading = true;
});
final response = await dio.get(nextPage);
nextPage = response.data['next'];
setState(() {
isLoading = false;
names.addAll(response.data['results']);
});
}
}
@override
void initState() {
this._getMoreData();
super.initState();
_scrollController.addListener(() {
if (_scrollController.position.pixels ==
_scrollController.position.maxScrollExtent) {
_getMoreData();
}
});
}
@override
void dispose() {
_scrollController.dispose();
super.dispose();
}
Widget _buildProgressIndicator() {
return new Padding(
padding: const EdgeInsets.all(8.0),
child: new Center(
child: new Opacity(
opacity: isLoading ? 1.0 : 00,
child: new CircularProgressIndicator(),
),
),
);
}
Widget _buildList() {
return ListView.builder(
//+1 for progressbar
itemCount: names.length + 1,
itemBuilder: (BuildContext context, int index) {
if (index == names.length) {
return _buildProgressIndicator();
} else {
return new ListTile(
title: Text((names[index]['name'])),
onTap: () {
print(names[index]);
},
);
}
},
controller: _scrollController,
);
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text(Pagination),
),
body: Container(
child: _buildList(),
),
resizeToAvoidBottomPadding: false,
);
}
}

英文原文:Flutter ListView REST API Pagination

炫意HTML5 » Flutter ListView 分页加载更多效果

Java基础教程Android基础教程