在flutter中使用ListView进行分页

问题描述 投票:0回答:6

我是 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();
    }
  }
}

这可行,但是这是正确/好的方法吗?有点困惑,因为如果我到达页面末尾并向上/向下滚动,滚动时似乎有点粘..

pagination dart flutter
6个回答
14
投票

将其添加到控制器的监听器中

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
        }
      }

3
投票

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(),
        ),
      ),

0
投票

https://pub.dartlang.org/packages/loadmore

您可以使用这个包,或者您可以查看代码并重做它以满足您的需求


0
投票

尝试使用这个包,https://pub.dev/packages/paginate_firestore 这让我很开心。您只需要添加 2 个属性即可完成!

它在 documentSnapshot 中提供输出,我在 ListView 上尝试过(效果很好)根据贡献者的说法,GridView 到目前为止尚未发布。


0
投票

无需任何滚动控制器的最简单方法:

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,
              )
          ],);
  },
),


0
投票

您可以通过使用

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
内部调用此方法并传递索引...

© www.soinside.com 2019 - 2024. All rights reserved.