在颤动中使用 ListView 进行分页
Posted
技术标签:
【中文标题】在颤动中使用 ListView 进行分页【英文标题】:Pagination with ListView in flutter 【发布时间】:2019-05-15 12:58:54 【问题描述】:我是 Flutter 的新手,我一直在寻找合适的分页结果 2 天。
我遵循了这个代码Flutter ListView lazy loading 但没有达到我想要的。看下面的代码。
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
main() => runApp(InitialSetupPage());
class InitialSetupPage extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: "API Pagination",
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: HomePage(),
);
class HomePage extends StatefulWidget
@override
_HomePageState createState() => _HomePageState();
class _HomePageState extends State<HomePage>
int pageNum = 1;
bool isPageLoading = false;
List<Map<String, dynamic>> arrayOfProducts;
Future<List<Map<String, dynamic>>> _callAPIToGetListOfData() async
isPageLoading = true;
final responseDic =
await http.post(urlStr, headers: accessToken, body: paramDic);
Map<String, dynamic> dicOfRes = json.decode(responseDic.body);
List<Map<String, dynamic>> temArr = List<Map<String, dynamic>>.from(dicOfRes["data"]["products"]);
if (pageNum == 1)
arrayOfProducts = temArr;
else
arrayOfProducts.addAll(temArr);
return arrayOfProducts;
ScrollController controller;
@override
void initState()
controller = new ScrollController()..addListener(_scrollListener);
super.initState();
_scrollListener()
print(controller.position.extentAfter);
if (controller.position.extentAfter <= 0 && isPageLoading == false)
_callAPIToGetListOfData().then((data)
setState(()
);
);
@override
void dispose()
controller.removeListener(_scrollListener);
super.dispose();
@override
Widget build(BuildContext context)
return Scaffold(
appBar: AppBar(
title: Text("Online Products"),
centerTitle: true,
),
body: Container(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: _callAPIToGetListOfData(),
builder: (BuildContext context, AsyncSnapshot snapshot)
switch (snapshot.connectionState)
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError)
Text(
'YOu have some error : $snapshot.hasError.toString()');
else if (snapshot.data != null)
isPageLoading = false;
pageNum++;
print(arrayOfProducts);
return Scrollbar(
child: ListView.builder(
itemCount: arrayOfProducts.length,
controller: controller,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index)
return ListTile(
title: Text(
'$index $arrayOfProducts[index]['title']'),
);
),
);
),
));
所以,当我到达页面底部时,我的_scrollListener
方法得到调用,我已经设置setState(()....
方法来重新加载小部件。问题是我加载了我的实际位置,它从列表顶部开始。那么我哪里错了?其实我想要喜欢。 https://github.com/istyle-inc/LoadMoreTableViewController/blob/master/screen.gif
编辑:
最终代码: (@Rémi Rousselet 指导)
import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;
import 'dart:async';
import 'dart:convert';
main() => runApp(InitialSetupPage());
class InitialSetupPage extends StatelessWidget
@override
Widget build(BuildContext context)
return MaterialApp(
title: "API Pagination",
debugShowCheckedModeBanner: false,
theme: ThemeData(primarySwatch: Colors.green),
home: HomePage(),
);
class HomePage extends StatefulWidget
@override
_HomePageState createState() => _HomePageState();
class _HomePageState extends State<HomePage>
int pageNum = 1;
bool isPageLoading = false;
List<Map<String, dynamic>> arrayOfProducts;
ScrollController controller;
Future<List<Map<String, dynamic>>> future;
int totalRecord = 0;
@override
void initState()
controller = new ScrollController()..addListener(_scrollListener);
future = _callAPIToGetListOfData();
super.initState();
@override
void dispose()
controller.removeListener(_scrollListener);
super.dispose();
@override
Widget build(BuildContext context)
final constListView = ListView.builder(
itemCount: arrayOfProducts == null ? 0 : arrayOfProducts.length,
controller: controller,
physics: AlwaysScrollableScrollPhysics(),
itemBuilder: (context, index)
return Column(
children: <Widget>[
ListTile(
title: Text('$index $arrayOfProducts[index]['title']'),
leading: CircleAvatar(backgroundImage: NetworkImage(arrayOfProducts[index]['thumbnail'] ?? "")),
),
Container(
color: Colors.black12,
height: (index == arrayOfProducts.length-1 && totalRecord > arrayOfProducts.length) ? 50 : 0,
width: MediaQuery.of(context).size.width,
child:Center(
child: CircularProgressIndicator()
),
)
],
);
);
return Scaffold(
appBar: AppBar(
title: Text("Online Products"),
centerTitle: true,
),
body: Container(
child: FutureBuilder<List<Map<String, dynamic>>>(
future: future,
builder: (BuildContext context, AsyncSnapshot snapshot)
switch (snapshot.connectionState)
case ConnectionState.none:
case ConnectionState.active:
case ConnectionState.waiting:
return Center(child: CircularProgressIndicator());
case ConnectionState.done:
if (snapshot.hasError)
Text(
'YOu have some error : $snapshot.hasError.toString()');
else if (snapshot.data != null)
isPageLoading = false;
print(arrayOfProducts);
return constListView;
),
));
Future<List<Map<String, dynamic>>> _callAPIToGetListOfData() async
isPageLoading = true;
final responseDic =
await http.post(urlStr, headers: accessToken, body: paramDic);
Map<String, dynamic> dicOfRes = json.decode(responseDic.body);
List<Map<String, dynamic>> temArr =
List<Map<String, dynamic>>.from(dicOfRes["data"]["products"]);
setState(()
if (pageNum == 1)
totalRecord = dicOfRes["total_record"];
print('============>>>>>>> $totalRecord');
arrayOfProducts = temArr;
else
arrayOfProducts.addAll(temArr);
pageNum++;
);
return arrayOfProducts;
_scrollListener()
if (totalRecord == arrayOfProducts.length)
return;
print(controller.position.extentAfter);
if (controller.position.extentAfter <= 0 && isPageLoading == false)
_callAPIToGetListOfData();
那行得通,但它是正确/好方法吗?有点困惑,因为如果我到达页面末尾并向上/向下滚动,滚动时似乎有点粘..
【问题讨论】:
为什么不使用itemBuilder
来延迟加载数据?
我已经用过请检查一下。
不,您正在使用ScrollController
- 我的意思是从itemBuilder
启动按需数据加载
***.com/questions/49508322/… 怎么没有做你想做的事?
@RémiRousselet 每次新页面从 ListView 的顶部开始,而不是它应该停留在我们滚动的末尾。
【参考方案1】:
将此添加到您的控制器侦听器
if (scrollController.position.maxScrollExtent == scrollController.offset)
/***
* we need to get new data on page scroll end but if the last
* time when data is returned, its count should be Constants.itemsCount' (10)
*
* So we calculate every time
*
* productList.length >= (Constants.itemsCount*pageNumber)
*
* list must contain the products == Constants.itemsCount if the page number is 1
* but if page number is increased then we need to calculate the total
* number of products we have received till now example:
* first time on page scroll if last count of productList is Constants.itemsCount
* then increase the page number and get new data.
* Now page number is 2, now we have to check the count of the productList
* if it is==Constants.itemsCount*pageNumber (20 in current case) then we have
* to get data again, if not then we assume, server has not more data then
* we currently have.
*
*/
if (productList.length >= (Constants.itemsCount * pageNumber) &&
!isLoading)
pageNumber++;
print("PAGE NUMBER $pageNumber");
print("getting data");
getProducts(false); // Hit API to get new data
【讨论】:
Constants.itemsCount 怎么得到? 这只是一个计数,这样我每次都能得到 Constant.itemsCount 个项目 scrollController.position.maxScrollExtent == scrollController.offset 这一行节省了我的时间。 +尊重【参考方案2】:https://pub.dev/packages/pagination_view 是我发现使用起来更直观的包,需要从页面视图中进行最少的更改并且感觉更直观:
从列表视图移动到分页视图很容易,保存小部件的类需要是有状态的,否则它将不起作用:
PaginationView<User>(
preloadedItems: <User>[
User(faker.person.name(), faker.internet.email()),
User(faker.person.name(), faker.internet.email()),
],
itemBuilder: (BuildContext context, User user, int index) => ListTile(
title: Text(user.name),
subtitle: Text(user.email),
leading: IconButton(
icon: Icon(Icons.person),
onPressed: () => null,
),
),
paginationViewType: PaginationViewType.listView // optional
pageFetch: pageFetch,
onError: (dynamic error) => Center(
child: Text('Some error occured'),
),
onEmpty: Center(
child: Text('Sorry! This is empty'),
),
bottomLoader: Center( // optional
child: CircularProgressIndicator(),
),
initialLoader: Center( // optional
child: CircularProgressIndicator(),
),
),
【讨论】:
【参考方案3】:https://pub.dartlang.org/packages/loadmore
您可以使用这个包,也可以查看代码并重做以满足您的需求
【讨论】:
谢谢,但我不想使用包。为了学习,我想做自己。 github.com/OpenFlutter/flutter_listview_loadmore/blob/master/… 如前面的 cmets 所说,使用类似以下代码的 itembuilder。它比使用滚动控制器要简单得多!【参考方案4】:尝试使用这个包,https://pub.dev/packages/paginate_firestore 这让我很快乐。您只需要添加 2 个属性即可!
它在 documentSnapshot 中提供输出,我在 ListView 上尝试过(效果很好)根据贡献者的说法,GridView 直到现在还没有发布。
【讨论】:
以上是关于在颤动中使用 ListView 进行分页的主要内容,如果未能解决你的问题,请参考以下文章
如何在颤动中使用listview builder制作动态单选按钮?