我想在我的应用程序中有一个底部导航栏,其行为类似于以下内容:
我能够达到1和2,但我停留在第3点。 这是我的构造:
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后退按钮,您将进入一个空白页面(出于种种原因)。 如何避免这种重复的全局密钥错误而又不会丢失我想要的行为?
我希望我的解释是合理的..
可以完美实现此功能的示例应用程序是Instagram。
答案 0 :(得分:5)
您希望使导航标签像Twitter,Instagram,应用一样,以便每个标签都具有自己的导航历史记录和独家新闻
我想我了解您想要实现的目标,但是您做错了
您应该对'ScoopWillPop'中的标签内容使用'tabBarView',并让每个标签管理自己的导航历史记录,在我的一个项目进行了如此多的努力之后,我发现了实现此想法的最佳方法
我对您的代码进行了许多更改,希望您可以清楚了解
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
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> with SingleTickerProviderStateMixin{
int _selectedIndex = 0;
List<int> _history = [0];
GlobalKey<NavigatorState> _navigatorKey = GlobalKey<NavigatorState>();
TabController _tabController;
List<Widget> mainTabs;
List<BuildContext> navStack = [null, null]; // one buildContext for each tab to store history of navigation
@override
void initState() {
_tabController = TabController(vsync: this, length: 2);
mainTabs = <Widget>[
Navigator(
onGenerateRoute: (RouteSettings settings){
return PageRouteBuilder(pageBuilder: (context, animiX, animiY) { // use page PageRouteBuilder instead of 'PageRouteBuilder' to avoid material route animation
navStack[0] = context;
return HomePage();
});
}),
Navigator(
onGenerateRoute: (RouteSettings settings){
return PageRouteBuilder(pageBuilder: (context, animiX, animiY) { // use page PageRouteBuilder instead of 'PageRouteBuilder' to avoid material route animation
navStack[1] = context;
return SettingsPage();
});
}),
];
super.initState();
}
final List<BottomNavigationBarRootItem> bottomNavigationBarRootItems = [
BottomNavigationBarRootItem(
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.home),
title: Text('Home'),
),
),
BottomNavigationBarRootItem(
bottomNavigationBarItem: BottomNavigationBarItem(
icon: Icon(Icons.settings),
title: Text('Settings'),
),
),
];
@override
Widget build(BuildContext context) {
return WillPopScope(
child: Scaffold(
body: TabBarView(
controller: _tabController,
physics: NeverScrollableScrollPhysics(),
children: mainTabs,
),
bottomNavigationBar: BottomNavigationBar(
items: bottomNavigationBarRootItems.map((e) => e.bottomNavigationBarItem).toList(),
currentIndex: _selectedIndex,
selectedItemColor: Colors.amber[800],
onTap: _onItemTapped,
),
),
onWillPop: () async{
if (Navigator.of(navStack[_tabController.index]).canPop()) {
Navigator.of(navStack[_tabController.index]).pop();
setState((){ _selectedIndex = _tabController.index; });
return false;
}else{
if(_tabController.index == 0){
setState((){ _selectedIndex = _tabController.index; });
SystemChannels.platform.invokeMethod('SystemNavigator.pop'); // close the app
return true;
}else{
_tabController.index = 0; // back to first tap if current tab history stack is empty
setState((){ _selectedIndex = _tabController.index; });
return false;
}
}
},
);
}
void _onItemTapped(int index) {
_tabController.index = index;
setState(() => _selectedIndex = index);
}
}
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 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).push(MaterialPageRoute(builder: (context) => HomeSubPage())),
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> with AutomaticKeepAliveClientMixin{
@override
// implement wantKeepAlive
bool get wantKeepAlive => true;
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),
),
),
);
}
}
/* convert it to statfull so i can use AutomaticKeepAliveClientMixin to avoid disposing tap */
class SettingsPage extends StatefulWidget {
@override
_SettingsPageState createState() => _SettingsPageState();
}
class _SettingsPageState extends State<SettingsPage> with AutomaticKeepAliveClientMixin{
@override
// implement wantKeepAlive
bool get wantKeepAlive => true;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text('Settings Page'),
),
body: Container(
child: Center(
child: Text('Settings Page'),
),
),
);
}
}
答案 1 :(得分:0)
您只需要使用nestedNavigator
child: bottomNavigationBarRootItems[_selectedIndex].nestedNavigator,
删除父导航器
// child: Navigator(
// key: _navigatorKey,
// initialRoute: bottomNavigationBarRootItems.first.routeName,
// onGenerateRoute: (RouteSettings settings) {
// WidgetBuilder builder;
// builder = (BuildContext context) {
// return bottomNavigationBarRootItems
// .where((element) => element.routeName ==settings.name)
// .last
// .nestedNavigator;
// };
// builder = (BuildContext context) => HomePage();
// return MaterialPageRoute(
// builder: builder,
// settings: settings,
// );
// },
// ),
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);
// });
});
}
答案 2 :(得分:0)
通常,每当您切换到另一个选项卡时,选项卡的状态都会丢失,我之前使用过Notes应用程序,它的底部导航栏可以在两者之间进行切换,我设法保存每个选项卡的状态甚至更新数据其他选项卡,然后再仅使用数据库切换到其他选项卡(如果这样做不能帮助尝试使用共享首选项)。希望对您有所帮助! 祝你好运!