我是 flutter 新手,我已经寻找正确的分页结果 2 天了。
我遵循了这段代码Flutter ListView延迟加载 但没有达到我想要的。看下面的代码。
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();
}
}
}
这可行,但是这是正确/好的方法吗?有点困惑,因为如果我到达页面末尾并向上/向下滚动,滚动时似乎有点粘..
将其添加到控制器的监听器中
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
}
}
https://pub.dev/packages/pagination_view是我发现使用起来更直观的包,需要对页面视图进行最少的更改,并且感觉更直观:
从列表视图移动到 PaginationView 很容易,保存小部件的类需要是有状态的,否则将无法工作:
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(),
),
),
https://pub.dartlang.org/packages/loadmore
您可以使用这个包,或者您可以查看代码并重做它以满足您的需求
尝试使用这个包,https://pub.dev/packages/paginate_firestore 这让我很开心。您只需要添加 2 个属性即可完成!
它在 documentSnapshot 中提供输出,我在 ListView 上尝试过(效果很好)根据贡献者的说法,GridView 到目前为止尚未发布。
无需任何滚动控制器的最简单方法:
int offset = 0;
int limit = 10;
int posts = [];
getPosts(){
//get more posts and add them to posts array then increment skip = skip + limit
}
ListView.builder(
itemCount: posts.length,
itemBuilder: (BuildContext context, int index) {
if (index == posts.length - 1) {
getPosts();
}
return Column(
children: [
yourItemWidget(posts[index]),
if (index == posts.length - 1)
CupertinoActivityIndicator(),
if (index == posts.length - 1)
SizedBox(
height: 100,
)
],);
},
),
您可以通过使用
ScrollController
与 maxScrollExtent
和 offset
进行检查,或者借助 index
... 进行计算。
让我们看看第一种方法...
在班级内设置
ScrollController()
final _scrollController = ScrollController();
在
ListView
方法中设置此 ListView.builder
控制器...
controller: _scrollController
在 init 方法中添加监听器...
@override
void initState() {
super.initState();
_scrollController.addListener(_onScroll);
}
void _onScroll() {
print("list view scrolling");
}
现在你可以在
_onscroll
方法中收听 listview 滚动...
使用以下代码,您可以在
_scrollController.position.maxScrollExtent
的帮助下检查在此页面上可以滚动多少距离,并在 offset
的帮助下检查当前的滚动位置...您还可以通过以下方式玩最后一个返回 currentScroll >= (maxScroll * 0.9)
如果您想在用户触及 listview
底部之前提前获取一点,请将值 0.9 更改为 0.8 或更小...
bool get _isBottom {
if (!_scrollController.hasClients) return false;
final maxScroll = _scrollController.position.maxScrollExtent;
final currentScroll = _scrollController.offset;
return currentScroll >= (maxScroll * 0.9);
}
现在您可以在
_isButtom
方法中使用 _onScroll()
...
void _onScroll() {
if (_isBottom) FetchedMore());
//set currentPage+1 before making API call
//or set startIndex to listViewData.length
}
如果您使用某些状态管理器,即 bloc / Riverpod,请使用
context.read
来获取更多内容...否则您必须添加一些额外的逻辑来防止为同一页面发送多个 API 调用...
您可能想知道要获取哪个页面。 如果您使用页面基本分页,只需每次在
currentPage+1
方法中添加 FetchMore
即可,或者如果您根据 API 需求使用 ListView
方法,则设置此 startIndex
索引...
现在让我们看看第二种方法在当前索引的帮助下计算
用
index
计算是否需要像下面这样获取下一页...
void getMoreData(int index) {
const pageSize = 10;
final currentPosition = index + 1;
//check if you need to loadMore
final loadMore = currentPosition % pageSize == 0 && index != 0;
//check which page needs to catch data
final int currentPageToRequest = currentPosition ~/ 10;
if (loadMore && currentPageToRequest > state.pageNo) {
state.copyWith(pageNo: state.pageNo + 1); //update the current page no
getDataWithPage();
}
}
在
ListView
内部itemBuilder
内部调用此方法并传递索引...