如何以编程方式 select BottomNavigationBar Tab in Flutter 而不是内置 onTap 回调?

How to programmatically select BottomNavigationBar Tab in Flutter instead of built in onTap callback?

我一直在使用 BottomNavigationBar,但我无法在 BottomNavigationBar 的 onTap 回调之外以编程方式select Tab。

带 onTap 回调的代码,有效:

    return new BottomNavigationBar(
  items: <BottomNavigationBarItem>[
    _bottomNavigationItem(Icons.account_circle, DrawerTitles.CONTACTS),
    _bottomNavigationItem(Icons.delete, DrawerTitles.DELETED_CONTACTS),
    _bottomNavigationItem(Icons.list, DrawerTitles.LOGS),
  ],
  onTap: (int index) {
    setState(() {
      navigationIndex = index;
      switch (navigationIndex) {
        case 0:
          handleBottomNavigationBarClicks(DrawerTitles.CONTACTS);
          break;
        case 1:
          handleBottomNavigationBarClicks(DrawerTitles.DELETED_CONTACTS);
          break;
        case 2:
          handleBottomNavigationBarClicks(DrawerTitles.LOGS);
          break;
      }
    });
  },
  currentIndex: navigationIndex,
  fixedColor: Colors.blue[400],
  type: BottomNavigationBarType.fixed,
);

但我想在 onTap 回调之外更改选项卡。

我试过在 onTap callBack 之外更改 BottomNavigatioBar 使用的索引,但没有成功。

这是我尝试过的:

void changeTabs(int tabIndex) {
setState(() {
     navigationIndex = tabIndex;
});}

这里是代码gist

有什么方法可以更改标签页吗?

您可以使用 GlobalKey 获取此 BottomNavigationBar 小部件。通过这个 GlobalKey 你可以处理这个小部件。 Here is an gist for the code

这里分配一个GlobalKey

GlobalKey globalKey = new GlobalKey(debugLabel: 'btm_app_bar');

然后将该键放入您的 BottomNavigationBar

new BottomNavigationBar(
    key: globalKey,
    items: [...],
   onTap: (int index) {...},
  ),

如果您必须使用 CurvedNavigationBar,现在您可以调用小部件的方法并使用 CurvedNavigationBar 而不是 BottomNavigationBar。

 final BottomNavigationBar navigationBar = globalKey.currentWidget;
 navigationBar.onTap(2);

这里有一个完整的例子,说明如何实现你想要的。

import 'package:flutter/material.dart';

void main() => runApp(new MyApp());

const String page1 = "Page 1";
const String page2 = "Page 2";
const String page3 = "Page 3";
const String title = "BNB Demo";

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return new MaterialApp(
      title: title,
      home: new MyHomePage(title: title),
    );
  }
}

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> {
  List<Widget> _pages;
  Widget _page1;
  Widget _page2;
  Widget _page3;

  int _currentIndex;
  Widget _currentPage;

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

    _page1 = Page1();
    _page2 = Page2();
    _page3 = Page3();

    _pages = [_page1, _page2, _page3];

    _currentIndex = 0;
    _currentPage = _page1;
  }

  void changeTab(int index) {
    setState(() {
      _currentIndex = index;
      _currentPage = _pages[index];
    });
  }

  @override
  Widget build(BuildContext context) {
    return new Scaffold(
      appBar: new AppBar(
        title: new Text(widget.title),
      ),
      body: _currentPage,
      bottomNavigationBar: BottomNavigationBar(
          onTap: (index) => changeTab(index),
          currentIndex: _currentIndex,
          items: [
            BottomNavigationBarItem(
                title: Text(page1), icon: Icon(Icons.account_circle)),
            BottomNavigationBarItem(
                title: Text(page2), icon: Icon(Icons.account_circle)),
            BottomNavigationBarItem(
                title: Text(page3), icon: Icon(Icons.account_circle))
          ]),
      drawer: new Drawer(
        child: new Container(
          margin: EdgeInsets.only(top: 20.0),
          child: new Column(
            children: <Widget>[
              navigationItemListTitle(page1, 0),
              navigationItemListTitle(page2, 1),
              navigationItemListTitle(page3, 2),
            ],
          ),
        ),
      ),
    );
  }

  Widget navigationItemListTitle(String title, int index) {
    return new ListTile(
      title: new Text(
        title,
        style: new TextStyle(color: Colors.blue[400], fontSize: 22.0),
      ),
      onTap: () {
        Navigator.pop(context);
        changeTab(index);
      },
    );
  }
}

class Page1 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page1),
    );
  }
}

class Page2 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page2),
    );
  }
}

class Page3 extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text(page3),
    );
  }
}

每当您想切换到标签页时,请调用 changeTab(YOUR_TAB_INDEX)

CupertinoApp 的另一种变通解决方案。它正在满足我的应用要求。

它也是“有没有办法从子项更改标签页?”的答案,因为我们可以在任何子项中传递控制器,我们可以更改标签页索引。

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

void main() {
  runApp(CupertinoApp(home: HomeScreen(),));
}

final GlobalKey<NavigatorState> firstTabNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> secondTabNavKey = GlobalKey<NavigatorState>();
final GlobalKey<NavigatorState> thirdTabNavKey = GlobalKey<NavigatorState>();

class HomeScreen extends StatelessWidget {

  var controller = CupertinoTabController(initialIndex: 0);

  @override
  Widget build(BuildContext context) {
    return CupertinoTabScaffold(
        controller: controller,
        tabBar: CupertinoTabBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.home)),
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.cart)),
            BottomNavigationBarItem(icon: Icon(CupertinoIcons.person)),
          ],
        ),
        tabBuilder: (context, index) {
          if (index == 0) {
            return CupertinoTabView(navigatorKey: firstTabNavKey, builder: (context) => FirstTab(controller: this.controller,),);
          } else if (index == 1) {
            return CupertinoTabView(navigatorKey: secondTabNavKey, builder: (context) => SecondTab(),);
          } else {
            return CupertinoTabView(navigatorKey: thirdTabNavKey, builder: (context) => ThirdTab(),);
          }
        }
    );
  }
}

class FirstTab extends StatelessWidget {
  final CupertinoTabController controller;

  FirstTab({required this.controller});

  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold( child: Container(
      child: Center(
        child: Row(
          children: <Widget>[
            TextButton(onPressed: () => {controller.index = 1}, child: Text('Second Tab')),
            TextButton(onPressed: () => {controller.index = 2}, child: Text('Third Tab')),
          ],
        ),
      ),
    ));
  }
}

class SecondTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold( child:Container(child: Center( child: Text('Second Screen'),),));
  }
}

class ThirdTab extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return CupertinoPageScaffold( child:Container(child: Center( child: Text('Third Screen'),),));
  }
}

感谢@HuyHoàng 的 flutter 版本 2+ 我使用这个:

var bottomWidgetKey=new GlobalKey<State<BottomNavigationBar>>();

然后将此键分配给 BottomNavigationBar 然后可以像下面这样访问底部:

BottomNavigationBar navigationBar =  bottomWidgetKey.currentWidget as BottomNavigationBar;
navigationBar.onTap!(1);

如果您想从小部件内部更改选项卡,您可以这样做:

假设这是您要从中更改当前活动选项卡的小部件:

class MyWidget extends StatefulWidget {

  final Function() openHomeTab;

  MyWidget({
    this.openHomeTab,
  });
  @override
  _MyWidget createState() => _MyWidget();
}

class _MyWidget extends State<MyWidget> {
  @override
  Widget build(BuildContext context) {
    return CupertinoButton(
      onPressed: () {
        widget.openHomeTab();
      },
    );
  }
}

现在在包含选项卡栏 (BottomNavigationBar) 的小部件中:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentTab,
        children: _buildScreens(),
      ),
      bottomNavigationBar: BottomNavigationBar(
        showSelectedLabels: true,
        showUnselectedLabels: true,
        selectedItemColor: Colors.primary,
        //selectedLabelStyle: TextStyle(),
        elevation: 0,
        type: BottomNavigationBarType.fixed,
        onTap: (index) {
          openTab(index);
        },
        currentIndex: _currentTab,
        items: <BottomNavigationBarItem>[
          _buildTabIcon(0),
          _buildTabIcon(1),
          _buildTabIcon(2),
          _buildTabIcon(3),
          _buildTabIcon(4)
        ],
      ),
    );
  }

 
  List<StatefulWidget> _buildScreens() {
    _screens = <StatefulWidget>[
      ..., 
      ...,
      ...,
      MyWidget(
        openHomeTab: openHomeTab,
      )
    ];
    return _screens;
  }

  void openHomeTab() {
    openTab(0);
  }

  void openTab(int index) {
    setState(() {
      _currentTab = index;
    });
  }

这是回答上面第一个问题的完整示例。代码是从上面修改而来的。注意将回调方法传递给第 3 页构造函数。

import 'package:flutter/material.dart';

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

const String page1 = "Home";
const String page2 = "Service";
const String page3 = "Profile";
const String title = "BNB Demo";

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return const MaterialApp(
      title: title,
      home: MyHomePage(title: title),
    );
  }
}

class MyHomePage extends StatefulWidget {
  const MyHomePage({Key? key, required this.title}) : super(key: key);
  final String title;

  @override
  State<MyHomePage> createState() => _MyHomePageState();
}

class _MyHomePageState extends State<MyHomePage> {
  late List<Widget> _pages;
  late Widget _page1;
  late Widget _page2;
  late Widget _page3;
  late int _currentIndex;
  late Widget _currentPage;

  @override
  void initState() {
    super.initState();
    _page1 = const Page1();
    _page2 = const Page2();
    _page3 = Page3(changePage: _changeTab);
    _pages = [_page1, _page2, _page3];
    _currentIndex = 0;
    _currentPage = _page1;
  }

  void _changeTab(int index) {
    setState(() {
      _currentIndex = index;
      _currentPage = _pages[index];
    });
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(widget.title),
      ),
      body: _currentPage,
      bottomNavigationBar: BottomNavigationBar(
          onTap: (index) {
            _changeTab(index);
          },
          currentIndex: _currentIndex,
          items: const [
            BottomNavigationBarItem(
              label: page1,
              icon: Icon(Icons.home),
            ),
            BottomNavigationBarItem(
              label: page2,
              icon: Icon(Icons.home_repair_service),
            ),
            BottomNavigationBarItem(
              label: page3,
              icon: Icon(Icons.person),
            ),
          ]),
      drawer: Drawer(
        child: Container(
          margin: const EdgeInsets.only(top: 20.0),
          child: Column(
            children: <Widget>[
              _navigationItemListTitle(page1, 0),
              _navigationItemListTitle(page2, 1),
              _navigationItemListTitle(page3, 2),
            ],
          ),
        ),
      ),
    );
  }

  Widget _navigationItemListTitle(String title, int index) {
    return ListTile(
      title: Text(
        '$title Page',
        style: TextStyle(color: Colors.blue[400], fontSize: 22.0),
      ),
      onTap: () {
        Navigator.pop(context);
        _changeTab(index);
      },
    );
  }
}

class Page1 extends StatelessWidget {
  const Page1({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$page1 Page', style: Theme.of(context).textTheme.headline6),
    );
  }
}

class Page2 extends StatelessWidget {
  const Page2({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return Center(
      child: Text('$page2 Page', style: Theme.of(context).textTheme.headline6),
    );
  }
}

class Page3 extends StatelessWidget {
  const Page3({Key? key, required this.changePage}) : super(key: key);
  final void Function(int) changePage;

  @override
  Widget build(BuildContext context) {
    return Align(
      alignment: Alignment.center,
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          Text('$page3 Page', style: Theme.of(context).textTheme.headline6),
          ElevatedButton(
            onPressed: () => changePage(0),
            child: const Text('Switch to Home Page'),
          )
        ],
      ),
    );
  }
}