【问题标题】:Pagination with ListView in flutter在颤动中使用 ListView 进行分页
【发布时间】: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 启动按需数据加载
  • stackoverflow.com/questions/49508322/… 怎么没有做你想做的事?
  • @RémiRousselet 每次新页面从 ListView 的顶部开始,而不是它应该停留在我们滚动的末尾。

标签: pagination dart flutter


【解决方案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

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

    【讨论】:

    【解决方案4】:

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

    它在 documentSnapshot 中提供输出,我在 ListView 上尝试过(效果很好)根据贡献者的说法,GridView 直到现在还没有发布。

    【讨论】:

      猜你喜欢
      • 2021-04-14
      • 1970-01-01
      • 2020-04-01
      • 2021-09-17
      • 1970-01-01
      • 1970-01-01
      • 2019-01-31
      • 2019-09-23
      • 2020-03-30
      相关资源
      最近更新 更多