将Flutter MaterialPageRoute作为全屏对话框出现在BottomNavigationBar下面 [英] Flutter MaterialPageRoute as fullscreenDialog appears underneath BottomNavigationBar

查看:172
本文介绍了将Flutter MaterialPageRoute作为全屏对话框出现在BottomNavigationBar下面的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

这里还有一个类似的未解决问题:全屏对话框屏幕不覆盖底部导航栏 但我想提供更多背景信息,以了解这是否有助于找到解决方案. 我们将从我的main.dart开始,在构建一个构建自定义DynamicAppMaterialApp.这是重要的东西:

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,但是全局DrawerBottomNavigationBar是在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后面而不是在所有内容之上滑动的.这是MaterialApponGenerateRoute方法:

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的新手,并且不太了解路由如何与上下文一起使用,因此我想知道调用NavigatornavigateTo方法的屏幕上下文是否成为主要的构建上下文,并且因此不在小部件树的顶部吗?

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的根支架,该根支架可以保留我的DrawerBottomNavigationBar,但是在其他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构建到每个.但是要做到这一点,我不得不改变我的架构的工作方式……为了更好!现在,我有一个BottomNavigationBarRootScreenSwitcher,它们侦听来自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,现在它包含一个ScaffoldDrawer,然后将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屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆