首页 文章

颤动:BottomNavigationBar 在选项卡更改时重建页面

提问于
浏览
14

我在 Flutter 中的 BottomNavigationBar 有问题。如果我更改选项卡,我想保持页面处于活动状态。

在这里我的实施

BottomNavigation

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),
      ],
    );
  }
}

这是我的索引页面(第一个标签):

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;
}

因此,如果我从索引选项卡切换到任何其他选项卡并返回到索引选项卡,则索引选项卡将始终重建。我调试了它,发现构建函数总是在 tab 开关上调用。

你能帮助我解决这个问题吗?

非常感谢 Albo

4 回答

  • 18

    之前的答案都没有为我解决。

    切换选项卡时保持页面活动的解决方案是将页面包装在IndexedStack中。

    class Tabbar extends StatefulWidget {
      Tabbar({this.screens});
    
      static const Tag = "Tabbar";
      final List<Widget> screens;
      @override
      State<StatefulWidget> createState() {
      return _TabbarState();
      }
    }
    
    class _TabbarState extends State<Tabbar> {
      int _currentIndex = 0;
      Widget currentScreen;
    
      @override
      Widget build(BuildContext context) {
        var _l10n = PackedLocalizations.of(context);
    
        return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: widget.screens,
      ),
      bottomNavigationBar: BottomNavigationBar(
        fixedColor: Colors.black,
        type: BottomNavigationBarType.fixed,
        onTap: onTabTapped,
        currentIndex: _currentIndex,
        items: [
          BottomNavigationBarItem(
            icon: new Icon(Icons.format_list_bulleted),
            title: new Text(_l10n.tripsTitle),
          ),
          BottomNavigationBarItem(
            icon: new Icon(Icons.settings),
            title: new Text(_l10n.settingsTitle),
          )
        ],
      ),
    );
      }
    
      void onTabTapped(int index) {
        setState(() {
          _currentIndex = index;
        });
      }
    }
    
  • 10

    您需要使用导航器包装每个根页面(当您按下底部导航项时看到的第一页)并将它们放入堆栈中。

    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;
            });
          },
        );
      }
    }
    

    页面将被重建,但无论如何您应该将业务逻辑与 UI 分开。我更喜欢使用 BLoC 模式,但您也可以使用 Redux,ScopedModel 或 InhertedWidget。

  • 1

    我不确定,但CupertinoTabBar会有所帮助。
    如果你不想要它,这个视频会很棒网址

    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(),
                  ),
                ],
              ),
            ],
          ),
        ),
        );
      }
    }
    
  • 1

    如果,您只需要记住列表中的滚动位置,最好的选择是只为key属性使用PageStoreKey对象:

    @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])
              );
            },
          ),
        );
      }
    

    根据https://docs.flutter.io/flutter/widgets/PageStorageKey-class.html,这应该适用于任何可滚动的小部件。

相关问题