【问题标题】:How to do infinite scroll with pagination in flutter如何在颤动中使用分页进行无限滚动
【发布时间】:2020-04-01 16:31:10
【问题描述】:

我是 Flutter 的新手。我想用 REST API 进行分页。我的问题是如何添加无限滚动,然后将数据加载到下一页。如何加载到“https://MY_API_URL?page=2”、第 3 页等? 任何人都可以帮助我吗?非常感谢

【问题讨论】:

  • 调用getJSONData(int page) 作为调用itemBuilder 的结果并将其缓存在某处(例如使用MapCache

标签: flutter


【解决方案1】:

编辑 将 sendPagesDataRequest 更改为以下应该可以工作
如果你给我的json字符串是正确的

Future<PagesData> sendPagesDataRequest(int page) async {
    print('page ${page}');
    try {
      /*String url = Uri.encodeFull(
          'http://api.worldbank.org/v2/country?page=$page&format=json');*/
      String url = Uri.encodeFull("https://MY_API_URL?page=$page");
      http.Response response = await http.get(url);
      print('body ${response.body}');

      /*String responseString = '''
      {"current_page": 1, 
"data": [ 
    { "id": 1, "title": "Germa", "likes": 5, "image": "https://picsum.photos/250?image=8"}, 
    { "id": 2, "title": "Jepun", "likes": 3, "image": "https://picsum.photos/250?image=9"} 
    ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}
      ''';*/

      PagesData pagesData = pagesDataFromJson(response.body);
      return pagesData;
    } catch (e) {
      if (e is IOException) {
        /*return CountriesData.withError(
            'Please check your internet connection.');*/
      } else {
        print(e.toString());
        /*return CountriesData.withError('Something went wrong.');*/
      }
    }
  }

编辑
带有新 sendPagesDataRequest 的完整代码

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'package:flutter_paginator/flutter_paginator.dart';
import 'package:flutter_paginator/enums.dart';
import 'package:cached_network_image/cached_network_image.dart';

// To parse this JSON data, do
//
//     final pagesData = pagesDataFromJson(jsonString);

import 'dart:convert';

PagesData pagesDataFromJson(String str) => PagesData.fromJson(json.decode(str));

String pagesDataToJson(PagesData data) => json.encode(data.toJson());

class PagesData {
  int currentPage;
  List<Datum> data;
  String firstPageUrl;
  int from;
  int lastPage;
  String lastPageUrl;
  String nextPageUrl;

  PagesData({
    this.currentPage,
    this.data,
    this.firstPageUrl,
    this.from,
    this.lastPage,
    this.lastPageUrl,
    this.nextPageUrl,
  });

  factory PagesData.fromJson(Map<String, dynamic> json) => PagesData(
    currentPage: json["current_page"],
    data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
    firstPageUrl: json["first_page_url"],
    from: json["from"],
    lastPage: json["last_page"],
    lastPageUrl: json["last_page_url"],
    nextPageUrl: json["next_page_url"],
  );

  Map<String, dynamic> toJson() => {
    "current_page": currentPage,
    "data": List<dynamic>.from(data.map((x) => x.toJson())),
    "first_page_url": firstPageUrl,
    "from": from,
    "last_page": lastPage,
    "last_page_url": lastPageUrl,
    "next_page_url": nextPageUrl,
  };
}

class Datum {
  int id;
  String title;
  int likes;
  String image;

  Datum({
    this.id,
    this.title,
    this.likes,
    this.image,
  });

  factory Datum.fromJson(Map<String, dynamic> json) => Datum(
    id: json["id"],
    title: json["title"],
    likes: json["likes"],
    image: json["image"],
  );

  Map<String, dynamic> toJson() => {
    "id": id,
    "title": title,
    "likes": likes,
    "image": image,
  };
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Paginator',
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return HomeState();
  }
}

class HomeState extends State<HomePage> {
  GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Paginator'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.format_list_bulleted),
            onPressed: () {
              paginatorGlobalKey.currentState
                  .changeState(listType: ListType.LIST_VIEW);
            },
          ),
          IconButton(
            icon: Icon(Icons.grid_on),
            onPressed: () {
              paginatorGlobalKey.currentState.changeState(
                listType: ListType.GRID_VIEW,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2),
              );
            },
          ),
          IconButton(
            icon: Icon(Icons.library_books),
            onPressed: () {
              paginatorGlobalKey.currentState
                  .changeState(listType: ListType.PAGE_VIEW);
            },
          ),
        ],
      ),
      body: Paginator.listView(
        key: paginatorGlobalKey,
        pageLoadFuture: sendPagesDataRequest,
        pageItemsGetter: listItemsGetterPages,
        listItemBuilder: listItemBuilder,
        loadingWidgetBuilder: loadingWidgetMaker,
        errorWidgetBuilder: errorWidgetMaker,
        emptyListWidgetBuilder: emptyListWidgetMaker,
        totalItemsGetter: totalPagesGetter,
        pageErrorChecker: pageErrorChecker,
        scrollPhysics: BouncingScrollPhysics(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          paginatorGlobalKey.currentState.changeState(
              pageLoadFuture: sendCountriesDataRequest, resetState: true);
        },
        child: Icon(Icons.refresh),
      ),
    );
  }

  Future<CountriesData> sendCountriesDataRequest(int page) async {
    print('page ${page}');
    try {
      String url = Uri.encodeFull(
          'http://api.worldbank.org/v2/country?page=$page&format=json');
      http.Response response = await http.get(url);
      print('body ${response.body}');
      return CountriesData.fromResponse(response);
    } catch (e) {
      if (e is IOException) {
        return CountriesData.withError(
            'Please check your internet connection.');
      } else {
        print(e.toString());
        return CountriesData.withError('Something went wrong.');
      }
    }
  }

  Future<PagesData> sendPagesDataRequest(int page) async {
    print('page ${page}');
    try {
      /*String url = Uri.encodeFull(
          'http://api.worldbank.org/v2/country?page=$page&format=json');*/
      String url = Uri.encodeFull("https://MY_API_URL?page=$page");
      http.Response response = await http.get(url);
      print('body ${response.body}');

      /*String responseString = '''
      {"current_page": 1, 
"data": [ 
    { "id": 1, "title": "Germa", "likes": 5, "image": "https://picsum.photos/250?image=8"}, 
    { "id": 2, "title": "Jepun", "likes": 3, "image": "https://picsum.photos/250?image=9"} 
    ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}
      ''';*/

      PagesData pagesData = pagesDataFromJson(response.body);
      return pagesData;
    } catch (e) {
      if (e is IOException) {
        /*return CountriesData.withError(
            'Please check your internet connection.');*/
      } else {
        print(e.toString());
        /*return CountriesData.withError('Something went wrong.');*/
      }
    }
  }

  List<dynamic> listItemsGetter(CountriesData countriesData) {
    List<String> list = [];
    countriesData.countries.forEach((value) {
      list.add(value['name']);
    });
    return list;
  }

  List<dynamic> listItemsGetterPages(PagesData pagesData) {
    List<Datum> list = [];
    pagesData.data.forEach((value) {
      list.add(value);
    });
    return list;
  }

  Widget listItemBuilder(dynamic item, int index) {
    return Container(
      decoration: BoxDecoration(
          color: Colors.blue[50]
      ),
      margin: const EdgeInsets.all(8),
      child: Column(
        children: <Widget>[
          new CachedNetworkImage(
            imageUrl: item.image,
            placeholder: (context, url) => new CircularProgressIndicator(),
            errorWidget: (context, url, error) => new Icon(Icons.error),
          ),
          ListTile(title: Text(item.title), subtitle: Text('Likes: ' + item.likes.toString()),),
        ],),
    );
  }

  Widget loadingWidgetMaker() {
    return Container(
      alignment: Alignment.center,
      height: 160.0,
      child: CircularProgressIndicator(),
    );
  }

  Widget errorWidgetMaker(PagesData countriesData, retryListener) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text("error"),
        ),
        FlatButton(
          onPressed: retryListener,
          child: Text('Retry'),
        )
      ],
    );
  }

  Widget emptyListWidgetMaker(PagesData countriesData) {
    return Center(
      child: Text('No countries in the list'),
    );
  }

  int totalPagesGetter(PagesData pagesData) {
    return pagesData.lastPage;
  }

  bool pageErrorChecker(PagesData pagesData) {
    //return countriesData.statusCode != 200;
    return false;
  }
}

class CountriesData {
  List<dynamic> countries;
  int statusCode;
  String errorMessage;
  int total;
  int nItems;

  CountriesData.fromResponse(http.Response response) {
    this.statusCode = response.statusCode;
    List jsonData = json.decode(response.body);
    countries = jsonData[1];
    total = jsonData[0]['total'];
    nItems = countries.length;
  }

  CountriesData.withError(String errorMessage) {
    this.errorMessage = errorMessage;
  }
}

编辑
你需要更改sendPagesDataRequest,我使用静态字符串
假设你的 json 字符串是这样的

{"current_page": 1, 
"data": [ 
    { "id": 1, "title": "Germa", "likes": 5, "image": "image url"}, 
    { "id": 2, "title": "Jepun", "likes": 3, "image": "image url"} 
    ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}

编辑工作演示

编辑完整代码

import 'dart:async';
import 'dart:convert';
import 'dart:io';

import 'package:flutter/material.dart';
import 'package:http/http.dart' as http;

import 'package:flutter_paginator/flutter_paginator.dart';
import 'package:flutter_paginator/enums.dart';
import 'package:cached_network_image/cached_network_image.dart';

// To parse this JSON data, do
//
//     final pagesData = pagesDataFromJson(jsonString);

import 'dart:convert';

PagesData pagesDataFromJson(String str) => PagesData.fromJson(json.decode(str));

String pagesDataToJson(PagesData data) => json.encode(data.toJson());

class PagesData {
  int currentPage;
  List<Datum> data;
  String firstPageUrl;
  int from;
  int lastPage;
  String lastPageUrl;
  String nextPageUrl;

  PagesData({
    this.currentPage,
    this.data,
    this.firstPageUrl,
    this.from,
    this.lastPage,
    this.lastPageUrl,
    this.nextPageUrl,
  });

  factory PagesData.fromJson(Map<String, dynamic> json) => PagesData(
    currentPage: json["current_page"],
    data: List<Datum>.from(json["data"].map((x) => Datum.fromJson(x))),
    firstPageUrl: json["first_page_url"],
    from: json["from"],
    lastPage: json["last_page"],
    lastPageUrl: json["last_page_url"],
    nextPageUrl: json["next_page_url"],
  );

  Map<String, dynamic> toJson() => {
    "current_page": currentPage,
    "data": List<dynamic>.from(data.map((x) => x.toJson())),
    "first_page_url": firstPageUrl,
    "from": from,
    "last_page": lastPage,
    "last_page_url": lastPageUrl,
    "next_page_url": nextPageUrl,
  };
}

class Datum {
  int id;
  String title;
  int likes;
  String image;

  Datum({
    this.id,
    this.title,
    this.likes,
    this.image,
  });

  factory Datum.fromJson(Map<String, dynamic> json) => Datum(
    id: json["id"],
    title: json["title"],
    likes: json["likes"],
    image: json["image"],
  );

  Map<String, dynamic> toJson() => {
    "id": id,
    "title": title,
    "likes": likes,
    "image": image,
  };
}

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Paginator',
      home: HomePage(),
    );
  }
}

class HomePage extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return HomeState();
  }
}

class HomeState extends State<HomePage> {
  GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Flutter Paginator'),
        actions: <Widget>[
          IconButton(
            icon: Icon(Icons.format_list_bulleted),
            onPressed: () {
              paginatorGlobalKey.currentState
                  .changeState(listType: ListType.LIST_VIEW);
            },
          ),
          IconButton(
            icon: Icon(Icons.grid_on),
            onPressed: () {
              paginatorGlobalKey.currentState.changeState(
                listType: ListType.GRID_VIEW,
                gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                    crossAxisCount: 2),
              );
            },
          ),
          IconButton(
            icon: Icon(Icons.library_books),
            onPressed: () {
              paginatorGlobalKey.currentState
                  .changeState(listType: ListType.PAGE_VIEW);
            },
          ),
        ],
      ),
      body: Paginator.listView(
        key: paginatorGlobalKey,
        pageLoadFuture: sendPagesDataRequest,
        pageItemsGetter: listItemsGetterPages,
        listItemBuilder: listItemBuilder,
        loadingWidgetBuilder: loadingWidgetMaker,
        errorWidgetBuilder: errorWidgetMaker,
        emptyListWidgetBuilder: emptyListWidgetMaker,
        totalItemsGetter: totalPagesGetter,
        pageErrorChecker: pageErrorChecker,
        scrollPhysics: BouncingScrollPhysics(),
      ),
      floatingActionButton: FloatingActionButton(
        onPressed: () {
          paginatorGlobalKey.currentState.changeState(
              pageLoadFuture: sendCountriesDataRequest, resetState: true);
        },
        child: Icon(Icons.refresh),
      ),
    );
  }

  Future<CountriesData> sendCountriesDataRequest(int page) async {
    print('page ${page}');
    try {
      String url = Uri.encodeFull(
          'http://api.worldbank.org/v2/country?page=$page&format=json');
      http.Response response = await http.get(url);
      print('body ${response.body}');
      return CountriesData.fromResponse(response);
    } catch (e) {
      if (e is IOException) {
        return CountriesData.withError(
            'Please check your internet connection.');
      } else {
        print(e.toString());
        return CountriesData.withError('Something went wrong.');
      }
    }
  }

  Future<PagesData> sendPagesDataRequest(int page) async {
    print('page ${page}');
    try {
      String url = Uri.encodeFull(
          'http://api.worldbank.org/v2/country?page=$page&format=json');
      http.Response response = await http.get(url);
      print('body ${response.body}');
      String responseString = '''
      {"current_page": 1, 
"data": [ 
    { "id": 1, "title": "Germa", "likes": 5, "image": "https://picsum.photos/250?image=8"}, 
    { "id": 2, "title": "Jepun", "likes": 3, "image": "https://picsum.photos/250?image=9"} 
    ], 
 "first_page_url": "https:/API_URL?page=1", 
 "from": 1, 
 "last_page": 30, 
 "last_page_url": "https:/API_URLpage=30", 
 "next_page_url": "https:/API_URL?page=2"
}
      ''';

      PagesData pagesData = pagesDataFromJson(responseString);
      return pagesData;
    } catch (e) {
      if (e is IOException) {
        /*return CountriesData.withError(
            'Please check your internet connection.');*/
      } else {
        print(e.toString());
        /*return CountriesData.withError('Something went wrong.');*/
      }
    }
  }

  List<dynamic> listItemsGetter(CountriesData countriesData) {
    List<String> list = [];
    countriesData.countries.forEach((value) {
      list.add(value['name']);
    });
    return list;
  }

  List<dynamic> listItemsGetterPages(PagesData pagesData) {
    List<Datum> list = [];
    pagesData.data.forEach((value) {
      list.add(value);
    });
    return list;
  }

  Widget listItemBuilder(dynamic item, int index) {
    return Container(
      decoration: BoxDecoration(
          color: Colors.blue[50]
      ),
      margin: const EdgeInsets.all(8),
      child: Column(
        children: <Widget>[
          new CachedNetworkImage(
            imageUrl: item.image,
            placeholder: (context, url) => new CircularProgressIndicator(),
            errorWidget: (context, url, error) => new Icon(Icons.error),
          ),
          ListTile(title: Text(item.title), subtitle: Text('Likes: ' + item.likes.toString()),),
        ],),
    );
  }

  Widget loadingWidgetMaker() {
    return Container(
      alignment: Alignment.center,
      height: 160.0,
      child: CircularProgressIndicator(),
    );
  }

  Widget errorWidgetMaker(PagesData countriesData, retryListener) {
    return Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: <Widget>[
        Padding(
          padding: const EdgeInsets.all(16.0),
          child: Text("error"),
        ),
        FlatButton(
          onPressed: retryListener,
          child: Text('Retry'),
        )
      ],
    );
  }

  Widget emptyListWidgetMaker(PagesData countriesData) {
    return Center(
      child: Text('No countries in the list'),
    );
  }

  int totalPagesGetter(PagesData pagesData) {
    return pagesData.lastPage;
  }

  bool pageErrorChecker(PagesData pagesData) {
    //return countriesData.statusCode != 200;
    return false;
  }
}

class CountriesData {
  List<dynamic> countries;
  int statusCode;
  String errorMessage;
  int total;
  int nItems;

  CountriesData.fromResponse(http.Response response) {
    this.statusCode = response.statusCode;
    List jsonData = json.decode(response.body);
    countries = jsonData[1];
    total = jsonData[0]['total'];
    nItems = countries.length;
  }

  CountriesData.withError(String errorMessage) {
    this.errorMessage = errorMessage;
  }
}

你可以使用包https://pub.dev/packages/flutter_paginator
它将使用page 参数自动调用您的REST
在下面的演示中,我添加了 print message ,所以你可以看到它在向下滚动时自动调用 rest with page
您可以在下面复制粘贴运行完整代码

代码 sn-p

Future<CountriesData> sendCountriesDataRequest(int page) async {
    print('page ${page}');
    try {
      String url = Uri.encodeFull(
          'http://api.worldbank.org/v2/country?page=$page&format=json');
      http.Response response = await http.get(url);
      print('body ${response.body}');
      return CountriesData.fromResponse(response);
    } catch (e) {
      if (e is IOException) {
        return CountriesData.withError(
            'Please check your internet connection.');
      } else {
        print(e.toString());
        return CountriesData.withError('Something went wrong.');
      }
    }
  }

工作演示

完整的演示代码

    import 'dart:async';
    import 'dart:convert';
    import 'dart:io';

    import 'package:flutter/material.dart';
    import 'package:http/http.dart' as http;

    import 'package:flutter_paginator/flutter_paginator.dart';
    import 'package:flutter_paginator/enums.dart';

    void main() => runApp(MyApp());

    class MyApp extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'Flutter Paginator',
          home: HomePage(),
        );
      }
    }

    class HomePage extends StatefulWidget {
      @override
      State<StatefulWidget> createState() {
        return HomeState();
      }
    }

    class HomeState extends State<HomePage> {
      GlobalKey<PaginatorState> paginatorGlobalKey = GlobalKey();

      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text('Flutter Paginator'),
            actions: <Widget>[
              IconButton(
                icon: Icon(Icons.format_list_bulleted),
                onPressed: () {
                  paginatorGlobalKey.currentState
                      .changeState(listType: ListType.LIST_VIEW);
                },
              ),
              IconButton(
                icon: Icon(Icons.grid_on),
                onPressed: () {
                  paginatorGlobalKey.currentState.changeState(
                    listType: ListType.GRID_VIEW,
                    gridDelegate: SliverGridDelegateWithFixedCrossAxisCount(
                        crossAxisCount: 2),
                  );
                },
              ),
              IconButton(
                icon: Icon(Icons.library_books),
                onPressed: () {
                  paginatorGlobalKey.currentState
                      .changeState(listType: ListType.PAGE_VIEW);
                },
              ),
            ],
          ),
          body: Paginator.listView(
            key: paginatorGlobalKey,
            pageLoadFuture: sendCountriesDataRequest,
            pageItemsGetter: listItemsGetter,
            listItemBuilder: listItemBuilder,
            loadingWidgetBuilder: loadingWidgetMaker,
            errorWidgetBuilder: errorWidgetMaker,
            emptyListWidgetBuilder: emptyListWidgetMaker,
            totalItemsGetter: totalPagesGetter,
            pageErrorChecker: pageErrorChecker,
            scrollPhysics: BouncingScrollPhysics(),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              paginatorGlobalKey.currentState.changeState(
                  pageLoadFuture: sendCountriesDataRequest, resetState: true);
            },
            child: Icon(Icons.refresh),
          ),
        );
      }

      Future<CountriesData> sendCountriesDataRequest(int page) async {
        print('page ${page}');
        try {
          String url = Uri.encodeFull(
              'http://api.worldbank.org/v2/country?page=$page&format=json');
          http.Response response = await http.get(url);
          print('body ${response.body}');
          return CountriesData.fromResponse(response);
        } catch (e) {
          if (e is IOException) {
            return CountriesData.withError(
                'Please check your internet connection.');
          } else {
            print(e.toString());
            return CountriesData.withError('Something went wrong.');
          }
        }
      }

      List<dynamic> listItemsGetter(CountriesData countriesData) {
        List<String> list = [];
        countriesData.countries.forEach((value) {
          list.add(value['name']);
        });
        return list;
      }

      Widget listItemBuilder(value, int index) {
        return ListTile(
          leading: Text(index.toString()),
          title: Text(value),
        );
      }

      Widget loadingWidgetMaker() {
        return Container(
          alignment: Alignment.center,
          height: 160.0,
          child: CircularProgressIndicator(),
        );
      }

      Widget errorWidgetMaker(CountriesData countriesData, retryListener) {
        return Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            Padding(
              padding: const EdgeInsets.all(16.0),
              child: Text(countriesData.errorMessage),
            ),
            FlatButton(
              onPressed: retryListener,
              child: Text('Retry'),
            )
          ],
        );
      }

      Widget emptyListWidgetMaker(CountriesData countriesData) {
        return Center(
          child: Text('No countries in the list'),
        );
      }

      int totalPagesGetter(CountriesData countriesData) {
        return countriesData.total;
      }

      bool pageErrorChecker(CountriesData countriesData) {
        return countriesData.statusCode != 200;
      }
    }

    class CountriesData {
      List<dynamic> countries;
      int statusCode;
      String errorMessage;
      int total;
      int nItems;

      CountriesData.fromResponse(http.Response response) {
        this.statusCode = response.statusCode;
        List jsonData = json.decode(response.body);
        countries = jsonData[1];
        total = jsonData[0]['total'];
        nItems = countries.length;
      }

      CountriesData.withError(String errorMessage) {
        this.errorMessage = errorMessage;
      }
    }

输出

I/flutter (20369): page 1
I/flutter (20369): body [{"page":1,"pages":7,"per_page":"50","total":304},[{"id":"ABW","iso2Code":"AW","name":"Aruba","region":{"id":"LCN","iso2code":"ZJ","value":"Latin America & Caribbean "},"adminregion":{"id":"","iso2code":"","value":""},"incomeLevel":{"id":"HIC","iso2code":"XD","value":"High income"},"lendingType":{"id":"LNX","iso2code":"XX","value":"Not classified"},"capitalCity":"Oranjestad","longitude":"-70.0167","latitude":"12.5167"},{"id":"AFG","iso2Code":"AF","name":"Afghanistan","region":{"id":"SAS","iso2code":"8S","value":"South Asia"},"adminregion":{"id":"SAS","iso2code":"8S","value":"South Asia"},"incomeLevel":{"id":"LIC","iso2code":"XM","value":"Low income"},"lendingType":{"id":"IDX","iso2code":"XI","value":"IDA"},"capitalCity":"Kabul","longitude":"69.1761","latitude":"34.5228"},{"id":"AFR","iso2Code":"A9","name":"Africa","region":{"id":"NA","iso2code":"NA","value":"Aggregates"},"adminregion":{"id":"","iso2code":"","value":""},"incomeLevel":{"id":"NA","iso2code":"NA","value":"Aggregates"},"lendingType":{"id":""
I/flutter (20369): page 2
I/flutter (20369): body [{"page":2,"pages":7,"per_page":"50","total":304},[{"id":"CIV","iso2Code":"CI","name":"Cote d'Ivoire","region":{"id":"SSF","iso2code":"ZG","value":"Sub-Saharan Africa "},"adminregion":{"id":"SSA","iso2code":"ZF","value":"Sub-Saharan Africa (excluding high income)"},"incomeLevel":{"id":"LMC","iso2code":"XN","value":"Lower middle income"},"lendingType":{"id":"IDX","iso2code":"XI","value":"IDA"},"capitalCity":"Yamoussoukro","longitude":"-4.0305","latitude":"5.332"},{"id":"CLA","iso2Code":"C6","name":"Latin America and the Caribbean (IFC classification)","region":{"id":"NA","iso2code":"NA","value":"Aggregates"},"adminregion":{"id":"","iso2code":"","value":""},"incomeLevel":{"id":"NA","iso2code":"NA","value":"Aggregates"},"lendingType":{"id":"","iso2code":"","value":"Aggregates"},"capitalCity":"","longitude":"","latitude":""},{"id":"CME","iso2Code":"C7","name":"Middle East and North Africa (IFC classification)","region":{"id":"NA","iso2code":"NA","value":"Aggregates"},"adminregion":{"id":"","iso2code":"","va

【讨论】:

  • 谢谢您的回复,这里的body部分Paginator.listView()不能更改或删除?
  • 您无法删除。它属于这个插件,它将所有相关的动作集成到这个插件中
  • 好吧,我对如何在我的代码中添加这个插件代码感到困惑
  • 你可以把_buildImageColumn(data[index])放到listItemBuilder中
  • 插件只是将所有操作集成到它。像空数据使用 emptyListWidgetMaker 和错误使用 errorWidgetMaker 。您可以复制粘贴完整代码并更改为您的。
【解决方案2】:

无限滚动分页是一项艰巨的任务。

除了懒惰地获取新项目之外,您还希望让用户了解您当前的状态。 例如,如果您正在加载第一页,您可能希望在屏幕中间显示一个进度指示器。但是,如果您正在加载后续页面,您可能希望在底部显示进度指示器。错误指示器也是如此。

如果来自服务器的列表为空或已完成,您还需要停止请求新页面。 更不用说您可能想为失败的请求添加“重试”按钮。

现在有一个名为Infinite Scroll Pagination 的包可以为您处理所有事情,而且使用非常简单。 为了展示这一点,我将使用来自@chunhunghan 答案的相同国家/地区列表示例:

class CountryListView extends StatefulWidget {
  @override
  _CountryListViewState createState() => _CountryListViewState();
}

class _CountryListViewState extends State<CountryListView> {
  static const _pageSize = 20;

  final PagingController<int, Country> _pagingController =
      PagingController(firstPageKey: 0);

  @override
  void initState() {
    _pagingController.addPageRequestListener((pageKey) {
      _fetchPage(pageKey);
    });
    super.initState();
  }

  void _fetchPage(int pageKey) {
    RemoteApi.getCountryList(pageKey, _pageSize).then((newItems) {
      final isLastPage = newItems.length < _pageSize;
      if (isLastPage) {
        _pagingController.appendLastPage(newItems);
      } else {
        final nextPageKey = pageKey + newItems.length;
        _pagingController.appendPage(newItems, nextPageKey);
      }
    }).catchError((error) {
      _pagingController.error = error;
    });
  }

  @override
  Widget build(BuildContext context) => PagedListView<int, Country>(
        pagingController: _pagingController,
        builderDelegate: PagedChildBuilderDelegate<Country>(
          itemBuilder: (context, item, index) => CountryListItem(
            country: item,
          ),
        ),
      );

  @override
  void dispose() {
    _pagingController.dispose();
    super.dispose();
  }
}

在上面的代码中,我在开头列出的所有问题(以及其他问题)都得到了解决,您可以根据需要自定义所有内容。

披露:我是包作者,如果您有任何疑问,请随时给我发消息。

【讨论】:

  • 不错。感谢你的回复。我会尝试使用这个包
  • @Edson 你在这个库上做得很好!
【解决方案3】:

我创建了一个带有分页的无限加载列表的轻量级示例。当您到达列表底部时,系统会请求新项目。用法如下所示:

import 'package:flutter/material.dart';

class Example extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return InfiniteList(
      widgetBuilder: (item) {
        return Text(item);
      },
      loadMore: (lastLoaded) {
        if (lastLoaded == null) {
          //first load request
          return ["hello", "world"];
        } else {
          //subsequent load request(s)
          return [];
        }
      },
      onItemSelected: (item) {
        print(item);
      },
    );
  }
}

这个想法是根据最后加载的项目进行分页,lastLoaded 而不是页码。如果页面 X+1 的内容在您加载页面 X 后发生更改(即从数据库中添加或删除某些内容时),这样做有助于确保您不会遗漏或重复任何内容。

如果您的 API 不支持,或者您不想要它,您可以为每个项目添加页码属性,然后执行:

something.load(page: lastLoaded.pageNumber + 1);

InfiniteList 的实现如下所示:

import 'package:flutter/material.dart';

extension on List {
  Object lastOrNull() {
    return this.isNotEmpty ? this.last : null;
  }
}

typedef ItemWidgetBuilder = Widget Function(Object item);
typedef FutureItemsCallback = Future<List<Object>> Function(Object lastLoadedItem);

typedef ItemCallback = void Function(Object item);

class InfiniteList extends StatefulWidget {
  final ItemWidgetBuilder widgetBuilder;
  final FutureItemsCallback loadMore;
  final ItemCallback onItemSelected;

  InfiniteList({Key key, @required this.widgetBuilder, @required this.loadMore, this.onItemSelected}) : super(key: key);

  @override
  State<StatefulWidget> createState() {
    return InfiniteListState();
  }
}

class InfiniteListState extends State<InfiniteList> {
  List<Object> items = [];
  bool shouldTryToLoadMore = true;

  @override
  void initState() {
    super.initState();
    waitOnItems();
  }

  void waitOnItems() async {
    try {
      final items = await widget.loadMore(this.items.lastOrNull());
      this.shouldTryToLoadMore = items.isNotEmpty;
      setState(() {
        this.items.addAll(items);
      });
    } catch(error) {
      print(error);
    }
  }

  @override
  Widget build(BuildContext context) {
    if (items.isEmpty) {
      return initiallyLoading();
    } else {
      //TODO: show progress bar at the bottom if loading more
      return list();
    }
  }

  Widget list() {
    return ListView.builder(
        itemCount: shouldTryToLoadMore ? null : items.length,
        itemBuilder: (context, index) {
          if (shouldTryToLoadMore && index == items.length - 1) {
            waitOnItems();
            return null;
          } else if (index >= items.length) {
            return null;
          } else if (widget.onItemSelected != null) {
            return InkWell(
              onTap: () => {
                widget.onItemSelected(items[index])
              },
              child: widget.widgetBuilder(items[index]),
            );
          } else {
            return widget.widgetBuilder(items[index]);
          }
        }
      );
  }

  Widget initiallyLoading() {
    return Center(
      child: CircularProgressIndicator(),
    );
  }
}

这里有一个完整的要点: https://gist.github.com/tombailey/988f788493cec9b95e7e9e007b8a7a0d

【讨论】:

  • 谢谢你的回复,我试试看。
猜你喜欢
  • 2022-07-15
  • 2022-11-11
  • 2016-10-17
  • 2020-11-09
  • 2012-10-05
  • 2019-05-15
  • 2021-04-14
  • 1970-01-01
  • 2021-09-07
相关资源
最近更新 更多