将Flutter MaterialPageRoute作为全屏对话框出现在BottomNavigationBar下面 [英] Flutter MaterialPageRoute as fullscreenDialog appears underneath BottomNavigationBar
问题描述
这里还有一个类似的未解决问题:全屏对话框屏幕不覆盖底部导航栏
但我想提供更多背景信息,以了解这是否有助于找到解决方案.
我们将从我的main.dart
开始,在构建一个构建自定义DynamicApp
的MaterialApp
.这是重要的东西:
There's a similar unanswered question here: FullscreenDialog screen not covering bottomnavigationbar
but I want to provide more context, to see if that helps find a solution.
We'll start at the top with my main.dart
, I am building a MaterialApp
that builds a custom DynamicApp
. Here's the important stuff:
Widget build(BuildContext context) {
var _rootScreenSwitcher = RootScreenSwitcher(key: switcherKey);
return MaterialApp(
title: 'Flutter Demo',
theme: ThemeData(
primarySwatch: Colors.green,
visualDensity: VisualDensity.adaptivePlatformDensity,
),
builder: (context, child) {
return DynamicApp(
navigator: locator<NavigationService>().navigatorKey,
child: child,
switcher: _rootScreenSwitcher,
);
},
navigatorKey: locator<NavigationService>().navigatorKey,
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case SettingsNavigator.routeName:
return MaterialPageRoute(
builder: (context) => SettingsNavigator(),
fullscreenDialog: true);
default:
return MaterialPageRoute(builder: (context) => SettingsNavigator());
}
},
home: _rootScreenSwitcher,
);
}
我的DynamicApp
像这样设置根Scaffold
:
Widget build(BuildContext context) {
return Scaffold(
drawer: NavigationDrawer(
selectedIndex: _selectedIndex,
drawerItems: widget.drawerItems,
headerView: Container(
child: Text('Drawer Header'),
decoration: BoxDecoration(color: Colors.blue),
),
onNavigationItemSelect: (index) {
onTapped(index);
return true; // true means that drawer must close and false is Vice versa
},
),
bottomNavigationBar: BottomNavigationBar(
type: BottomNavigationBarType.fixed,
onTap: (index) {
onTapped(index);
},
currentIndex: _selectedIndex,
items: bottomNavBarItems,
showUnselectedLabels: false,
),
body: widget.child,
);
}
DynamicApp
的子项是一个名为RootScreenSwitcher
的小部件,它是一个IndexedStack
,用于控制我的BottomNavigationBar
中的屏幕切换以及在Drawer
中选择了项目.这是RootScreenSwitcher
:
The child of the DynamicApp
is a widget called RootScreenSwitcher
which is an IndexedStack
and controls the switching of screens from my BottomNavigationBar
and also when items are selected in the Drawer
. Here's the RootScreenSwitcher
:
class RootScreenSwitcherState extends State<RootScreenSwitcher> {
int _currentIndex = 0;
int get currentIndex => _currentIndex;
set currentIndex(index) {
setState(() {
_currentIndex = index;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: SafeArea(
top: false,
child: IndexedStack(
index: _currentIndex,
children: menuItems.map<Widget>((MenuItem item) {
return item.widget;
}).toList(),
),
),
);
}
void switchScreen(int index) {
setState(() {
_currentIndex = index;
});
}
}
应用程序主要部分的每个部分都有其自己的Navigator
和根屏幕.一切正常,我对整体导航结构感到满意.每个根屏幕都有自己的AppBar
,但是全局Drawer
和BottomNavigationBar
是在DynamicApp
中处理的,因此我不必在其他Scaffold
屏幕中继续设置它们.
Each of the main section of the app has its own Navigator
and root screen. This all works fine, and I'm happy with the overall navigation structure. Each root screen has its own AppBar
but the global Drawer
and BottomNavigationBar
are handled in the DynamicApp
so I don't have to keep setting them in the other Scaffold
screens.
因此,接下来开始介绍该应用程序的其他部分,这些部分不受底部标签栏的服务,可以通过Drawer
或其他操作按钮进行显示.这些新部分中的每一个都必须是模式fullscreenDialog
屏幕,以便它们从底部向上滑动,但具有自己的导航和支架.
So, then it came to start to introduce other sections of the app that are not serviced by the bottom tab bar, and can be presented from the Drawer
or from other action buttons. Each of these new sections would have to be modal fullscreenDialog
screens so they slide up from the bottom, but have their own navigation and scaffold.
我的问题是,当我导航到SettingsNavigator
屏幕时,它是从BottomNavigationBar
后面而不是在所有内容之上滑动的.这是MaterialApp
的onGenerateRoute
方法:
My issue is that when I navigate to my SettingsNavigator
screen it slides up from behind the BottomNavigationBar
, and not on top of everything. Here's the onGenerateRoute
method of the MaterialApp
:
onGenerateRoute: (routeSettings) {
switch (routeSettings.name) {
case SettingsNavigator.routeName:
return MaterialPageRoute(
builder: (context) => SettingsNavigator(),
fullscreenDialog: true);
default:
return MaterialPageRoute(builder: (context) => SettingsNavigator());
}
}
我是Flutter的新手,并且不太了解路由如何与上下文一起使用,因此我想知道调用Navigator
的navigateTo
方法的屏幕上下文是否成为主要的构建上下文,并且因此不在小部件树的顶部吗?
I'm new to Flutter and don't quite understand how routing works with contexts, so I am wondering whether the context of the screen that calls the navigateTo
method of the Navigator
becomes the main build context, and is therefore not on top of the widget tree?
gif此处: https://www.dropbox.com/s/nwgzo0q28cqk61p/FlutteRModalProb.gif?dl = 0
这是树状结构,显示设置"屏幕的脚手架已放置在DynamicApp
Scaffold
内部.模态需要坐在DynamicApp
上方.
Here's the tree structure that shows that the Scaffold for the Settings screen has been placed inside the DynamicApp
Scaffold
. The modal needs to sit above DynamicApp
.
任何人都可以对此有所了解吗?
Can anyone shed some light on this?
更新:我尝试为标签栏屏幕创建和共享唯一的ScaffoldState
键,然后设置"页面具有不同的键.没关系.我现在想知道是不是BuildContext
具有相同的父代.
UPDATE: I have tried creating and sharing a unique ScaffoldState
key for the tab bar screens, and then the Settings page has a different key. It made no difference. I wonder now if it is the BuildContext
having the same parent.
UPDATE UPDATE:
昨晚我取得了突破,这使我意识到以目前的方式使用嵌入式脚手架是不可能的.问题是我有一个名为DynamicApp
的根支架,该根支架可以保留我的Drawer
和BottomNavigationBar
,但是在其他Scaffold
页中加载到主体中意味着模态插入该主体中并在BottomNavigationBar
后面.为了解决这个问题,您必须将BottomNavigationBar
子类化,并在每个Scaffold
中引用它;这意味着封装所有业务逻辑,以便在与导航交互时使用ChangeNotifier
更改状态.基本上,Flutter会在您的体系结构上强制进行关注点分离,我认为这是一件好事.完成所有额外工作后,我将为您提供一个更好的答案.
UPDATE UPDATE:
I had a breakthrough last night which has made me realise that it just isn't going to be possible to use embedded Scaffolds in the way I have them at the moment. The problem is that i have a root scaffold called DynamicApp
which persists my Drawer
and BottomNavigationBar
, but loading in other Scaffold
pages into the body means the modals are slotting into that body and behind the BottomNavigationBar
. To solve the problem you have to subclass BottomNavigationBar
and reference it in every Scaffold
; which means encapsulating all of the business logic so it uses ChangeNotifier
to change state when the nav is interacted with. Basically, Flutter forces a separation of concerns on your architecture, which I guess is a good thing. I'll compose a better answer when I've done all the extra work.
推荐答案
经过数小时的努力,我试图将ScaffoldState
密钥传递给我,而Navigators
的答案是将BottomNavigationBar
构建到每个BottomNavigationBar
和RootScreenSwitcher
,它们侦听来自AppState
ChangeNotifier
的更新,并在页面索引更改时自行重建.因此,我只需要在一个地方更改状态即可使应用程序自动适应.这是AppState
类别:
After many hours tearing my hair out trying to pass ScaffoldState
keys around, and Navigators
the answer was to build the BottomNavigationBar
into every Scaffold
. But to do this I've had to change how my architecture works ... for the better! I now have a BottomNavigationBar
and RootScreenSwitcher
that listens for updates from an AppState
ChangeNotifier
and rebuilds itself when the page index changes. So, I only have to change state in one place for the app to adapt automatically. This is the AppState
class:
import 'package:flutter/material.dart';
class AppState extends ChangeNotifier {
int _pageIndex = 0;
int get pageIndex {
return _pageIndex;
}
set setpageIndex(int index) {
_pageIndex = index;
notifyListeners();
}
}
这是我的自定义BottomNavigationBar
,称为AppBottomNavigationBar
:
and this is my custom BottomNavigationBar
called AppBottomNavigationBar
:
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
class AppBottomNavigationBar extends StatefulWidget {
AppBottomNavigationBar({Key key}) : super(key: key);
@override
_AppBottomNavigationBarState createState() => _AppBottomNavigationBarState();
}
class _AppBottomNavigationBarState extends State<AppBottomNavigationBar> {
@override
Widget build(BuildContext context) {
var state = Provider.of<AppState>(
context,
);
int currentIndex = state.pageIndex;
return BottomNavigationBar(
type: BottomNavigationBarType.fixed,
currentIndex: currentIndex ?? 0,
items: bottomNavBarItems,
showUnselectedLabels: false,
onTap: (int index) {
setState(() {
state.setpageIndex = index;
});
},
);
}
}
因此,现在在我的其他Scaffold
页中,我只需要包括以下行即可确保BottomNavigationBar is in the
Scaffold`:
So, now in my other Scaffold
pages I only need to include this line to make sure the BottomNavigationBar is in the
Scaffold`:
bottomNavigationBar: AppBottomNavigationBar(),
这意味着绝对最小的样板.
我将DynamicApp
类的名称更改为AppRootScaffold
,现在它包含一个Scaffold
,Drawer
,然后将Scaffold
的主体设置为RootScreenSwitcher
:
Which means absolute minimal boilerplate.
I changed the name of the DynamicApp
class to AppRootScaffold
and this now contains a Scaffold
, Drawer
, and then set the body of the Scaffold
as the RootScreenSwitcher
:
class RootScreenSwitcher extends StatelessWidget {
RootScreenSwitcher({Key key}) : super(key: key);
@override
Widget build(BuildContext context) {
var state = Provider.of<AppState>(
context,
);
int currentIndex = state.pageIndex;
return SafeArea(
top: false,
child: IndexedStack(
index: currentIndex ?? 0,
children: menuItems.map<Widget>((MenuItem item) {
return item.widget;
}).toList(),
),
);
}
}
要简化此架构,我还有很多工作要做,但这绝对是更好的选择.
I still have a lot to do to streamline this architecture, but it is definitely the better way to go.
更新:
您能发现新的Scaffold
体系结构的问题吗?
这仍然不是很好.具有讽刺意味的是,我需要根目录Scaffold
中的BottomNavigationBar
才能正常工作.但是随后,模态将不会再次出现在栏的顶部.
UPDATE:
Can you spot the problem with the new Scaffold
architecture?
This is still not great. Ironically, I need the BottomNavigationBar
back in the root Scaffold
for this to work as expected. But then the modals wont appear over the top of the bar again.
这篇关于将Flutter MaterialPageRoute作为全屏对话框出现在BottomNavigationBar下面的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!