Как сделать вложенную навигацию в Flutter

#flutter #flutter-navigation

#flutter #flutter-навигация

Вопрос:

Есть ли у кого-нибудь какие-либо рекомендации по определению вложенной навигации в Flutter?

Я хочу сохранить постоянную нижнюю панель навигации даже при перенаправлении на новые экраны. Похоже на YouTube, где нижняя панель всегда присутствует, даже когда вы просматриваете меню глубже.

Я не могу понять это из документов.

Единственный учебник, который я смог найти до сих пор, который подробно описывает именно мое требование: https://medium.com/coding-with-flutter/flutter-case-study-multiple-navigators-with-bottomnavigationbar-90eb6caa6dbf ( исходный код). Однако это очень запутанно.

Прямо сейчас я использую

 Navigator.push(context,
                MaterialPageRoute(builder: (BuildContext context) {
              return Container()
 

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

Любые советы будут с благодарностью!

Ответ №1:

Вот простой пример, который даже поддерживает переход на первый экран с панелью вкладок.

 import 'package:flutter/material.dart';

import '../library/screen.dart';
import '../playlists/screen.dart';
import '../search/screen.dart';
import '../settings/screen.dart';

class TabsScreen extends StatefulWidget {
  @override
  _TabsScreenState createState() => _TabsScreenState();
}

class _TabsScreenState extends State<TabsScreen> {
  int _currentIndex = 0;

  final _libraryScreen = GlobalKey<NavigatorState>();
  final _playlistScreen = GlobalKey<NavigatorState>();
  final _searchScreen = GlobalKey<NavigatorState>();
  final _settingsScreen = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: IndexedStack(
        index: _currentIndex,
        children: <Widget>[
          Navigator(
            key: _libraryScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => LibraryScreen(),
            ),
          ),
          Navigator(
            key: _playlistScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => PlaylistsScreen(),
            ),
          ),
          Navigator(
            key: _searchScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => SearchScreen(),
            ),
          ),
          Navigator(
            key: _settingsScreen,
            onGenerateRoute: (route) => MaterialPageRoute(
              settings: route,
              builder: (context) => SettingsScreen(),
            ),
          ),
        ],
      ),
      bottomNavigationBar: BottomNavigationBar(
        type: BottomNavigationBarType.fixed,
        currentIndex: _currentIndex,
        onTap: (val) => _onTap(val, context),
        backgroundColor: Theme.of(context).scaffoldBackgroundColor,
        items: [
          BottomNavigationBarItem(
            icon: Icon(Icons.library_books),
            title: Text('Library'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.list),
            title: Text('Playlists'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            title: Text('Search'),
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.settings),
            title: Text('Settings'),
          ),
        ],
      ),
    );
  }

  void _onTap(int val, BuildContext context) {
    if (_currentIndex == val) {
      switch (val) {
        case 0:
          _libraryScreen.currentState.popUntil((route) => route.isFirst);
          break;
        case 1:
          _playlistScreen.currentState.popUntil((route) => route.isFirst);
          break;
        case 2:
          _searchScreen.currentState.popUntil((route) => route.isFirst);
          break;
        case 3:
          _settingsScreen.currentState.popUntil((route) => route.isFirst);
          break;
        default:
      }
    } else {
      if (mounted) {
        setState(() {
          _currentIndex = val;
        });
      }
    }
  }
}

 

Комментарии:

1. Это сработало очень хорошо для меня — я обобщил этот подход и добавил несколько помощников, таких как tabNavigatorOf(context, { int tabIndex })

2. У меня возникла проблема с сохранением состояния вкладки, но индексированный стек работал отлично, спасибо

3. После этого, как мы можем нажать страницу без нижней панели с любой внутренней страницы?

4. У меня возникают проблемы с этим подходом — когда я переключаюсь с одной вкладки на другую, а затем нажимаю кнопку «Назад», приложение закрывается вместо того, чтобы переключаться обратно на первую вкладку. Кроме того, если я выполняю вложенную навигацию, например, переключаюсь на другую страницу с навигатором внутри, например, на страницу библиотеки, нажатие назад также закрывает приложение. Какие-нибудь советы?

5. если кнопка переключается обратно на первую страницу, попробуйте использовать WillPopScope на странице навигаторов со значением ключа.. @schneida

Ответ №2:

Вот пример кода для постоянной нижней панели навигации в качестве начального:

 import 'package:flutter/material.dart';

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: MainPage(),
    );
  }
}

class MainPage extends StatelessWidget {
  final navigatorKey = GlobalKey<NavigatorState>();

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Column(
        children: <Widget>[
          Expanded(
            child: Navigator(
              key: navigatorKey,
              onGenerateRoute: (route) => MaterialPageRoute(
                    settings: route,
                    builder: (context) => PageOne(),
                  ),
            ),
          ),
          BottomNavigationBar(navigatorKey)
        ],
      ),
    );
  }
}

class BottomNavigationBar extends StatelessWidget {
  final GlobalKey<NavigatorState> navigatorKey;

  BottomNavigationBar(this.navigatorKey) : assert(navigatorKey != null);

  Future<void> push(Route route) {
    return navigatorKey.currentState.push(route);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      color: Colors.blue,
      child: ButtonBar(
        alignment: MainAxisAlignment.spaceEvenly,
        children: <Widget>[
          RaisedButton(
            child: Text("PageOne"),
            onPressed: () {
              push(MaterialPageRoute(builder: (context) => PageOne()));
            },
          ),
          RaisedButton(
            child: Text("PageTwo"),
            onPressed: () {
              push(MaterialPageRoute(builder: (context) => PageTwo()));
            },
          )
        ],
      ),
    );
  }
}

class PageOne extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Page One"),
          RaisedButton(
            onPressed: (){
              Navigator.of(context).pop();
            },
            child: Text("Pop"),
          ),
        ],
      ),
    );
  }
}

class PageTwo extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Container(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          Text("Page Two"),
          RaisedButton(
            onPressed: (){
              Navigator.of(context).pop();
            },
            child: Text("Pop"),
          ),
        ],
      ),
    );
  }
}
 

Вот как это выглядит на экране

Запись экрана вложенного навигатора для постоянной нижней панели навигации

Комментарии:

1.Я попробовал что-то, основанное на вашем решении. Я создал Expanded Column в Scaffold теле. Я помещаю a BottomNavigationBar внутрь и использую a GlobalKey<NavigatorState>() для продвижения новых страниц. Однако анимация между BottomNavigationBar страницами крайне неудобна. Он не использует BottomNavigationBar анимацию. Это просто накладывает одну страницу поверх другой (и портит go back )

2. Я не понимаю, какую анимацию вы хотите? Скользящий переход влево-вправо?

3. @HarshBhikadia Я попробовал ваше решение и работает хорошо, но, когда на определенной странице, например, на странице 1, а на странице 1 есть виджет TextField (), при нажатии на этот виджет текстового поля автоматически открывается маршрут по умолчанию, который равен «/». По сути, он повторно открывает страницу, на которой возвращается этот маршрут по умолчанию. Есть способ решить эту проблему?

4. Кажется, что вложенный навигатор теряет свое состояние при горячей перезагрузке, и я начинаю с начального маршрута. У кого-нибудь еще такое поведение? Это довольно неприятно при разработке…

5. @HarshBhikadia После этого, если я хочу не показывать нижнюю панель на внутренней странице (например, на странице сведений), как мне этого добиться?