web-dev-qa-db-fra.com

Flutter: BottomNavigationBar reconstruit la page lors du changement d'onglet

J'ai un problème avec mon BottomNavigationBar dans Flutter. Je veux garder ma page en vie si je change d'onglet. 

voici ma mise en oeuvre

Navigation de fond

class Home extends StatefulWidget {
  @override
  State<StatefulWidget> createState() {
    return _HomeState();
  }
}

class _HomeState extends State<Home> {
  int _currentIndex = 0;
  List<Widget> _children;
  final Key keyOne = PageStorageKey("IndexTabWidget");

  @override
  void initState() {
    _children = [
      IndexTabWidget(key: keyOne),
      PlaceholderWidget(Colors.green),
      NewsListWidget(),
      ShopsTabWidget(),
      PlaceholderWidget(Colors.blue),
    ];
    super.initState();
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(MyApp.appName),
        textTheme: Theme.of(context).textTheme.apply(
              bodyColor: Colors.black,
              displayColor: Colors.blue,
            ),
      ),
      body: _children[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        onTap: onTabTapped,
        key: IHGApp.globalKey,
        fixedColor: Colors.green,
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.perm_contact_calendar),
            title: Container(height: 0.0),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.business),
            title: Container(height: 0.0),
          ),
        ],
      ),
    );
  }

  void onTabTapped(int index) {
    setState(() {
      _currentIndex = index;
    });
  }

  Column buildButtonColumn(IconData icon) {
    Color color = Theme.of(context).primaryColor;

    return Column(
      mainAxisSize: MainAxisSize.min,
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        Icon(icon, color: color),
      ],
    );
  }
}

Voici ma page d'index (premier onglet):

class IndexTabWidget extends StatefulWidget {
  IndexTabWidget({Key key}) : super(key: key);

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

class IndexTabState extends State<IndexTabWidget>
    with AutomaticKeepAliveClientMixin {
  List<News> news = List();
  FirestoreNewsRepo newsFirestore = FirestoreNewsRepo();

  @override
  Widget build(BuildContext context) {
    return Material(
      color: Colors.white,
      child: new Container(
        child: new SingleChildScrollView(
          child: new ConstrainedBox(
            constraints: new BoxConstraints(),
            child: new Column(
              children: <Widget>[
                HeaderWidget(
                  CachedNetworkImageProvider(
                    'https://static1.fashionbeans.com/wp-content/uploads/2018/04/50-barbershop-top-savill.jpg',
                  ),
                  "",
                ),
                AboutUsWidget(),
                Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: SectionTitleWidget(title: StringStorage.salonsTitle),
                ),
                StreamBuilder(
                  stream: newsFirestore.observeNews(),
                  builder: (context, snapshot) {
                    if (!snapshot.hasData) {
                      return CircularProgressIndicator();
                    } else {
                      news = snapshot.data;
                      return Column(
                        children: <Widget>[
                          ShopItemWidget(
                            AssetImage('assets/images/picture.png'),
                            news[0].title,
                            news[0],
                          ),
                          ShopItemWidget(
                            AssetImage('assets/images/picture1.png'),
                            news[1].title,
                            news[1],
                          )
                        ],
                      );
                    }
                  },
                ),
                Padding(
                  padding: const EdgeInsets.only(
                      left: 16.0, right: 16.0, bottom: 16.0),
                  child: SectionTitleWidget(title: StringStorage.galleryTitle),
                ),
                GalleryCategoryCarouselWidget(),
              ],
            ),
          ),
        ),
      ),
    );
  }

  @override
  bool get wantKeepAlive => true;
}

Donc, si je bascule de l’onglet Index vers un autre et que je retourne à l’index, l’onglet Index sera toujours reconstruit. Je l'ai débogué et ai vu que la fonction de construction est toujours appelée on tab switch.

Pourriez-vous m'aider avec ce problème?

Merci beaucoup Albo

3
NullPointer

Vous devez envelopper chaque page racine (la première page que vous voyez lorsque vous appuyez sur un élément de navigation du bas) avec un navigateur et les placer dans une pile.

class HomePage extends StatefulWidget {
  @override
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  final int _pageCount = 2;
  int _pageIndex = 0;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _body(),
      bottomNavigationBar: _bottomNavigationBar(),
    );
  }

  Widget _body() {
    return Stack(
      children: List<Widget>.generate(_pageCount, (int index) {
        return IgnorePointer(
          ignoring: index != _pageIndex,
          child: Opacity(
            opacity: _pageIndex == index ? 1.0 : 0.0,
            child: Navigator(
              onGenerateRoute: (RouteSettings settings) {
                return new MaterialPageRoute(
                  builder: (_) => _page(index),
                  settings: settings,
                );
              },
            ),
          ),
        );
      }),
    );
  }

  Widget _page(int index) {
    switch (index) {
      case 0:
        return Page1();
      case 1:
        return Page2();
    }

    throw "Invalid index $index";
  }

  BottomNavigationBar _bottomNavigationBar() {
    final theme = Theme.of(context);
    return new BottomNavigationBar(
      fixedColor: theme.accentColor,
      currentIndex: _pageIndex,
      items: [
        BottomNavigationBarItem(
          icon: Icon(Icons.list),
          title: Text("Page 1"),
        ),
        BottomNavigationBarItem(
          icon: Icon(Icons.account_circle),
          title: Text("Page 2"),
        ),
      ],
      onTap: (int index) {
        setState(() {
          _pageIndex = index;
        });
      },
    );
  }
}

Les pages seront reconstruites, mais vous devez quand même séparer votre logique métier de votre interface utilisateur. Je préfère utiliser le modèle BLoC mais vous pouvez également utiliser Redux, ScopedModel ou InhertedWidget.

4
BauerMitFackel

Je ne suis pas sûr mais CupertinoTabBar aiderait.
Si vous n'en voulez pas, cette vidéo serait géniale url .

import 'Dart:async';
import 'Dart:io';
import 'package:flutter/cupertino.Dart';
import 'package:flutter/material.Dart';
import 'package:flutter/services.Dart';
import 'package:flutter_inapp_purchase/flutter_inapp_purchase.Dart';

class HomeScreen extends StatefulWidget {
  @override
  _HomeScreenState createState() => new _HomeScreenState();
}

class _HomeScreenState extends State<HomeScreen> {
  final List<dynamic> pages = [
    new Page1(),
    new Page2(),
    new Page3(),
    new Page4(),
  ];

  int currentIndex = 0;

  @override
  Widget build(BuildContext context) {
    return new WillPopScope(
      onWillPop: () async {
        await Future<bool>.value(true);
      },
      child: new CupertinoTabScaffold(
        tabBar: new CupertinoTabBar(
          iconSize: 35.0,
          onTap: (index) {
            setState(() => currentIndex = index);
          },
          activeColor: currentIndex == 0 ? Colors.white : Colors.black,
          inactiveColor: currentIndex == 0 ? Colors.green : Colors.grey,
          backgroundColor: currentIndex == 0 ? Colors.black : Colors.white,
          currentIndex: currentIndex,
          items: const <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_one),
              title: Text(''),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_two),
              title: Text(''),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_3),
              title: Text(''),
            ),
            BottomNavigationBarItem(
              icon: Icon(Icons.looks_4),
              title: Text(''),
            ),
          ],
        ),
        tabBuilder: (BuildContext context, int index) {
          return new DefaultTextStyle(
            style: const TextStyle(
              fontFamily: '.SF UI Text',
              fontSize: 17.0,
              color: CupertinoColors.black,
            ),
            child: new CupertinoTabView(
              routes: <String, WidgetBuilder>{
                '/Page1': (BuildContext context) => new Page1(),
                '/Page2': (BuildContext context) => new Page2(),
                '/Page3': (BuildContext context) => new Page3(),
                '/Page4': (BuildContext context) => new Page4(),
              },
              builder: (BuildContext context) {
                return pages[currentIndex];
              },
            ),
          );
        },
      ),
    );
  }
}

class Page1 extends StatefulWidget {
  @override
  _Page1State createState() => _Page1State();
}

class _Page1State extends State<Page1> {

  String title;

 @override
  void initState() {
    title = 'Page1';
    super.initState();
  }
   @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
          title: new Text(title),
          leading: new IconButton(
            icon: new Icon(Icons.text_fields),
            onPressed: () {
              Navigator.of(context)
                  .Push(MaterialPageRoute(builder: (context) => Page13()));
            },
          )),
      body: new Center(
        child: new Text(title),
      ),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
        appBar: new AppBar(
            title: new Text('Page2'),
            leading: new IconButton(
              icon: new Icon(Icons.airline_seat_flat_angled),
              onPressed: () {
                Navigator.of(context)
                    .Push(MaterialPageRoute(builder: (context) => Page12()));
              },
            )),
        body: new Center(
          child: Column(
            children: <Widget>[
              CupertinoSlider(
                      value: 25.0,
                      min: 0.0,
                      max: 100.0,
                      onChanged: (double value) {
                        print(value);
                      }
                    ),
            ],
          ),
        ),
    );
  }
}

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Page3'),
      ),
      body: new Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: <Widget>[
            new RaisedButton(
                child: new Text('Cupertino'),
                textColor: Colors.white,
                color: Colors.red,
                onPressed: () {
                  List<int> list = List.generate(10, (int i) => i + 1);    
                  list.shuffle();
                  var subList = (list.sublist(0, 5));
                  print(subList);
                  subList.forEach((li) => list.remove(li));
                  print(list);
                }
            ),
            new SizedBox(height: 30.0),
            new RaisedButton(
                child: new Text('Android'),
                textColor: Colors.white,
                color: Colors.lightBlue,
                onPressed: () {
                  var mes = 'message';
                  var messa = 'メッセージ';
                  var input = 'You have a new message';
                  if (input.contains(messa) || input.contains(mes)) {
                    print('object');
                  } else {
                    print('none');
                  }
                }
            ),
          ],
        ),
      ),
    );
  }
}

class Page4 extends StatelessWidget {
  static List<int> ints = [1, 2, 3, 4, 5];

  static _abc() {
    print(ints.last);
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Page4'),
      ),
      body: new Center(
          child: new RaisedButton(
        child: new Text('Static', style: new TextStyle(color: Colors.white)),
        color: Colors.lightBlue,
        onPressed: _abc,
      )),
    );
  }
}

class Page12 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text('Page12'),
        actions: <Widget>[
          new FlatButton(
            child: new Text('GO'),
            onPressed: () {
              Navigator.of(context)
                  .Push(MaterialPageRoute(builder: (context) => Page13()));
            },
          )
        ],
      ),
      body: new Center(
          child: new RaisedButton(
        child: new Text('Swiper', style: new TextStyle(color: Colors.white)),
        color: Colors.redAccent,
        onPressed: () {},
      )),
    );
  }
}

class Page13 extends StatefulWidget {
  @override
  _Page13State createState() => _Page13State();
}

class _Page13State extends State<Page13> with SingleTickerProviderStateMixin {
 final List<String> _productLists = Platform.isAndroid
      ? [
          'Android.test.purchased',
          'point_1000',
          '5000_point',
          'Android.test.canceled',
        ]
      : ['com.cooni.point1000', 'com.cooni.point5000'];

  String _platformVersion = 'Unknown';
  List<IAPItem> _items = [];
  List<PurchasedItem> _purchases = [];

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

  Future<void> initPlatformState() async {
    String platformVersion;
    try {
      platformVersion = await FlutterInappPurchase.platformVersion;
    } on PlatformException {
      platformVersion = 'Failed to get platform version.';
    }

    var result = await FlutterInappPurchase.initConnection;
    print('result: $result');

    if (!mounted) return;

    setState(() {
      _platformVersion = platformVersion;
    });

    // refresh items for Android
    String msg = await FlutterInappPurchase.consumeAllItems;
    print('consumeAllItems: $msg');
  }

  Future<Null> _buyProduct(IAPItem item) async {
    try {
      PurchasedItem purchased = await FlutterInappPurchase.buyProduct(item.productId);
      print('purchased: ${purchased.toString()}');
    } catch (error) {
      print('$error');
    }
  }

  Future<Null> _getProduct() async {
    List<IAPItem> items = await FlutterInappPurchase.getProducts(_productLists);
    print(items);
    for (var item in items) {
      print('${item.toString()}');
      this._items.add(item);
    }

    setState(() {
      this._items = items;
      this._purchases = [];
    });
  }

  Future<Null> _getPurchases() async {
    List<PurchasedItem> items = await FlutterInappPurchase.getAvailablePurchases();
    for (var item in items) {
      print('${item.toString()}');
      this._purchases.add(item);
    }

    setState(() {
      this._items = [];
      this._purchases = items;
    });
  }

  Future<Null> _getPurchaseHistory() async {
    List<PurchasedItem> items = await FlutterInappPurchase.getPurchaseHistory();
    for (var item in items) {
      print('${item.toString()}');
      this._purchases.add(item);
    }

    setState(() {
      this._items = [];
      this._purchases = items;
    });
  }

  List<Widget> _renderInApps() {
    List<Widget> widgets = this
        ._items
        .map((item) => Container(
              margin: EdgeInsets.symmetric(vertical: 10.0),
              child: Container(
                child: Column(
                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.only(bottom: 5.0),
                      child: Text(
                        item.toString(),
                        style: TextStyle(
                          fontSize: 18.0,
                          color: Colors.black,
                        ),
                      ),
                    ),
                    FlatButton(
                      color: Colors.orange,
                      onPressed: () {
                        print("---------- Buy Item Button Pressed");
                        this._buyProduct(item);
                      },
                      child: Row(
                        children: <Widget>[
                          Expanded(
                            child: Container(
                              height: 48.0,
                              alignment: Alignment(-1.0, 0.0),
                              child: Text('Buy Item'),
                            ),
                          ),
                        ],
                      ),
                    ),
                  ],
                ),
              ),
            ))
        .toList();
    return widgets;
  }

  List<Widget> _renderPurchases() {
    List<Widget> widgets = this
        ._purchases
        .map((item) => Container(
              margin: EdgeInsets.symmetric(vertical: 10.0),
              child: Container(
                child: Column(
                  children: <Widget>[
                    Container(
                      margin: EdgeInsets.only(bottom: 5.0),
                      child: Text(
                        item.toString(),
                        style: TextStyle(
                          fontSize: 18.0,
                          color: Colors.black,
                        ),
                      ),
                    )
                  ],
                ),
              ),
            ))
        .toList();
    return widgets;
  }

  @override
  Widget build(BuildContext context) {
    double screenWidth = MediaQuery.of(context).size.width-20;
    double buttonWidth=(screenWidth/3)-20;

    return new Scaffold(
      appBar: new AppBar(),
      body: Container(
      padding: EdgeInsets.all(10.0),
      child: ListView(
        children: <Widget>[
          Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            mainAxisAlignment: MainAxisAlignment.start,
            children: <Widget>[
              Container(
                child: Text(
                  'Running on: $_platformVersion\n',
                  style: TextStyle(fontSize: 18.0),
                ),
              ),
              Column(
                children: <Widget>[
                  Row(
                    mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                    children: <Widget>[
                      Container(
                        width: buttonWidth,
                        height: 60.0,
                        margin: EdgeInsets.all(7.0),
                        child: FlatButton(
                          color: Colors.amber,
                          padding: EdgeInsets.all(0.0),
                          onPressed: () async {
                            print("---------- Connect Billing Button Pressed");
                            await FlutterInappPurchase.initConnection;
                          },
                          child: Container(
                            padding: EdgeInsets.symmetric(horizontal: 20.0),
                            alignment: Alignment(0.0, 0.0),
                            child: Text(
                              'Connect Billing',
                              style: TextStyle(
                                fontSize: 16.0,
                              ),
                            ),
                          ),
                        ),
                      ),
                      Container(
                        width: buttonWidth,
                        height: 60.0,
                        margin: EdgeInsets.all(7.0),
                        child: FlatButton(
                          color: Colors.amber,
                          padding: EdgeInsets.all(0.0),
                          onPressed: () async {
                            print("---------- End Connection Button Pressed");
                            await FlutterInappPurchase.endConnection;
                            setState(() {
                              this._items = [];
                              this._purchases = [];
                            });
                          },
                          child: Container(
                            padding: EdgeInsets.symmetric(horizontal: 20.0),
                            alignment: Alignment(0.0, 0.0),
                            child: Text(
                              'End Connection',
                              style: TextStyle(
                                fontSize: 16.0,
                              ),
                            ),
                          ),
                        ),
                      ),
                    ],
                  ),
                  Row(
                      mainAxisAlignment: MainAxisAlignment.spaceEvenly,
                      children: <Widget>[
                        Container(
                            width: buttonWidth,
                            height: 60.0,
                            margin: EdgeInsets.all(7.0),
                            child: FlatButton(
                              color: Colors.green,
                              padding: EdgeInsets.all(0.0),
                              onPressed: () {
                                print("---------- Get Items Button Pressed");
                                this._getProduct();
                              },
                              child: Container(
                                padding: EdgeInsets.symmetric(horizontal: 20.0),
                                alignment: Alignment(0.0, 0.0),
                                child: Text(
                                  'Get Items',
                                  style: TextStyle(
                                    fontSize: 16.0,
                                  ),
                                ),
                              ),
                            )),
                        Container(
                            width: buttonWidth,
                            height: 60.0,
                            margin: EdgeInsets.all(7.0),
                            child: FlatButton(
                              color: Colors.green,
                              padding: EdgeInsets.all(0.0),
                              onPressed: () {
                                print(
                                    "---------- Get Purchases Button Pressed");
                                this._getPurchases();
                              },
                              child: Container(
                                padding: EdgeInsets.symmetric(horizontal: 20.0),
                                alignment: Alignment(0.0, 0.0),
                                child: Text(
                                  'Get Purchases',
                                  style: TextStyle(
                                    fontSize: 16.0,
                                  ),
                                ),
                              ),
                            )),
                        Container(
                            width: buttonWidth,
                            height: 60.0,
                            margin: EdgeInsets.all(7.0),
                            child: FlatButton(
                              color: Colors.green,
                              padding: EdgeInsets.all(0.0),
                              onPressed: () {
                                print(
                                    "---------- Get Purchase History Button Pressed");
                                this._getPurchaseHistory();
                              },
                              child: Container(
                                padding: EdgeInsets.symmetric(horizontal: 20.0),
                                alignment: Alignment(0.0, 0.0),
                                child: Text(
                                  'Get Purchase History',
                                  style: TextStyle(
                                    fontSize: 16.0,
                                  ),
                                ),
                              ),
                            )),
                      ]),
                ],
              ),
              Column(
                children: this._renderInApps(),
              ),
              Column(
                children: this._renderPurchases(),
              ),
            ],
          ),
        ],
      ),
    ),
    );
  }
}
0
Daibaku

Si vous devez simplement vous rappeler la position de défilement dans une liste, la meilleure option consiste simplement à utiliser un objet PageStoreKey pour la propriété key:

  @override
  Widget build(BuildContext context) {
    return Container(
      child: ListView.builder(
        key: PageStorageKey<String>('some-list-key'),
        scrollDirection: Axis.vertical,
        shrinkWrap: true,
        itemCount: items.length,
        itemBuilder: (BuildContext context, int index) {
          return GestureDetector(
            onTap: () => _onElementTapped(index),
            child: makeCard(items[index])
          );
        },
      ),
    );
  }

Selon https://docs.flutter.io/flutter/widgets/PageStorageKey-class.html , cela devrait fonctionner sur TOUT widget WRD.

0
LucaRoverelli