我想在我的应用程序中添加一个底部导航栏,并且希望它的行为如下:
运行此代码后,点击“打开子页面” -> 点击“点击我”,您应该在“主页子页面”中看到“已单击”。 如果现在在底部导航栏中点击“设置”,然后使用Android返回按钮,您将回到显示按钮为“已单击”的“主页”选项卡上,显示完全相同的页面。 如果您在底部导航栏中单击“设置”,然后单击“主页”,则再次进入显示按钮为“已单击”的完全相同页面。 这正是我需要的行为 但是,当您执行后者时,还会收到一个错误消息,指出“小部件树中检测到重复的GlobalKey。”。如果现在两次点击Android返回按钮,则会进入空白页面(由于显而易见的原因)。 如何避免此“重复全局键错误”,同时不失去我想要的行为?
希望我的解释有意义..
这与以下问题相关:Flutter persistent navigation bar with named routes?
- 每个选项卡都应该有自己的嵌套导航器,以便我可以在保留底部导航栏的同时切换到子路由
- 当切换到另一个选项卡时,我希望能够通过后退按钮返回到先前的选项卡
- 当我再次点击选项卡1时,我不想重新实例化子路由,而是想回到它的最后状态。例如,如果我在路由'/tab1/subRoute1'上,我希望再次进入这个视图。
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Bottom NavBar Demo',
home: BottomNavigationBarController(),
);
}
}
class BottomNavigationBarController extends StatefulWidget {
BottomNavigationBarController({Key key}) : super(key: key);
@override
_BottomNavigationBarControllerState createState() =>
_BottomNavigationBarControllerState();
}
class _BottomNavigationBarControllerState
extends State<BottomNavigationBarController> {
int _selectedIndex = 0;
List<int> _history = [0];
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
final List<BottomNavigationBarRootItem> bottomNavigationBarRootItems = [
BottomNavigationBarRootItem(
routeName: '/',
nestedNavigator: HomeNavigator(
navigatorKey: GlobalKey<NavigatorState>(),
),
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
),
BottomNavigationBarRootItem(
routeName: '/settings',
nestedNavigator: SettingsNavigator(
navigatorKey: GlobalKey<NavigatorState>(),
),
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Settings'),
),
),
];
@override
Widget build(BuildContext context) {
return Scaffold(
body: WillPopScope(
onWillPop: () async {
final nestedNavigatorState =
bottomNavigationBarRootItems[_selectedIndex]
.nestedNavigator
.navigatorKey
.currentState;
if (nestedNavigatorState.canPop()) {
nestedNavigatorState.pop();
return false;
} else if (_navigatorKey.currentState.canPop()) {
_navigatorKey.currentState.pop();
return false;
}
return true;
},
child: Navigator(
key: _navigatorKey,
initialRoute: bottomNavigationBarRootItems.first.routeName,
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
builder = (BuildContext context) {
return bottomNavigationBarRootItems
.where((element) => element.routeName == settings.name)
.first
.nestedNavigator;
};
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
),
),
bottomNavigationBar: BottomNavigationBar(
items: bottomNavigationBarRootItems
.map((e) => e.bottomNavigationBarItem)
.toList(),
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
);
}
void _onItemTapped(int index) {
if (index == _selectedIndex) return;
setState(() {
_selectedIndex = index;
_history.add(index);
_navigatorKey.currentState
.pushNamed(bottomNavigationBarRootItems[_selectedIndex].routeName)
.then((_) {
_history.removeLast();
setState(() => _selectedIndex = _history.last);
});
});
}
}
class BottomNavigationBarRootItem {
final String routeName;
final NestedNavigator nestedNavigator;
final BottomNavigationBarItem bottomNavigationBarItem;
BottomNavigationBarRootItem({
@required this.routeName,
@required this.nestedNavigator,
@required this.bottomNavigationBarItem,
});
}
abstract class NestedNavigator extends StatelessWidget {
final GlobalKey<NavigatorState> navigatorKey;
NestedNavigator({Key key, @required this.navigatorKey}) : super(key: key);
}
class HomeNavigator extends NestedNavigator {
HomeNavigator({Key key, @required GlobalKey<NavigatorState> navigatorKey})
: super(
key: key,
navigatorKey: navigatorKey,
);
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/':
builder = (BuildContext context) => HomePage();
break;
case '/home/1':
builder = (BuildContext context) => HomeSubPage();
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
);
}
}
class SettingsNavigator extends NestedNavigator {
SettingsNavigator({Key key, @required GlobalKey<NavigatorState> navigatorKey})
: super(
key: key,
navigatorKey: GlobalKey<NavigatorState>(),
);
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
initialRoute: '/',
onGenerateRoute: (RouteSettings settings) {
WidgetBuilder builder;
switch (settings.name) {
case '/':
builder = (BuildContext context) => SettingsPage();
break;
default:
throw Exception('Invalid route: ${settings.name}');
}
return MaterialPageRoute(
builder: builder,
settings: settings,
);
},
);
}
}
class HomePage extends StatelessWidget {
const HomePage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Page'),
),
body: Center(
child: RaisedButton(
onPressed: () => Navigator.of(context).pushNamed('/home/1'),
child: Text('Open Sub-Page'),
),
),
);
}
}
class HomeSubPage extends StatefulWidget {
const HomeSubPage({Key key}) : super(key: key);
@override
_HomeSubPageState createState() => _HomeSubPageState();
}
class _HomeSubPageState extends State<HomeSubPage> {
String _text;
@override
void initState() {
_text = 'Click me';
super.initState();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Home Sub Page'),
),
body: Center(
child: RaisedButton(
onPressed: () => setState(() => _text = 'Clicked'),
child: Text(_text),
),
),
);
}
}
class SettingsPage extends StatelessWidget {
const SettingsPage({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings Page'),
),
body: Container(
child: Center(
child: Text('Settings Page'),
),
),
);
}
}
运行此代码后,点击“打开子页面” -> 点击“点击我”,您应该在“主页子页面”中看到“已单击”。 如果现在在底部导航栏中点击“设置”,然后使用Android返回按钮,您将回到显示按钮为“已单击”的“主页”选项卡上,显示完全相同的页面。 如果您在底部导航栏中单击“设置”,然后单击“主页”,则再次进入显示按钮为“已单击”的完全相同页面。 这正是我需要的行为 但是,当您执行后者时,还会收到一个错误消息,指出“小部件树中检测到重复的GlobalKey。”。如果现在两次点击Android返回按钮,则会进入空白页面(由于显而易见的原因)。 如何避免此“重复全局键错误”,同时不失去我想要的行为?
希望我的解释有意义..
这与以下问题相关:Flutter persistent navigation bar with named routes?