Закрытие ящика при щелчке нижней панели навигации, флаттер

я хочу закрыть свой виджет каждый раз, когда пользователь нажимает кнопку в , но не могу понять эту вещь. Теперь моя настройка BNB заключается в том, что текущее состояние всего экрана запоминается через приложение, но я хочу закрыть ящик, если он открыт на любом из экранов до нажатия кнопки BNB. Каждый из моих экранов имеет свои собственные ящики и панели приложений, поэтому я не могу сделать один ящик внутри BNB (или я могу, и я могу динамически менять их с помощью переключателя, когда нажимается конкретный экран, НО тогда ящик будет закрывать Нижняя панель навигации и т. д.), но пока я хочу, чтобы она работала так. Итак, вот код с некоторыми поясняющими комментариями внутри:

Нижняя панель навигации:

      class BottomNavBar extends StatefulWidget {
  static const String id = 'bottom_navbar_screen';
  @override
  _BottomNavBarState createState() => _BottomNavBarState();
}

class _BottomNavBarState extends State<BottomNavBar> {
  int _selectedIndex = 0;

  /// list of screen that will render inside the BNB
  List<Navigation> _items = [
    Navigation(
        widget: Screen1(), navigationKey: GlobalKey<NavigatorState>()),
    Navigation(
        widget: Screen2(), navigationKey: GlobalKey<NavigatorState>()),
    Navigation(
        widget: Screen3(), navigationKey: GlobalKey<NavigatorState>()),
    Navigation(
        widget: Screen4(), navigationKey: GlobalKey<NavigatorState>()),
  ];

  /// function that renders components based on selected one in the BNB

  void _onItemTapped(int index) {
    if (index == _selectedIndex) {
      _items[index]
          .navigationKey
          .currentState
          .popUntil((route) => route.isFirst);
    } else {
      setState(() {
        _selectedIndex = index;
      });
    }
 /// when the index is selected, on the button press do some actions
    switch (_selectedIndex) {
      case 0:
         //  Do some actions
        break;
      case 1:
         //  Do some actions
        break;
      case 2:
         //  Do some actions
        break;
      case 3:
         //  Do some actions
        break;
    }
  }

  /// navigation Tab widget for a list of all the screens and puts them in a Indexed Stack
  Widget _navigationTab(
      {GlobalKey<NavigatorState> navigationKey, Widget widget}) {
    return Navigator(
      key: navigationKey,
      onGenerateRoute: (routeSettings) {
        return MaterialPageRoute(builder: (context) => widget);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final isFirstRouteInCurrentTab =
            !await _items[_selectedIndex].navigationKey.currentState.maybePop();
        if (isFirstRouteInCurrentTab) {
          if (_selectedIndex != 0) {
            _onItemTapped(1);
            return false;
          }
        }

        /// let system handle back button if we're on the first route
        return isFirstRouteInCurrentTab;
      },
      child: Scaffold(
        body: IndexedStack(
          index: _selectedIndex,
          children: _items
              .map((e) => _navigationTab(
                  navigationKey: e.navigationKey, widget: e.widget))
              .toList(),
        ),
        bottomNavigationBar: BottomNavigationBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              label: 'Screen 1,
            ),
            BottomNavigationBarItem(
              label: 'Screen 2,
            ),
            BottomNavigationBarItem(
              label: 'Screen 3,
            ),
            BottomNavigationBarItem(
              label: 'Screen 4,
            ),
          ],
          currentIndex: _selectedIndex,
          showUnselectedLabels: true,
          onTap: _onItemTapped,
        ),
      ),
    );
  }
}

Допустим, все 4 экрана одинаковы и у них есть свои AppBar и Drawer:

      @override
  Widget build(BuildContext context) {
    return  Scaffold(
        backgroundColor: Colors.white,
        drawer: Drawer(), // so this is what I want to close on BNB button press in each of the 4 screens
        appBar: AppBar() // each screen has its own app bar
          title: Text('Screens 1-4),
        ),
        body: Text('Body of Screens 1-4)
      ),
    );
  }

Так как каждый из экранов имеет свои собственные панели приложений и ящики, ящик не отображается поверх нижней панели навигации, поэтому мои кнопки BNB можно нажимать. Если я поставлю один ящик для всех экранов внутри BNB, то вы не сможете щелкнуть BNB, если сначала не закроете ящик, чего я сейчас не ищу.

Итак, мой последний вопрос: как мне закрыть каждый из экранных ящиков (если они ранее были открыты, то есть) при нажатии на нижнюю панель навигации? (т.е. я на экране 1, я открываю ящик, затем нажимаю на экран 2 в BNB и хочу / закройте ящик на экране 1, прежде чем я перейду на экран 2.)

Заранее спасибо за помощь!

1 ответ

Хороший способ сделать это — использовать GlobalKey для вашего скаффолда. Итак, для всех ваших лесов вы определяете их, используя:

      class SomeClass extends StatelessWidget {
  final scaffoldKey = GlobalKey<ScaffoldState>()

  Widget build(BuildContext context) {
    Scaffold(
      backgroundColor: Colors.white,
      drawer: Drawer(), // so this is what I want to close on BNB button press in each of the 4 screens
      appBar: AppBar( // each screen has its own app bar
        title: Text('Screens 1-4),
      ),
      body: Text('Body of Screens 1-4),
      key: scaffoldKey,
      ),
    );
  }

}

И затем вы можете передать этот ключ в свой BottomNavigationBar. В вашем BottomNavigationBar вы можете иметь все scaffoldKeys и в функции onItemTap:

      void _onItemTapped(int index) {
    for (scaffoldKey in scaffoldKeys) {
      // If the drawer is open
      if (scaffoldKey.currentState.isDrawerOpen) {
        // Closes the drawer
        scaffoldKey.currentState?.openEndDrawer();
      }
    }
    if (index == _selectedIndex) {
      _items[index]
          .navigationKey
          .currentState
          .popUntil((route) => route.isFirst);
    } else {
      setState(() {
        _selectedIndex = index;
      });
    }
 /// when the index is selected, on the button press do some actions
    switch (_selectedIndex) {
      case 0:
         //  Do some actions
        break;
      case 1:
         //  Do some actions
        break;
      case 2:
         //  Do some actions
        break;
      case 3:
         //  Do some actions
        break;
    }
  }

Вам решать, как найти лучший способ передачи ключей. Например, вы можете определить их в виджетах, содержащих как нижнюю панель навигации, так и различные каркасы, и передать их в качестве параметров. Вы можете использовать State Management... все, что подходит для вашего варианта использования.

Вот как может выглядеть ваш код:

      class BottomNavBar extends StatefulWidget {
  static const String id = 'bottom_navbar_screen';
  @override
  _BottomNavBarState createState() => _BottomNavBarState();
}

class _BottomNavBarState extends State<BottomNavBar> {
  int _selectedIndex = 0;
  late final List<GlobalKey<ScaffoldState>> scaffoldKeys;
  /// list of screen that will render inside the BNB
  late final List<Navigation> _items;
  @override
  initState() {
  super.initState()
  scaffoldKeys = [GlobalKey<ScaffoldState>(), GlobalKey<ScaffoldState>(), GlobalKey<ScaffoldState>(), GlobalKey<ScaffoldState>()];
  _items = [
    Navigation(
        widget: Screen1(scaffoldKey: scaffoldKeys[0]), navigationKey: GlobalKey<NavigatorState>()),
    Navigation(
        widget: Screen2(scaffoldKey: scaffoldKeys[1]), navigationKey: GlobalKey<NavigatorState>()),
    Navigation(
        widget: Screen3(scaffoldKey: scaffoldKeys[2]), navigationKey: GlobalKey<NavigatorState>()),
    Navigation(
        widget: Screen4(scaffoldKey: scaffoldKeys[3]), navigationKey: GlobalKey<NavigatorState>()),
  ];
  }

  /// function that renders components based on selected one in the BNB

  void _onItemTapped(int index) {
    for (scaffoldKey in scaffoldKeys) {
      // If the drawer is open
      if (scaffoldKey.currentState.isDrawerOpen) {
        // Closes the drawer
        scaffoldKey.currentState?.openEndDrawer();
      }
    }
    if (index == _selectedIndex) {
      _items[index]
          .navigationKey
          .currentState
          .popUntil((route) => route.isFirst);
    } else {
      setState(() {
        _selectedIndex = index;
      });
    }
 /// when the index is selected, on the button press do some actions
    switch (_selectedIndex) {
      case 0:
         //  Do some actions
        break;
      case 1:
         //  Do some actions
        break;
      case 2:
         //  Do some actions
        break;
      case 3:
         //  Do some actions
        break;
    }
  }

  /// navigation Tab widget for a list of all the screens and puts them in a Indexed Stack
  Widget _navigationTab(
      {GlobalKey<NavigatorState> navigationKey, Widget widget, GlobalKey<ScaffoldState> scaffoldKey}) {
    return Navigator(
      key: navigationKey,
      onGenerateRoute: (routeSettings) {
        return MaterialPageRoute(builder: (context) => widget);
      },
    );
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      onWillPop: () async {
        final isFirstRouteInCurrentTab =
            !await _items[_selectedIndex].navigationKey.currentState.maybePop();
        if (isFirstRouteInCurrentTab) {
          if (_selectedIndex != 0) {
            _onItemTapped(1);
            return false;
          }
        }

        /// let system handle back button if we're on the first route
        return isFirstRouteInCurrentTab;
      },
      child: Scaffold(
        body: IndexedStack(
          index: _selectedIndex,
          children: _items
              .map((e) => _navigationTab(
                  navigationKey: e.navigationKey, widget: e.widget))
              .toList(),
        ),
        bottomNavigationBar: BottomNavigationBar(
          items: <BottomNavigationBarItem>[
            BottomNavigationBarItem(
              label: 'Screen 1,
            ),
            BottomNavigationBarItem(
              label: 'Screen 2,
            ),
            BottomNavigationBarItem(
              label: 'Screen 3,
            ),
            BottomNavigationBarItem(
              label: 'Screen 4,
            ),
          ],
          currentIndex: _selectedIndex,
          showUnselectedLabels: true,
          onTap: _onItemTapped,
        ),
      ),
    );
  }
}

А вы экранируете:

      class Screen1 extends StatelessWidget {
  final GlobalKey<ScaffoldState> scaffoldKey;
  Screen1({required this.scaffoldKey});
  @override
  Widget build(BuildContext context) {
    return  Scaffold(
        key: scaffoldKey,
        backgroundColor: Colors.white,
        drawer: Drawer(), // so this is what I want to close on BNB button press in each of the 4 screens
        appBar: AppBar( // each screen has its own app bar
          title: Text('Screens 1-4'),
        ),
        body: Text('Body of Screens 1-4'),
    );
    }
  }

Я изменил список экранов _items на поздние переменные, чтобы вы могли передавать им scaffoldKeys при их объявлении.

Другие вопросы по тегам