Flutter MaterialPageRoute в полноэкранном режиме Диалог появляется под BottomNavigationBar - PullRequest
0 голосов
/ 03 августа 2020

Здесь есть аналогичный вопрос без ответа: Экран FullscreenDialog не закрывает нижнюю панель навигации , но я хочу предоставить больше контекста, чтобы посмотреть, поможет ли это найти решение. Мы начнем с моего main.dart сверху, я создаю MaterialApp, который создает собственный DynamicApp. Вот важные вещи:

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,
    );
  }

My DynamicApp настраивает root 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:

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 и root. Все это работает нормально, и я доволен общей структурой навигации. Каждый экран root имеет свой собственный AppBar, но глобальные Drawer и BottomNavigationBar обрабатываются в DynamicApp, поэтому мне не нужно постоянно устанавливать их на других экранах Scaffold.

Итак, пришло время представить другие разделы приложения, которые не обслуживаются нижней панелью вкладок и могут быть представлены с помощью Drawer или других кнопок действий. Каждый из этих новых разделов должен быть модальным fullscreenDialog экранами, чтобы они скользили вверх снизу, но имели свою собственную навигацию и каркас.

Моя проблема в том, что когда я перехожу на свой экран SettingsNavigator, он скользит вверх из-за BottomNavigationBar, а не поверх всего. Вот метод onGenerateRoute из MaterialApp:

onGenerateRoute: (routeSettings) {
        switch (routeSettings.name) {
          case SettingsNavigator.routeName:
            return MaterialPageRoute(
                builder: (context) => SettingsNavigator(),
                fullscreenDialog: true);
          default:
            return MaterialPageRoute(builder: (context) => SettingsNavigator());
        }
      }

Я новичок во Flutter и не совсем понимаю, как маршрутизация работает с контекстами, поэтому мне интересно, контекст экрана, который вызывает метод navigateTo из Navigator становится основным контекстом сборки и, следовательно, не находится на вершине дерева виджетов?

gif здесь: https://www.dropbox.com/s/nwgzo0q28cqk61p/FlutteRModalProb.gif?dl=0

Вот древовидная структура, показывающая, что скаффолд для экрана настроек был помещен внутри DynamicApp Scaffold. Модальное окно должно располагаться выше DynamicApp. введите описание изображения здесь

Может ли кто-нибудь пролить свет на это?

ОБНОВЛЕНИЕ: я попытался создать и поделиться уникальным ключом ScaffoldState для экранов панели вкладок, а затем на странице настроек есть другой ключ. Это не имело значения. Теперь мне интересно, имеет ли это BuildContext тот же родительский элемент.

ОБНОВЛЕНИЕ ОБНОВЛЕНИЕ: вчера вечером у меня был прорыв, который заставил меня понять, что просто невозможно использовать встроенные скаффолды в в том виде, в каком они есть у меня сейчас. Проблема в том, что у меня есть каркас root с именем DynamicApp, который сохраняет мои Drawer и BottomNavigationBar, но загрузка других страниц Scaffold в тело означает, что модальные окна вставляются в это тело и за BottomNavigationBar. Чтобы решить проблему, вы должны создать подкласс BottomNavigationBar и ссылаться на него в каждом Scaffold; что означает инкапсуляцию всех бизнес-логов c, поэтому он использует ChangeNotifier для изменения состояния при взаимодействии с навигацией. По сути, Flutter требует разделения задач на вашу архитектуру, что, я думаю, хорошо. Я напишу лучший ответ, когда сделаю всю дополнительную работу.

1 Ответ

0 голосов
/ 06 августа 2020

После того, как я много часов рвал волосы, пытаясь передать ScaffoldState ключи, и Navigators ответ заключался в том, чтобы встроить BottomNavigationBar в каждые Scaffold. Но для этого мне пришлось изменить принцип работы моей архитектуры ... к лучшему! Теперь у меня есть BottomNavigationBar и RootScreenSwitcher, которые прослушивают обновления от AppState ChangeNotifier и восстанавливают себя при изменении индекса страницы. Итак, мне нужно изменить состояние только в одном месте, чтобы приложение адаптировалось автоматически. Это класс AppState:

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:

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`:

bottomNavigationBar: AppBottomNavigationBar(),

Что означает абсолютный минимальный шаблон. Я изменил имя класса DynamicApp на AppRootScaffold, и теперь он содержит Scaffold, Drawer, а затем установил тело Scaffold как 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(),
      ),
    );
  }
}

Мне еще многое предстоит сделать, чтобы оптимизировать эту архитектуру, но это определенно лучший способ go.

ОБНОВЛЕНИЕ: вы можете определить проблему с новой архитектурой Scaffold? введите описание изображения здесь Это все еще не здорово. По иронии судьбы, мне нужно вернуть BottomNavigationBar в root Scaffold, чтобы это работало должным образом. Но тогда модальные окна больше не появятся поверх панели.

...