web-dev-qa-db-fra.com

Flutter - ListView à l'intérieur d'un TabBarView perd sa position de défilement

J'ai une application Flutter très simple avec un TabBarView avec deux vues (Tab 1 et Tab 2), l'un d'eux (Onglet 1) a un ListView avec de nombreux widgets de texte simples, le problème est qu'après avoir fait défiler les éléments ListView de Tab 1, si je glisse de Tab 1 à Tab 2 et enfin je glisse de Tab 2 à Tab 1, la position de défilement précédente dans la ListView de Tab 1 se perdre.

Voici le code:

import 'package:flutter/material.Dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  TabController controller;

  @override
  void initState() {
    super.initState();
    controller = new TabController(
      length: 2,
      vsync: this,
    );
  }

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

  @override
  Widget build(BuildContext context) {
    var tabs = <Tab>[
      new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
      new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
    ];

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new TabBarView(controller: controller, children: <Widget>[
        new ListView(children: <Widget>[
          new Column(children: <Widget>[
            new Text('Data 1'),
            new Text('Data 2'),
            new Text('Data 3'),
            new Text('Data 4'),
            new Text('Data 5'),
            new Text('Data 6'),
            new Text('Data 7'),
            new Text('Data 8'),
            new Text('Data 9'),
            new Text('Data 10'),
            new Text('Data 11'),
            new Text('Data 12'),
            new Text('Data 13'),
            new Text('Data 14'),
            new Text('Data 15'),
            new Text('Data 16'),
            new Text('Data 17'),
            new Text('Data 18'),
            new Text('Data 19'),
            new Text('Data 20'),
            new Text('Data 21'),
            new Text('Data 22'),
            new Text('Data 23'),
            new Text('Data 24'),
            new Text('Data 25'),
            new Text('Data 26'),
            new Text('Data 27'),
            new Text('Data 28'),
            new Text('Data 29'),
            new Text('Data 30'),
            new Text('Data 31'),
            new Text('Data 32'),
            new Text('Data 33'),
            new Text('Data 34'),
            new Text('Data 35'),
            new Text('Data 36'),
            new Text('Data 37'),
            new Text('Data 38'),
            new Text('Data 39'),
            new Text('Data 40'),
            new Text('Data 41'),
            new Text('Data 42'),
            new Text('Data 43'),
            new Text('Data 44'),
            new Text('Data 45'),
            new Text('Data 46'),
            new Text('Data 47'),
            new Text('Data 48'),
          ])
        ]),
        new Center(child: new Text('Tab 2'))
      ]),
      bottomNavigationBar: new Material(
        color: Colors.deepOrange,
        child: new TabBar(controller: controller, tabs: tabs),
      ),
    );
  }
}

J'ai même séparé les TabBarViewenfants (Tab 1 et Tab 2) dans une autre classe et j'ai remarqué que le

@override
  Widget build(BuildContext context) {
  ...
}

la méthode de chaque enfant (onglet 1 et onglet 2) est exécutée chaque fois que je glisse vers son onglet conteneur.

Mes questions sont:

1.- Comment puis-je conserver le défilement du ListView même si je passe d'un onglet à l'autre?

2.- Existe-t-il un moyen d'exécuter

@override
Widget build(BuildContext context) {

}

méthode une seule fois si je sépare les TabBarView childrens (Tab 1 et Tab 2) dans une autre classe? Je veux dire, si je avoir à récupérer des données lorsque l'onglet 1 et l'onglet 2 sont créés Je ne veux pas le faire à chaque fois que l'onglet est glissé. Ce serait cher.

.- En général, existe-t-il un moyen d'empêcher qu'une vue d'onglet (y compris ses variables, données, etc.) soit reconstruite à chaque fois que je glisse sur cet onglet?

Merci d'avance.

16
SaloGala

1.- Comment puis-je conserver le défilement du ListView même si je passe d'un onglet à l'autre?

Ok, ce n'était pas aussi facile que je le pensais mais je pense que j'ai réussi à le faire.

Mon idée est de conserver l'offset de listview dans HomePageState, et lorsque nous faisons défiler listview, nous obtenons simplement l'offset de notifier et le définissons via setter (veuillez le rendre plus propre et le partager!).

Ensuite, lorsque nous reconstruisons listview, nous demandons simplement à notre widget principal de nous donner un décalage enregistré et par ScrollController nous initialisons la liste avec ce décalage.

J'ai également changé votre affichage de liste car il y avait un élément de colonne avec 50 textes pour utiliser 50 éléments avec un texte chacun. J'espère que cela ne vous dérange pas :)

Le code:

import 'package:flutter/material.Dart';

void main() {
  runApp(new MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: 'Flutter Demo',
      theme: new ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: new MyHomePage(title: 'Flutter Demo Home Page'),
    );
  }
}

typedef double GetOffsetMethod();
typedef void SetOffsetMethod(double offset);

class MyHomePage extends StatefulWidget {
  MyHomePage({Key key, this.title}) : super(key: key);

  final String title;

  @override
  _MyHomePageState createState() => new _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage>
    with SingleTickerProviderStateMixin {
  TabController controller;
  double listViewOffset=0.0;

  @override
  void initState() {
    super.initState();
    controller = new TabController(
      length: 2,
      vsync: this,
    );
  }

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

  @override
  Widget build(BuildContext context) {
    var tabs = <Tab>[
      new Tab(icon: new Icon(Icons.home), text: 'Tab 1'),
      new Tab(icon: new Icon(Icons.account_box), text: 'Tab 2')
    ];

    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: new TabBarView(
      controller: controller,
      children: <Widget>[
        new StatefulListView(
          getOffsetMethod: () => listViewOffset,
          setOffsetMethod: (offset) => this.listViewOffset = offset,
        ),
        new Center(child: new Text('Tab 2'))
      ]),
      bottomNavigationBar: new Material(
        color: Colors.deepOrange,
        child: new TabBar(controller: controller, tabs: tabs),
      ),
    );
  }
}

class StatefulListView extends StatefulWidget {
  StatefulListView({Key key, this.getOffsetMethod, this.setOffsetMethod}) : super(key: key);

  final GetOffsetMethod getOffsetMethod;
  final SetOffsetMethod setOffsetMethod;

  @override
  _StatefulListViewState createState() => new _StatefulListViewState();
}

class _StatefulListViewState extends State<StatefulListView> {

  ScrollController scrollController;

  @override
  void initState() {
    super.initState();
    scrollController = new ScrollController(
      initialScrollOffset: widget.getOffsetMethod()
    );
  }

  @override
  Widget build(BuildContext context) {
    return new NotificationListener(
      child: new ListView.builder(
        controller: scrollController,
        itemCount: 50,
        itemBuilder: (BuildContext context, int index) {
          return new Text("Data "+index.toString());
        },
      ),
      onNotification: (notification) {
        if (notification is ScrollNotification) {
          widget.setOffsetMethod(notification.metrics.pixels);
        }
      },
    );
  }
}
9
Marcin Szałek

Si vous donnez à chaque TabBarView une PageStorageKey, le décalage de défilement sera enregistré. Voir plus d'informations sur PageStorageKey ici .

26
Jordan Nelson

Pour être plus précis, vous pouvez utiliser PageStorageKey avec n'importe quelle vue déroulante pour conserver la position de défilement, par exemple:

new ListView.builder(key: new PageStorageKey('myListView'), ...)
13
Oleg Khalidov

Existe-t-il un moyen d'exécuter la méthode de génération de widget (contexte BuildContext) une seule fois ...

À mon humble avis, l'idée de flutter est d'être toujours prêt à reconstruire. Cela devrait être bon marché. Si vous avez des actions coûteuses, vous pouvez utiliser State pour "mettre en cache" les résultats. Par exemple. vous pouvez faire une requête réseau dans initState et via setState reconstruire lorsque vous recevez une réponse. Pour les onglets, vous pouvez préparer et enregistrer des données dans le widget parent. Vous pouvez trouver plus d'informations dans tutoriel de flottement sur la gestion de l'état

0
German Saprykin

Sortie:

enter image description here


Code:

@override
Widget build(BuildContext context) {
  return DefaultTabController(
    length: 2,
    child: Scaffold(
      appBar: AppBar(
        title: Text("PageStorageKey"),
        bottom: TabBar(
          tabs: [
            Tab(icon: Icon(Icons.looks_one), text: "List1"),
            Tab(icon: Icon(Icons.looks_two), text: "List2"),
          ],
        ),
      ),
      body: TabBarView(
        children: [
          _buildList(key: "key1", string: "List1: "),
          _buildList(key: "key2", string: "List2: "),
        ],
      ),
    ),
  );
}

Widget _buildList({String key, String string}) {
  return ListView.builder(
    key: PageStorageKey(key),
    itemBuilder: (_, i) => ListTile(title: Text("${string} ${i}")),
  );
}
0
CopsOnRoad