8
votes

Flutter ListView.builder - Comment accéder à certains index par programme

J'ai un écran qui se construit à l'aide de MaterialApp , DefaultTabController , Scaffold et TabBarView .

dans cet écran, j'ai un contenu de corps qui récupère une liste d'éléments de sqllite en utilisant StreamBuilder . j'obtiens exactement 100 éléments ("liste finie") à montrer en utilisant ListView .

ma question, en utilisant ListView.builder , comment pouvons-nous sauter à certains index lorsque cet écran s'est ouvert?

mon écran principal:

import 'package:draggable_scrollbar/draggable_scrollbar.dart';
import 'package:flutter/material.dart';

class MyDraggableScrollBar {
  static Widget create({
    @required BuildContext context,
    @required ScrollController scrollController,
    @required double heightScrollThumb,
    @required Widget child,
  }) {
    return DraggableScrollbar(

      alwaysVisibleScrollThumb: true,
      scrollbarTimeToFade: Duration(seconds: 3),
      controller: scrollController,
      heightScrollThumb: heightScrollThumb,
      backgroundColor: Colors.green,
      scrollThumbBuilder: (
        Color backgroundColor,
        Animation<double> thumbAnimation,
        Animation<double> labelAnimation,
        double height, {
        Text labelText,
        BoxConstraints labelConstraints,
      }) {
        return InkWell(
          onTap: () {},
          child: Container(
            height: height,
            width: 7,
            color: backgroundColor,
          ),
        );
      },
      child: child,
    );
  }
}

class MyDraggableScrollBar.dart :

...
ScrollController controller = ScrollController();

 @override
  Widget build(BuildContext context) {

    return MaterialApp(
      debugShowCheckedModeBanner : false,
      home: DefaultTabController(
        length: 3,
        child: Scaffold(
            appBar: AppBar(
              backgroundColor: Pigment.fromString(UIData.primaryColor),
              elevation: 0,
              centerTitle: true,
              title: Text(translations.text("quran").toUpperCase()),
              bottom: TabBar(
                tabs: [
                    Text("Tab1"),
                    Text("Tab2"),
                    Text("Tab3")
                ],
              ),
              leading: Row(
                crossAxisAlignment: CrossAxisAlignment.stretch,
                children: <Widget>[
                  Expanded(
                      child: InkWell(
                        child: SizedBox(child: Image.asset("assets/images/home.png"), height: 10, width: 1,),
                        onTap: () => Navigator.of(context).pop(),
                      )
                  ),
                ],
              ),
            ),

            floatingActionButton: FloatingActionButton(
              onPressed: _scrollToIndex,
              tooltip: 'Testing Index Jump',
              child: Text("GO"),
            ),

            body:
            TabBarView(
              children: [
                Stack(
                  children: <Widget>[
                    MyDraggableScrollBar.create(
                        scrollController: controller,
                        context: context,
                        heightScrollThumb: 25,
                        child: ListView(
                          controller: controller,
                          children: <Widget>[
                            Padding(
                                padding: EdgeInsets.fromLTRB(30, 15, 30, 8),
                                child: Container(
                                    alignment: Alignment.center,
                                    height: 30,
                                    child: ClipRRect(
                                      borderRadius: BorderRadius.circular(8),
                                      child: TextField(
                                        style: TextStyle(color: Colors.green),
                                        decoration: new InputDecoration(
                                            contentPadding: EdgeInsets.all(5),
                                            border: InputBorder.none,
                                            filled: true,
                                            hintStyle: new TextStyle(color: Colors.green, fontSize: 14),
                                            prefixIcon: Icon(FontAwesomeIcons.search,color: Colors.green,size: 17,),
                                            hintText: translations.text("search-quran"),
                                            fillColor: Colors.grey[300],
                                            prefixStyle: TextStyle(color: Colors.green)
                                        ),
                                        onChanged: (val) => quranBloc.searchSurah(val),
                                      ),
                                    )
                                )
                            ),

                            //surah list
                            streamBuilderQuranSurah(context)

                          ],
                        )
                    ) // MyDraggableScrollBar

                  ],
                ),
                Icon(Icons.directions_transit),
                Icon(Icons.directions_bike),
              ],
            )
        )));
  }

  Widget streamBuilderQuranSurah(BuildContext ctx){
    return StreamBuilder(
      stream: quranBloc.chapterStream ,
      builder: (BuildContext context, AsyncSnapshot<ChaptersModel> snapshot){
        if(snapshot.hasData){

          return ListView.builder(
            controller: controller,
            shrinkWrap: true,
            physics: NeverScrollableScrollPhysics(),
            itemCount:(snapshot.data.chapters?.length ?? 0),
            itemBuilder: (BuildContext context, int index) {
              var chapter =
              snapshot.data.chapters?.elementAt(index);
              return chapterDataCell(chapter);
            },
          );
        }
        else{

          return SurahItemShimmer();
        }
      },
    );
  }
...

J'ai essayé de trouver d'autres solutions mais ne semble pas fonctionner, par exemple indexed_list_view qui ne prend en charge que la liste infinie

et il semble que le flutter n'ait toujours pas de fonctionnalité pour cela, voir ce problème

Une idée ?


3 commentaires

Connaissez-vous déjà le numéro d'index ou l'élément que vous souhaitez ignorer?


Salut @AjilO. oui parce que c'est une liste finie et que ce sera un paramètre d'un autre écran.


vous recherchez probablement ce stackoverflow.com/a/58809961/6668797 ScrollController(initialScrollOffset: _)


4 Réponses :


1
votes

Solution générale:

Pour stocker tout ce qui peut être représenté sous forme de nombre / chaîne / liste de chaînes, Flutter fournit un plugin puissant et facile à utiliser qui stocke les valeurs nécessaires à stocker avec une clé. Ainsi, la prochaine fois que vous en aurez besoin, vous devrez récupérer ou même mettre à jour cette valeur, tout ce dont vous aurez besoin est cette clé.

Pour commencer, ajoutez le plugin shared_preferences au fichier pubspec.yaml,

class MyWidget extends StatefulWidget {
  @override
  _MyWidgetState createState() => _MyWidgetState();
}

class _MyWidgetState extends State<MyWidget> with WidgetsBindingObserver{

  //[...]
  ScrollController _scrollController;
  SharedPreferences _sharedPreferences;

  Future<void> resumeController() async{
    _sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
      if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
      else _sharedPreferences.setDouble("scroll-offset-0", 0);
      setState((){});
      return _sharedPreferences;
    });

  }

  @override
  void initState() {
    resumeController();
    WidgetsBinding.instance.addObserver(this);
    super.initState();
  }

  @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _scrollController.dispose();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
       _sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
    super.didChangeAppLifecycleState(state);
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      home: Scaffold(
        appBar: AppBar(
          title: Text("Smart Scroll View"),
        ),
        body: ListView.builder(
            itemCount: 50,
            controller: _scrollController,
            itemBuilder: (c,i)=>
                Padding(
                  padding: EdgeInsets.symmetric(horizontal: 24,vertical: 16),
                  child: Text((i+1).toString()),
                ),
        ),
      ),
    );
  }
}

Exécutez flutter pub get partir du terminal ou si vous utilisez IntelliJ, cliquez simplement sur Packages get (vous le trouverez quelque part dans le coin supérieur droit de votre écran lors de la visualisation du fichier pubspec.yaml )

Une fois la commande ci-dessus exécutée avec succès, importez le fichier ci-dessous dans votre main.dart ou le fichier concerné.

 @override
  void dispose() {
    WidgetsBinding.instance.removeObserver(this);
    _scrollController.dispose();
    super.dispose();
  }

  @override
  void didChangeAppLifecycleState(AppLifecycleState state) {
    if(state==AppLifecycleState.paused || state==AppLifecycleState.inactive || state==AppLifecycleState.suspending)
       _sharedPreferences.setDouble("scroll-offset-0", _scrollController.offset);
    super.didChangeAppLifecycleState(state);
  }

Maintenant, attachez simplement un ScrollController à votre widget ListView.builder() et assurez-vous que le décalage final / dernier est stocké avec une clé spécifique en utilisant shared_preferences chaque fois que l'utilisateur quitte l'application de quelque manière que ce soit et est défini lorsque l'initState de votre widget concerné est appelé.

Afin de savoir détecter les changements dans l'état de notre application et agir en conséquence, nous WidgetsBindingObserver de WidgetsBindingObserver de notre classe.

Étapes à suivre:

  1. Étendez la classe WidgetsBindingObserver avec la classe State de votre StatefulWidget.

  2. Définissez une fonction asynchrone resumeController() tant que fonction membre de la classe ci-dessus.

  resumeController();
  WidgetsBinding.instance.addObserver(this);
  1. Déclarez deux variables une pour stocker et transmettre le scrollcontroller et l'autre pour stocker et utiliser l'instance de SharedPreferences.
  ScrollController _scrollController;
  SharedPreferences _sharedPreferences;
  1. Appelez resumeController() et transmettez votre classe à la méthode addObserver de l'objet d'instance dans la classe WidgetsBinding.
  Future<void> resumeController() async{
    _sharedPreferences = await SharedPreferences.getInstance().then((_sharedPreferences){
      if(_sharedPreferences.getKeys().contains("scroll-offset-0")) _scrollController= ScrollController(initialScrollOffset:_sharedPreferences.getDouble("scroll-offset-0"));
      else _sharedPreferences.setDouble("scroll-offset-0", 0);
      setState((){});
      return _sharedPreferences;
    });
  1. Collez simplement ce code dans la définition de classe (en dehors des autres fonctions membres)
  import 'package:shared_preferences/shared_preferences.dart';
  1. Passez le ScrollController() au Scrollable concerné.

Exemple de travail:

dependencies:
  flutter:
    sdk: flutter
  shared_preferences: "<newest version>"


2 commentaires

Pensez-vous que AppLifecycleState.paused , AppLifecycleState.inactive , AppLifecycleState.suspending couvrent tous les cas et que le décalage de défilement sera toujours enregistré, quelle que soit la façon dont l'application est «fermée»?


De plus, la suspending n'est plus (plus une) valeur de AppLifecycleState



0
votes

Solution sans connaître la taille de vos widgets

la solution que j'ai trouvée sans connaître la taille de votre widget affiche une «sous-liste» inversée de l'index à la fin, puis faites défiler vers le haut de votre «sous-liste» et réinitialisez la liste entière. Comme il s'agit d'une liste inversée, l'élément sera ajouté en haut de la liste et vous resterez à votre position (l'index).

le problème est que vous ne pouvez pas utiliser un listView.builder car vous devrez changer la taille de la liste

exemple

class _ListViewIndexState extends State<ListViewIndex> {
  ScrollController _scrollController;
  List<Widget> _displayedList;
  @override
  void initState() {
    super.initState();

    _scrollController = ScrollController();

    _displayedList = widget.items.sublist(0, widget.items.length - widget.index);

    if (SchedulerBinding.instance.schedulerPhase == SchedulerPhase.persistentCallbacks) {
      SchedulerBinding.instance.addPostFrameCallback((_) {
//here the sublist is already build
        completeList();
      });
    }
  }

  completeList() {
//to go to the last item(in first position) 
    _scrollController.jumpTo(_scrollController.position.maxScrollExtent);
//reset the list to the full list
    setState(() {
      _displayedList = widget.items;
    });
  }
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: <Widget>[
        ListView(
          controller: _scrollController,
          reverse: true,
          children: _displayedList,
        ),
      ]
    );
  }
}

0 commentaires

0
votes

Le package https://pub.dev/packages/indexed_list_view pourrait peut-être vous aider pour cela. Utilisez quelque chose comme ceci:

IndexedListView.builder(
    controller: indexScrollController, 
    itemBuilder: itemBuilder
);


indexScrollController.jumpToIndex(10000);


0 commentaires

0
votes

Vous pouvez utiliser https://pub.dev/packages/scrollable_positioned_list . Vous pouvez transmettre l'index initial au widget.

ScrollablePositionedList.builder(
 initialScrollIndex: 12, //you can pass the desired index here//
 itemCount: 500,
 itemBuilder: (context, index) => Text('Item $index'),
 itemScrollController: itemScrollController,
 itemPositionsListener: itemPositionsListener,
);


0 commentaires