100 votes

Flutter ListView chargement paresseux

Comment puis-je réaliser un chargement paresseux des éléments pour un listview sans fin ? Je veux charger plus d'éléments par réseau lorsque l'utilisateur fait défiler jusqu'à la fin de la liste.

2 votes

Utilisez ListView.builder() et fournir un nombre d'articles plus important que ce qui est déjà chargé. Lorsque l'utilisateur fait défiler la liste jusqu'à un index qui n'a pas encore été chargé, vous chargez les données et présentez à nouveau la liste.

0 votes

@GünterZöchbauer Je n'aime pas cette approche car il est beaucoup plus difficile de travailler avec la pagination. Vous ne savez pas à l'intérieur d'un index inconnu s'il se trouve dans la page n+1 ou n+2 par exemple.

0 votes

Cela peut dépendre du cas d'utilisation. Cela a bien fonctionné dans notre projet. Vous pouvez également la combiner avec la position de défilement. Ce que j'aime, c'est que je peux avoir un nombre beaucoup plus important d'éléments chargés et que la barre de défilement le reflète.

116voto

Rémi Rousselet Points 45139

Vous pouvez écouter un ScrollController .

ScrollController contient quelques informations utiles, telles que le scrolloffset et une liste de ScrollPosition .

Dans votre cas, la partie intéressante est dans controller.position qui est l'image actuellement visible ScrollPosition . qui représente un segment du scrollable.

ScrollPosition contient des informations sur sa position à l'intérieur du scrollable. Par exemple extentBefore et extentAfter . Ou sa taille, avec extentInside .

En considérant ceci, vous pourriez déclencher un appel au serveur basé sur extentAfter qui représente l'espace de défilement restant disponible.

Voici un exemple basique utilisant ce que j'ai dit.

class MyHome extends StatefulWidget {
  @override
  _MyHomeState createState() => new _MyHomeState();
}

class _MyHomeState extends State<MyHome> {
  ScrollController controller;
  List<String> items = new List.generate(100, (index) => 'Hello $index');

  @override
  void initState() {
    super.initState();
    controller = new ScrollController()..addListener(_scrollListener);
  }

  @override
  void dispose() {
    controller.removeListener(_scrollListener);
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      body: new Scrollbar(
        child: new ListView.builder(
          controller: controller,
          itemBuilder: (context, index) {
            return new Text(items[index]);
          },
          itemCount: items.length,
        ),
      ),
    );
  }

  void _scrollListener() {
    print(controller.position.extentAfter);
    if (controller.position.extentAfter < 500) {
      setState(() {
        items.addAll(new List.generate(42, (index) => 'Inserted $index'));
      });
    }
  }
}

Vous pouvez clairement voir qu'en atteignant la fin du défilement, la barre de défilement se déploie car elle a chargé plus d'éléments.

0 votes

Oui, cela semble être quelque chose de bien et je vais l'essayer. Mais j'ai une question. Est-ce que itemCount (paramètre itemBuilder) sera modifié dynamiquement lorsque items.length sera modifié ?

0 votes

Par ailleurs, dois-je toujours appeler l'appel 'super' de la fonction surchargée à la fin du corps de la fonction ? C'est un peu hors sujet mais ça m'intéresse beaucoup).

0 votes

ItemCount sera mis à jour au fil du temps, oui. L'appel à super n'est pas toujours nécessaire. Mais certaines fonctions le demandent.

36voto

Goodbye World Points 135

Merci pour l'approche de Rémi Rousselet, mais elle ne résout pas tout le problème. En particulier lorsque le ListView a défilé jusqu'en bas, il appelle encore le scrollListener plusieurs fois. L'approche améliorée consiste à combiner le Notification Listener avec l'approche de Rémi. Voici ma solution :

bool _handleScrollNotification(ScrollNotification notification) {
  if (notification is ScrollEndNotification) {
    if (_controller.position.extentAfter == 0) {
      loadMore();
    }
  }
  return false;
}

@override
Widget build(BuildContext context) {
    final Widget gridWithScrollNotification = NotificationListener<
            ScrollNotification>(
        onNotification: _handleScrollNotification,
        child: GridView.count(
            controller: _controller,
            padding: EdgeInsets.all(4.0),
          // Create a grid with 2 columns. If you change the scrollDirection to
          // horizontal, this would produce 2 rows.
          crossAxisCount: 2,
          crossAxisSpacing: 2.0,
          mainAxisSpacing: 2.0,
          // Generate 100 Widgets that display their index in the List
          children: _documents.map((doc) {
            return GridPhotoItem(
              doc: doc,
            );
          }).toList()));
    return new Scaffold(
      key: _scaffoldKey,
      body: RefreshIndicator(
       onRefresh: _handleRefresh, child: gridWithScrollNotification));
}

8 votes

ScrollNotification a notification.metrics.extentAfter Vous n'avez donc pas besoin d'utiliser ScrollController . En outre, quand j'ai utilisé les deux à la première fois, il n'a jamais fonctionné jusqu'à ce que j'ai enlevé _controller de ListView .

11voto

user2179571 Points 15

La solution utilise ScrollController et j'ai vu des commentaires sur la page.
Je voudrais partager mes découvertes sur le paquet incrementally_loading_listview. https://github.com/MaikuB/incrementally_loading_listview .
Comme indiqué dans l'emballage : Cela pourrait être utilisé pour charger des données paginées reçues de requêtes API.

Fondamentalement, lorsque ListView construit le dernier élément et que cela signifie que l'utilisateur a fait défiler jusqu'en bas.
J'espère que cela pourra aider quelqu'un qui se pose des questions similaires.

Pour les besoins de la démo, j'ai modifié l'exemple pour qu'une page n'inclue qu'un seul élément et ajouter un CircularProgressIndicator.

enter image description here

...
bool _loadingMore;
bool _hasMoreItems;
int  _maxItems = 30;
int  _numItemsPage = 1;
...
_hasMoreItems = items.length < _maxItems;    
...
return IncrementallyLoadingListView(
              hasMore: () => _hasMoreItems,
              itemCount: () => items.length,
              loadMore: () async {
                // can shorten to "loadMore: _loadMoreItems" but this syntax is used to demonstrate that
                // functions with parameters can also be invoked if needed
                await _loadMoreItems();
              },
              onLoadMore: () {
                setState(() {
                  _loadingMore = true;
                });
              },
              onLoadMoreFinished: () {
                setState(() {
                  _loadingMore = false;
                });
              },
              loadMoreOffsetFromBottom: 0,
              itemBuilder: (context, index) {
                final item = items[index];
                if ((_loadingMore ?? false) && index == items.length - 1) {
                  return Column(
                    children: <Widget>[
                      ItemCard(item: item),
                      Card(
                        child: Padding(
                          padding: const EdgeInsets.all(16.0),
                          child: Column(
                            children: <Widget>[
                              Row(
                                crossAxisAlignment:
                                    CrossAxisAlignment.start,
                                children: <Widget>[
                                  Container(
                                    width: 60.0,
                                    height: 60.0,
                                    color: Colors.grey,
                                  ),
                                  Padding(
                                    padding: const EdgeInsets.fromLTRB(
                                        8.0, 0.0, 0.0, 0.0),
                                    child: Container(
                                      color: Colors.grey,
                                      child: Text(
                                        item.name,
                                        style: TextStyle(
                                            color: Colors.transparent),
                                      ),
                                    ),
                                  )
                                ],
                              ),
                              Padding(
                                padding: const EdgeInsets.fromLTRB(
                                    0.0, 8.0, 0.0, 0.0),
                                child: Container(
                                  color: Colors.grey,
                                  child: Text(
                                    item.message,
                                    style: TextStyle(
                                        color: Colors.transparent),
                                  ),
                                ),
                              )
                            ],
                          ),
                        ),
                      ),
                      Center(child: CircularProgressIndicator())
                    ],
                  );
                }
                return ItemCard(item: item);
              },
            );

exemple complet https://github.com/MaikuB/incrementally_loading_listview/blob/master/example/lib/main.dart

Package utilise ListView index = dernier élément et loadMoreOffsetFromBottom pour détecter quand charger plus.

    itemBuilder: (itemBuilderContext, index) {    
              if (!_loadingMore &&
              index ==
                  widget.itemCount() -
                      widget.loadMoreOffsetFromBottom -
                      1 &&
              widget.hasMore()) {
            _loadingMore = true;
            _loadingMoreSubject.add(true);
          }

4voto

Md Sadab Wasim Points 96

Utilisez lazy_load_scrollview : 1.0.0 paquet qui utilise le même concept en arrière-plan que panda world a répondu ici. Le paquet est plus facile à mettre en œuvre.

3 votes

J'utilise le même paquet et cela fonctionne bien. Je me demande juste comment on peut montrer CircularProgressIndicator à la fin de List lorsque les données sont toujours en cours de chargement, comme lorsque nous atteignons la fin de la List nous allons récupérer des données à partir de l'API et jusqu'à ce que nous obtenions les données, nous montrons juste la CircularProgressIndicator .

0voto

郑松岚 Points 1743

J'utilise https://pub.dev/packages/loadany pour gérer le chargement plus parce qu'il supporte ListView, GridView, ScrollView

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X