Flutter ListView 分页加载更多效果
对于RESTAPI接口,大部分情况下都得处理分页问题。
分页可以让我们把大量数据分割成一个个小段,分次加载。这样可以有效避免因为一次load全部数据而导致客户端变慢的问题。
本文介绍FlutterListView组件的分页实现,数据来源于HTTP请求。当用户下拉时,APP自动加载更多数据。
1创建StatefulWidgets
首先,要为ListView绑定为Stateful组件,以便能加载新数据。
ListView的状态(state)信息可以在组件创建时异步加载,也能在组件的生命周期里面用setState修改。
import'package:flutter/material.dart';
import'package:dio/dio.dart';
voidmain()=>runApp(MyApp());
classMyAppextendsStatelessWidget{
@override
Widgetbuild(BuildContextcontext){
returnMaterialApp(
title:'FlutterDemo',
theme:ThemeData(
primarySwatch:Colors.blue,
),
home:MyHomePage(),
);
}
}
classMyHomePageextendsStatefulWidget{
@override
_MyHomePageStatecreateState()=>_MyHomePageState();
}
class_MyHomePageStateextendsState<MyHomePage>{
StringnextPage="https://swapi.co/api/people";
ScrollController_scrollController=newScrollController();
boolisLoading=false;
Listnames=newList();
….
@override
Widgetbuild(BuildContextcontext){
returnScaffold(
appBar:AppBar(
title:constText("Pagination"),
),
body:Container(
child:_buildList(),
),
resizeToAvoidBottomPadding:false,
);
}
}
这里,当我们下拉但是还未收到数据时,resizeToAvoidBottomPadding可以保留出一个空位,loading效果就能显示在这个空位中。
2使用dio拉取HTTP接口数据
为模拟实际场景,我们这里从API接口拉取数据,以让我们更好地理解Flutter的分页机制。
这里我们用公开的SWAPI接口,这个接口支持分页返回人名。
2.1添加依赖
dependencies: #.... dio:2.0.22#checkforlatestversion.
dio是Dart的Http客户端,支持拦截器、全局配置、表单数据、请求撤销、文件下载和超时等。
2.2请求数据方法
因为SWAPI接口返回数据包含下一页URL,所以数据请求方法我们这样写:
ffinaldio=newDio();
void_getMoreData()async{
if(!isLoading){
setState((){
isLoading=true;
});
finalresponse=awaitdio.get(nextPage);
nextPage=response.data['next'];
setState((){
isLoading=false;
names.addAll(response.data['results']);
});
}
}
在_getMoreData方法中,我们通过网络请求来获取列表的更多数据。
3加载更多数据
接着我们来看看scrollController组件。scrollController是一个滑动监听组件,这里我们用来控制何时加载数据。
当我们滑动界面时,如果没有达到尾页,并且数据不是正在加载中状态,scrollController就会检查当前视图所在的位置来决定是否加载更多数据。
@override
voidinitState(){
this._getMoreData();
super.initState();
_scrollController.addListener((){
if(_scrollController.position.pixels==
_scrollController.position.maxScrollExtent){
_getMoreData();
}
});
}
@override
voiddispose(){
_scrollController.dispose();
super.dispose();
}
ListView支持 scrollController 事件绑定,当用户在ListView中滑动时,会出发scrollController事件。
现在,当我们滑动到单个ListView底部时,_getMoreData()会获取新数据,并保存到组件的state中。
4显示ListView加载状态
在ListViewbuilder中,我们使用一个技巧来让最后一行显示加载条(ProgressBar)。
Widget_buildProgressIndicator(){
returnnewPadding(
padding:constEdgeInsets.all(8.0),
child:newCenter(
child:newOpacity(
opacity:isLoading?1.0:0.0,
child:newCircularProgressIndicator(),
),
),
);
}
Widget_buildList(){
returnListView.builder(
//+1forprogressbar
itemCount:names.length+1,
itemBuilder:(BuildContextcontext,intindex){
if(index==names.length){
return_buildProgressIndicator();
}else{
returnnewListTile(
title:Text((names[index]['name'])),
onTap:(){
print(names[index]);
},
);
}
},
controller:_scrollController,
);
}
5完整代码
import'package:flutter/material.dart';
import'package:dio/dio.dart';
voidmain()=>runApp(MyApp());
classMyAppextendsStatelessWidget{
@override
Widgetbuild(BuildContextcontext){
returnMaterialApp(
title:'FlutterDemo',
theme:ThemeData(
primarySwatch:Colors.blue,
),
home:MyHomePage(),
);
}
}
classMyHomePageextendsStatefulWidget{
@override
_MyHomePageStatecreateState()=>_MyHomePageState();
}
class_MyHomePageStateextendsState<MyHomePage>{
StringnextPage="https://swapi.co/api/people";
ScrollController_scrollController=newScrollController();
boolisLoading=false;
Listnames=newList();
finaldio=newDio();
void_getMoreData()async{
if(!isLoading){
setState((){
isLoading=true;
});
finalresponse=awaitdio.get(nextPage);
nextPage=response.data['next'];
setState((){
isLoading=false;
names.addAll(response.data['results']);
});
}
}
@override
voidinitState(){
this._getMoreData();
super.initState();
_scrollController.addListener((){
if(_scrollController.position.pixels==
_scrollController.position.maxScrollExtent){
_getMoreData();
}
});
}
@override
voiddispose(){
_scrollController.dispose();
super.dispose();
}
Widget_buildProgressIndicator(){
returnnewPadding(
padding:constEdgeInsets.all(8.0),
child:newCenter(
child:newOpacity(
opacity:isLoading?1.0:00,
child:newCircularProgressIndicator(),
),
),
);
}
Widget_buildList(){
returnListView.builder(
//+1forprogressbar
itemCount:names.length+1,
itemBuilder:(BuildContextcontext,intindex){
if(index==names.length){
return_buildProgressIndicator();
}else{
returnnewListTile(
title:Text((names[index]['name'])),
onTap:(){
print(names[index]);
},
);
}
},
controller:_scrollController,
);
}
@override
Widgetbuild(BuildContextcontext){
returnScaffold(
appBar:AppBar(
title:constText("Pagination"),
),
body:Container(
child:_buildList(),
),
resizeToAvoidBottomPadding:false,
);
}
}
英文原文:FlutterListViewRESTAPIPagination