Flutter:具有持久性 BottomNavigationBar 的嵌套路由,但无需构建不必要的未选定页面

fil*_*trm 7 dart flutter

在整个互联网stackoverflow中,我搜索并看到了很多针对 Flutter 应用程序使用持久性 BottomNavigationBar 进行嵌套导航问题的解决方案。其中一些使用带有IndexedStackPageView的Navigator等等。所有这些都工作得很好,只是它们会不必要地构建未选择的选项卡(有时甚至在每次切换选项卡时重建所有选项卡),从而使解决方案不具有性能。我终于想出了 \xe2\x80\x93 的解决方案,因为我自己也在努力解决这个问题。

\n

fil*_*trm 17

该解决方案非常基本,但希望您能够在此基础上进行构建并进行调整。它实现了以下目标:

  • 嵌套导航,同时保留BottomNavigationBar
  • 除非已选择,否则不会构建选项卡
  • 保留导航状态
  • 保留滚动状态(例如ListView的)

导入“包:flutter/material.dart”;

void main() {
  runApp(MaterialApp(
    home: MyApp(),
  ));
}

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<Widget> _pages;

  List<BottomNavigationBarItem> _items = [
    BottomNavigationBarItem(
      icon: Icon(Icons.home),
      label: "Home",
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.messenger_rounded),
      label: "Messages",
    ),
    BottomNavigationBarItem(
      icon: Icon(Icons.settings),
      label: "Settings",
    )
  ];

  int _selectedPage;

  @override
  void initState() {
    super.initState();
    _selectedPage = 0;

    _pages = [
      MyPage(
        1,
        "Page 01",
        MyKeys.getKeys().elementAt(0),
      ),
      // This avoid the other pages to be built unnecessarily
      SizedBox(),
      SizedBox(),
    ];
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: WillPopScope(
        onWillPop: () async {
          return !await Navigator.maybePop(
            MyKeys.getKeys()[_selectedPage].currentState.context,
          );
        },
        child: IndexedStack(
          index: _selectedPage,
          children: _pages,
        ),
      ),
      bottomNavigationBar: BottomNavigationBar(
        items: _items,
        currentIndex: _selectedPage,
        onTap: (index) {
          setState(() {
            // now check if the chosen page has already been built
            // if it hasn't, then it still is a SizedBox
            if (_pages[index] is SizedBox) {
              if (index == 1) {
                _pages[index] = MyPage(
                  1,
                  "Page 02",
                  MyKeys.getKeys().elementAt(index),
                );
              } else {
                _pages[index] = MyPage(
                  1,
                  "Page 03",
                  MyKeys.getKeys().elementAt(index),
                );
              }
            }

            _selectedPage = index;
          });
        },
      ),
    );
  }
}

class MyPage extends StatelessWidget {
  MyPage(this.count, this.text, this.navigatorKey);
  final count;
  final text;
  final navigatorKey;
  @override
  Widget build(BuildContext context) {
    // You'll see that it will only print once
    print("Building $text with count: $count");
    return Navigator(
      key: navigatorKey,
      onGenerateRoute: (RouteSettings settings) {
        return MaterialPageRoute(
          builder: (BuildContext context) {
            return Scaffold(
              appBar: AppBar(
                title: Text(this.text),
              ),
              body: Center(
                child: RaisedButton(
                  child: Text(this.count.toString()),
                  onPressed: () {
                    Navigator.of(context).push(MaterialPageRoute(
                        builder: (ctx) => MyCustomPage(count + 1, text)));
                  },
                ),
              ),
            );
          },
        );
      },
    );
  }
}

class MyCustomPage extends StatelessWidget {
  MyCustomPage(this.count, this.text);
  final count;
  final text;
  @override
  Widget build(BuildContext parentContext) {
    return Scaffold(
      appBar: AppBar(
        title: Text(this.text),
      ),
      body: Column(
        children: [
          Expanded(
            child: Container(
              child: ListView.builder(
                itemCount: 15,
                itemBuilder: (context, index) {
                  return Container(
                    width: double.infinity,
                    child: Card(
                      child: Center(
                        child: RaisedButton(
                          child: Text(this.count.toString() + " pos($index)"),
                          onPressed: () {
                            Navigator.of(parentContext).push(MaterialPageRoute(
                                builder: (ctx) =>
                                    MyCustomPage(count + 1, text)));
                          },
                        ),
                      ),
                    ),
                  );
                },
              ),
            ),
          ),
        ],
      ),
    );
  }
}

class MyKeys {
  static final first = GlobalKey(debugLabel: 'page1');
  static final second = GlobalKey(debugLabel: 'page2');
  static final third = GlobalKey(debugLabel: 'page3');

  static List<GlobalKey> getKeys() => [first, second, third];
}
Run Code Online (Sandbox Code Playgroud)