[Dart, Flutter, Разработка под Android, Разработка под iOS] Детальный разбор навигации в Flutter
Автор
Сообщение
news_bot ®
Стаж: 6 лет 9 месяцев
Сообщений: 27286
Навигация во Flutter
Flutter набирает популярность среди разработчиков. Большенство подходов в построении приложений уже устоялись и применяются ежедневно в разработке E-commerce приложений. Тема навигации опускают на второй или третий план. Какой API навигации предоставляет Фреймворк? Какие подходы выработаны? Как использовать эти подходы и на что они годятся?
Введение
Начнём с того, что такое навигация? Навигация — это метод который позволяет перемещаться между пользовательским интерфейсом с заданными параметрами.
К примеру в IOS мире организовывает навигацию UIViewController, а в Android — Navigation component. А что предоставляет Flutter?
Navigator
Экраны в Flutter называются route. Для перемещениями между route существует класс Navigator который имеющий обширный API для реализации различных видов навигации.
Навигации на новый route и возвращение с него
Начнём с простого. Навигация на новый экран(route) вызывается методом push() который принимает в себя один аргумент — это Route.
Navigator.push(MaterialPageRoute(builder: (BuildContext context) => MyPage()));
Давайте детальнее разберёмся в вызове метода push:
Navigator — Виджет, который управляет навигацией.
Navigator.push() — метод который добавляет новый route в иерархию виджетов.
MaterialPageRoute() — Модальный route, который заменяет весь экран адаптивным к платформе переходом анимации.
builder — обязательный аргумент конструктора MaterialPageRoute, который возвращает пользовательский интерфейс Фреймворк для отрисовки.
[MyPage](https://miro.medium.com/max/664/1Xm96KtLeIAAMtAYWcr1-MA.png)* — пользовательский интерфейс реализованный при помощи Stateful/Stateless Widget
Возвращение на предыдущий route
Для возвращения с экрана на предыдущий необходимо использовать метод pop().
Navigator.pop();
Переда данных между экранами
Данные на новый экран передаются через конструктор при создании экрана в builder методе. Этот принцип работает в методах Navigator, где нужно реализовать метод build.
Navigator.push(context, MaterialPageRoute(builder: (context) => MyPage(someData: data)));
В примере продемонстрирована передача данных в класс MyPage (в этом классе хранится пользовательский интерфейс).
Для того чтобы передать данные на предыдущий экран нужно вызвать метод pop() и передать опциональным аргументом туда данные.
Navigator.pop(data);
Navigator State
Состояние виджета Navigator, который вызван внутри одного из видов MaterialApp/CupertinoApp/WidgetsApp. State отвечает за хранение истории навигации и предоставляет API для управления историей.
Базовые методы навигации повторяют структуру данных Stack. В диаграмме можно наблюдать методы и "call flow" NavigatorState.
Императивный vs Декларативный подход в навигации
Во всех примерах которые были приведены выше использовался императивный подход в навигации. Разница между императивным и декларативным подходом в том как будет выглядеть вызов нового route, и кто будет хранить реализацию перехода.
Давайте на простом примере:
Императивный подход , отвечает на вопрос — как?
Пример: Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там.
Декларативный подход, отвечает на вопрос — что?
Пример: Столик для двоих, пожалуйста.
Для более глубокого понимания разницы советую прочитать эту статью Imperative vs Declarative Programming
Императивная навигация
Вернёмся к реализации навигации. В императивном подходе описывается детали работы в вызывающем коде. В нашем случае это поля Route. В Flutter много типов route, например MaterialPageRoute и CupertinoPageRoute. Например в CupertinoPageRoute задаётся title, или settings.
Пример:
Navigator.push(
context,
CupertinoPageRoute<T>(
title: "Setting",
builder: (BuildContext context) => MyPage(),
settings: RouteSettings(name:"my_page"),
),
);
Этот код и знания о новом route будут храниться в ViewModel/Controller/BLoC/… У этот подхода существует недостаток.
Представим что потребовалось внести изменения в конструкторе в MyPage или в CupertinoPageRoute. Нужно искать каждый вызов метода push в проекте и изменять кодовую базу.
Вывод:
Этот подход не имеет единообразный подход к навигации, и знание о реализации route проникает в бизнес логику.
Декларативная навигация
Принцип декларативной навигации заключается в использовании декларативного подхода в программировании. Давайте разберём на примере.
Пример:
Navigator.pushNamed(context, '/my_page');
Принцип императивной навигации выглядит куда проще. Говорите "Отправь пользователя на экран настроек" передавая путь одним из аргументов навигации.
Для хранении реализации роста в самом Фреймворке предусмотрен механизм у MaterialApp/CupertinoApp/WidgetsApp. Это 2 колбэка onGenerateRoute и onUnknownRoute отвеспющие за хранение деталей реализации.
Пример:
MaterialApp(
onUnknownRoute: (settings) => CupertinoPageRoute(
builder: (context) {
return UndefinedView(name: settings.name);
}
),
onGenerateRoute: (settings) {
if (settings.name == '\my_page') {
return CupertinoPageRoute(
title: "MyPage",
settings: settings,
builder: (context) => MyPage(),
);
}
// Тут будут описание других роутов
},
);
Разберёмся подробнее в реализации:
Метод onGenerateRoute — данный метод срабатывает когда был вызван Navigator.pushNamed(). Метод должен вернуть route.
Метод onUnknownRoute — срабатывает когда метод onGenerateRoute вернул null. должен вернуть дефолтный route, по аналогии с web сайтами — 404 page.
Этот принцип упрощает вызывающий код и хранить логику переходов в одном едином месте с возможностью переиспользования кодовой базы внутри. Но так же существует недостаток, это универсальность для разных типов раутов.
Диалоговые и модальные окна
Для того чтобы вызвать диалоговое окна разного типа в Фреймворке предусмотрены глобальные методы. Давайте разберёмся в типах диалоговых окон.
Методы для вызова диалоговых и модальных окон:
- showAboutDialog
- showBottomSheet
- showDatePicker
- showGeneralDialog
- showMenu
- showModalBottomSheet
- showSearch
- showTimePicker
- showCupertinoDialog
- showDialog
- showLicensePage
- showCupertinoModalPopup
Эти методы покрывают базовые потребности разработчиков которые хотят работать с окнами. Если не хватает этого API, тогда стоит разобраться как эти методы работают.
Как работает это под капотом?
Давайте рассмотрим исходный код одного из методов, например showGeneralDialog.
Исходный код:
Future<T> showGeneralDialog<T>({
@required BuildContext context,
@required RoutePageBuilder pageBuilder,
bool barrierDismissible,
String barrierLabel,
Color barrierColor,
Duration transitionDuration,
RouteTransitionsBuilder transitionBuilder,
bool useRootNavigator = true,
RouteSettings routeSettings,
}) {
assert(pageBuilder != null);
assert(useRootNavigator != null);
assert(!barrierDismissible || barrierLabel != null);
return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>(
pageBuilder: pageBuilder,
barrierDismissible: barrierDismissible,
barrierLabel: barrierLabel,
barrierColor: barrierColor,
transitionDuration: transitionDuration,
transitionBuilder: transitionBuilder,
settings: routeSettings,
));
}
Давайте детальнее разберёмся в устройстве этого метода. showGeneralDialog вызывает метод push у NavigatorState с _DialogRoute(). Нижнее подчёркивание обозначает что этот класс приватный и используется только в пределах области видимости в которой сам описан, то есть в пределах этого файла.
Диалоговые и модальные окна которые отображаются при помощи глобальных методов — это кастомные route которые реализованы разработчиками Фреймворка.
Типы route в фреймворке
Теперь понятно что "every thins is a route", то есть что связанное с навигацией. Давайте взглянем на то, какие route уже реализованы в Фреймворке.
Два основных route в Flutter — это PageRoute и PopupRoute.
PageRoute — Модальный route, который заменяет весь экран.
PopupRoute — Модальный route, который накладывает виджет поверх текущего route.
Реализации PageRoute:
- MaterialPageRoute
- CupertinoPageRoute
- _SearchPageRoute
- PageRouteBuilder
Реализации PopupRoute:
- _ContexMenuRoute
- _DialogRoute
- _ModalBottomSheetRoute
- _CupertinoModalPopupRoute
- _PopupMenuRoute
Реализация PopupRoute приватна и скрыты для внешних потребителей, но роуты используют глобальные методы для показа диалоговых и модальных окон.
Вывод:
Универсальный метод для декларативной навигации из капота реализовать невозможно, так как нужно учесть навигацию не только между экранами.
Best practices
В этой части статьи можно задаться вопросом, "а насколько мне всё это нужно?". Ответить на этот вопрос можно легко, если у есть желание сделать масштабируемый API навигации которая в любой момент будет модифицирована под нужды проекта, то это один из вариантов. Если приложение будет в будущем расширяться со сложной логикой.
Начнём с того что мы сделаем некий сервис который будет будет соблюдать следующим аспектам:
- Декларативный вызов навигации.
- Отказ от использования BuildContext для навигации (Это критично если сервис навигации будет вызываться в компонентах, в которых нет возможности получить BuildContext).
- Модульность. Можно вызвать любой route, CupertinoPageRoute, BottomSheetRoute, DialogRoute и т.д.
Для нашего сервиса навигации нам понадобится интерфейс:
abstract class IRouter {
Future<T> routeTo<T extends Object>(RouteBundle bundle);
Future<bool> back<T extends Object>({T data, bool rootNavigator});
GlobalKey<NavigatorState> rootNavigatorKey;
}
Разберём методы:
routeTo - выполняет навигацию на новый экран.
back — возвращает на предыдущий экран.
rootNavigatorKey — GlobalKey умеющий вызывать методы NavigatorState.
После того как мы сделали интерфейс навигации, давайте сделаем реализацию этого интерфейса.
class Router implements IRouter {
@override
GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>();
@override
Future<T> routeTo<T>(RouteBundle bundle) async {
// Push logic here
}
@override
Future<bool> back<T>({T data, bool rootNavigator = false}) async {
// Back logic here
}
}
Супер, теперь нам нужно реализовать метод routeTo().
@override
Future<T> routeTo<T>(RouteBundle bundle) async {
assert(bundle != null, "The bundle [RouteBundle.bundle] is null");
NavigatorState rootState = rootNavigatorKey.currentState;
assert(rootState != null, 'rootState [NavigatorState] is null');
switch (bundle.route) {
case "/routeExample":
return await rootState.push(
_buildRoute<T>(
bundle: bundle,
child: RouteExample(),
),
);
case "/my_page":
return await rootState.push(
_buildRoute<T>(
bundle: bundle,
child: MyPage(),
),
);
default:
throw Exception('Route is not found');
}
}
Данный метод вызывает у root NavigatorState (который описан в WidgetsApp) метод push и конфигурирует его относительно RouteBundle который приходит одним из аргументов в данный метод.
Теперь нужно реализовать класс RouteBundle. Это просто модель, которая хранит в себе набор полей для конфигурации.
enum ContainerType {
/// The parent type is [Scaffold].
///
/// In IOS route with an iOS transition [CupertinoPageRoute].
/// In Android route with an Android transition [MaterialPageRoute].
///
scaffold,
/// Used for show child in dialog.
///
/// Route with [DialogRoute].
dialog,
/// Used for show child in [BottomSheet].
///
/// Route with [ModalBottomSheetRoute].
bottomSheet,
/// Used for show child only.
/// [AppBar] and other features is not implemented.
window,
}
class RouteBundle {
/// Creates a bundle that can be used for [Router].
RouteBundle({
this.route,
this.containerType,
});
/// The route for current navigation.
///
/// See [Routes] for details.
final String route;
/// The current status of this animation.
final ContainerType containerType;
}
enum ContainerType — тип контейнера, котрый будет задаваться декларативно из вызываемого кода.
RouteBundle — класс-холдер данных отвечающих конфигурацию нового route.
Как вы могли заметить у я использовал метод _buildRoute. Именно он отвечает за то, кой тип route будет вызван.
Route<T> _buildRoute<T>({@required RouteBundle bundle, @required Widget child}) {
assert(bundle.containerType != null, "The bundle.containerType [RouteBundle.containerType] is null");
switch (bundle.containerType) {
case ContainerType.scaffold:
return CupertinoPageRoute<T>(
title: bundle.title,
builder: (BuildContext context) => child,
settings: RouteSettings(name: bundle.route),
);
case ContainerType.dialog:
return DialogRoute<T>(
title: '123',
settings: RouteSettings(name: bundle.route),
pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) {
return child;
},
);
case ContainerType.bottomSheet:
return ModalBottomSheetRoute<T>(
settings: RouteSettings(name: bundle.route),
isScrollControlled: true,
builder: (BuildContext context) => child,
);
case ContainerType.window:
return CupertinoPageRoute<T>(
settings: RouteSettings(name: bundle.route),
builder: (BuildContext context) => child,
);
default:
throw Exception('ContainerType is not found');
}
}
Думаю что в этой функции стоит рассказать о ModalBottomSheetRoute и DialogRoute, которые использую. Исходный код этих route позаимствован из раздела Material исходного кода Flutter.
Осталось сделать метод back.
@override
Future<bool> back<T>({T data, bool rootNavigator = false}) async {
NavigatorState rootState = rootNavigatorKey.currentState;
return await (rootState).maybePop<T>(data);
}
Ну и конечно перед использованием сервиса необходимо передать rootNavigatorKey в App следующим образом:
MaterialApp(
navigatorKey: widget.router.rootNavigatorKey,
home: Home()
);
Кодовая база для нашего сервиса готова, давайте вызовем наш route. Для этого создадим инстанс нашего сервиса и каким-либо образом "прокинуть" в объект, который будет вызывать этот инстанс, например при помощи Dependency Injection.
router.routeTo(RouteBundle(route: '/my_page', containerType: ContainerType.window));
Теперь мы имеем единый подход к навигации, позволяющий решить вышеперечисленные проблемы:
- Декларативный вызов навигации
- Отказ от BuildContext по средствам GlobalKey
- Модульность достигнута возможностью конфигурирования route относительно имени пути и контейнера для View
Итог
В Фреймворке Flutter существуют различные методы для навигации, которые дают преимущества и недостатки.
Ну и конечно полезные ссылки:
Мой телеграм канал
Мои друзья Flutter Dev Podcast
Вакансии Flutter разработчиков
===========
Источник:
habr.com
===========
Похожие новости:
- [Dart, Flutter, Программирование, Разработка мобильных приложений] Flutter под капотом: Binding
- [Разработка под Arduino] Хирургическая операция по увеличению буфера последовательного порта у Arduino IDE (перевод)
- [C, Java, Python, Исследования и прогнозы в IT, Программирование] IEEE опубликовал новый рейтинг языков программирования
- [Контент-маркетинг] Почему мы отказались от копирайтеров на бирже
- [Java, Разработка мобильных приложений, Разработка под Android] Как подружить RxJava с VIPER в Android, подходы применения и о структуре планировщиков
- [Информационная безопасность, Разработка веб-сайтов] CRLF-инъекции и HTTP Response Splitting (перевод)
- [DevOps, Программирование] DevOps-инструменты, которые должен изучить каждый в 2020 году (перевод)
- [IT-компании, Информационная безопасность, Разработка под iOS, Смартфоны] Apple предлагает исследователям безопасности из некоторых стран специальный iPhone для работы
- [Open source, Openshift, Виртуализация, Разработка под Linux] Современные приложения на OpenShift, часть 1: веб-приложения всего за две команды
- [CSS, HTML, Разработка веб-сайтов] Современные решения старых CSS-задач (3 часть): Масштабирование изображений на CSS (перевод)
Теги для поиска: #_dart, #_flutter, #_razrabotka_pod_android (Разработка под Android), #_razrabotka_pod_ios (Разработка под iOS), #_flutter, #_dart, #_dart_2.0, #_android, #_ios, #_android_dev, #_android_development, #_ios_development, #_ios_razrabotka (iOS разработка), #_ios_dev, #_react, #_reactnative, #_crossplatform, #_crossplatform, #_navigation, #_ui, #_multiplatform, #_mobile, #_mobile_development, #_web, #_web_development, #_programming, #_dart, #_flutter, #_razrabotka_pod_android (
Разработка под Android
), #_razrabotka_pod_ios (
Разработка под iOS
)
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 03:33
Часовой пояс: UTC + 5
Автор | Сообщение |
---|---|
news_bot ®
Стаж: 6 лет 9 месяцев |
|
Навигация во Flutter Flutter набирает популярность среди разработчиков. Большенство подходов в построении приложений уже устоялись и применяются ежедневно в разработке E-commerce приложений. Тема навигации опускают на второй или третий план. Какой API навигации предоставляет Фреймворк? Какие подходы выработаны? Как использовать эти подходы и на что они годятся? Введение Начнём с того, что такое навигация? Навигация — это метод который позволяет перемещаться между пользовательским интерфейсом с заданными параметрами. К примеру в IOS мире организовывает навигацию UIViewController, а в Android — Navigation component. А что предоставляет Flutter? Navigator Экраны в Flutter называются route. Для перемещениями между route существует класс Navigator который имеющий обширный API для реализации различных видов навигации. Навигации на новый route и возвращение с него Начнём с простого. Навигация на новый экран(route) вызывается методом push() который принимает в себя один аргумент — это Route. Navigator.push(MaterialPageRoute(builder: (BuildContext context) => MyPage()));
Давайте детальнее разберёмся в вызове метода push: Navigator — Виджет, который управляет навигацией. Navigator.push() — метод который добавляет новый route в иерархию виджетов. MaterialPageRoute() — Модальный route, который заменяет весь экран адаптивным к платформе переходом анимации. builder — обязательный аргумент конструктора MaterialPageRoute, который возвращает пользовательский интерфейс Фреймворк для отрисовки. [MyPage](https://miro.medium.com/max/664/1Xm96KtLeIAAMtAYWcr1-MA.png)* — пользовательский интерфейс реализованный при помощи Stateful/Stateless Widget Возвращение на предыдущий route Для возвращения с экрана на предыдущий необходимо использовать метод pop(). Navigator.pop();
Переда данных между экранами Данные на новый экран передаются через конструктор при создании экрана в builder методе. Этот принцип работает в методах Navigator, где нужно реализовать метод build. Navigator.push(context, MaterialPageRoute(builder: (context) => MyPage(someData: data)));
В примере продемонстрирована передача данных в класс MyPage (в этом классе хранится пользовательский интерфейс). Для того чтобы передать данные на предыдущий экран нужно вызвать метод pop() и передать опциональным аргументом туда данные. Navigator.pop(data);
Navigator State Состояние виджета Navigator, который вызван внутри одного из видов MaterialApp/CupertinoApp/WidgetsApp. State отвечает за хранение истории навигации и предоставляет API для управления историей. Базовые методы навигации повторяют структуру данных Stack. В диаграмме можно наблюдать методы и "call flow" NavigatorState. Императивный vs Декларативный подход в навигации Во всех примерах которые были приведены выше использовался императивный подход в навигации. Разница между императивным и декларативным подходом в том как будет выглядеть вызов нового route, и кто будет хранить реализацию перехода. Давайте на простом примере: Императивный подход , отвечает на вопрос — как? Пример: Я вижу, что тот угловой столик свободен. Мы пойдём туда и сядем там. Декларативный подход, отвечает на вопрос — что? Пример: Столик для двоих, пожалуйста. Для более глубокого понимания разницы советую прочитать эту статью Imperative vs Declarative Programming Императивная навигация Вернёмся к реализации навигации. В императивном подходе описывается детали работы в вызывающем коде. В нашем случае это поля Route. В Flutter много типов route, например MaterialPageRoute и CupertinoPageRoute. Например в CupertinoPageRoute задаётся title, или settings. Пример: Navigator.push(
context, CupertinoPageRoute<T>( title: "Setting", builder: (BuildContext context) => MyPage(), settings: RouteSettings(name:"my_page"), ), ); Этот код и знания о новом route будут храниться в ViewModel/Controller/BLoC/… У этот подхода существует недостаток. Представим что потребовалось внести изменения в конструкторе в MyPage или в CupertinoPageRoute. Нужно искать каждый вызов метода push в проекте и изменять кодовую базу. Вывод: Этот подход не имеет единообразный подход к навигации, и знание о реализации route проникает в бизнес логику.
Декларативная навигация Принцип декларативной навигации заключается в использовании декларативного подхода в программировании. Давайте разберём на примере. Пример: Navigator.pushNamed(context, '/my_page');
Принцип императивной навигации выглядит куда проще. Говорите "Отправь пользователя на экран настроек" передавая путь одним из аргументов навигации. Для хранении реализации роста в самом Фреймворке предусмотрен механизм у MaterialApp/CupertinoApp/WidgetsApp. Это 2 колбэка onGenerateRoute и onUnknownRoute отвеспющие за хранение деталей реализации. Пример: MaterialApp(
onUnknownRoute: (settings) => CupertinoPageRoute( builder: (context) { return UndefinedView(name: settings.name); } ), onGenerateRoute: (settings) { if (settings.name == '\my_page') { return CupertinoPageRoute( title: "MyPage", settings: settings, builder: (context) => MyPage(), ); } // Тут будут описание других роутов }, ); Разберёмся подробнее в реализации: Метод onGenerateRoute — данный метод срабатывает когда был вызван Navigator.pushNamed(). Метод должен вернуть route. Метод onUnknownRoute — срабатывает когда метод onGenerateRoute вернул null. должен вернуть дефолтный route, по аналогии с web сайтами — 404 page. Этот принцип упрощает вызывающий код и хранить логику переходов в одном едином месте с возможностью переиспользования кодовой базы внутри. Но так же существует недостаток, это универсальность для разных типов раутов.
Диалоговые и модальные окна Для того чтобы вызвать диалоговое окна разного типа в Фреймворке предусмотрены глобальные методы. Давайте разберёмся в типах диалоговых окон. Методы для вызова диалоговых и модальных окон:
Эти методы покрывают базовые потребности разработчиков которые хотят работать с окнами. Если не хватает этого API, тогда стоит разобраться как эти методы работают. Как работает это под капотом? Давайте рассмотрим исходный код одного из методов, например showGeneralDialog. Исходный код: Future<T> showGeneralDialog<T>({
@required BuildContext context, @required RoutePageBuilder pageBuilder, bool barrierDismissible, String barrierLabel, Color barrierColor, Duration transitionDuration, RouteTransitionsBuilder transitionBuilder, bool useRootNavigator = true, RouteSettings routeSettings, }) { assert(pageBuilder != null); assert(useRootNavigator != null); assert(!barrierDismissible || barrierLabel != null); return Navigator.of(context, rootNavigator: useRootNavigator).push<T>(_DialogRoute<T>( pageBuilder: pageBuilder, barrierDismissible: barrierDismissible, barrierLabel: barrierLabel, barrierColor: barrierColor, transitionDuration: transitionDuration, transitionBuilder: transitionBuilder, settings: routeSettings, )); } Давайте детальнее разберёмся в устройстве этого метода. showGeneralDialog вызывает метод push у NavigatorState с _DialogRoute(). Нижнее подчёркивание обозначает что этот класс приватный и используется только в пределах области видимости в которой сам описан, то есть в пределах этого файла. Диалоговые и модальные окна которые отображаются при помощи глобальных методов — это кастомные route которые реализованы разработчиками Фреймворка.
Типы route в фреймворке Теперь понятно что "every thins is a route", то есть что связанное с навигацией. Давайте взглянем на то, какие route уже реализованы в Фреймворке. Два основных route в Flutter — это PageRoute и PopupRoute. PageRoute — Модальный route, который заменяет весь экран. PopupRoute — Модальный route, который накладывает виджет поверх текущего route. Реализации PageRoute:
Реализации PopupRoute:
Реализация PopupRoute приватна и скрыты для внешних потребителей, но роуты используют глобальные методы для показа диалоговых и модальных окон. Вывод: Универсальный метод для декларативной навигации из капота реализовать невозможно, так как нужно учесть навигацию не только между экранами.
Best practices В этой части статьи можно задаться вопросом, "а насколько мне всё это нужно?". Ответить на этот вопрос можно легко, если у есть желание сделать масштабируемый API навигации которая в любой момент будет модифицирована под нужды проекта, то это один из вариантов. Если приложение будет в будущем расширяться со сложной логикой. Начнём с того что мы сделаем некий сервис который будет будет соблюдать следующим аспектам:
Для нашего сервиса навигации нам понадобится интерфейс: abstract class IRouter {
Future<T> routeTo<T extends Object>(RouteBundle bundle); Future<bool> back<T extends Object>({T data, bool rootNavigator}); GlobalKey<NavigatorState> rootNavigatorKey; } Разберём методы: routeTo - выполняет навигацию на новый экран. back — возвращает на предыдущий экран. rootNavigatorKey — GlobalKey умеющий вызывать методы NavigatorState. После того как мы сделали интерфейс навигации, давайте сделаем реализацию этого интерфейса. class Router implements IRouter {
@override GlobalKey<NavigatorState> rootNavigatorKey = GlobalKey<NavigatorState>(); @override Future<T> routeTo<T>(RouteBundle bundle) async { // Push logic here } @override Future<bool> back<T>({T data, bool rootNavigator = false}) async { // Back logic here } } Супер, теперь нам нужно реализовать метод routeTo(). @override
Future<T> routeTo<T>(RouteBundle bundle) async { assert(bundle != null, "The bundle [RouteBundle.bundle] is null"); NavigatorState rootState = rootNavigatorKey.currentState; assert(rootState != null, 'rootState [NavigatorState] is null'); switch (bundle.route) { case "/routeExample": return await rootState.push( _buildRoute<T>( bundle: bundle, child: RouteExample(), ), ); case "/my_page": return await rootState.push( _buildRoute<T>( bundle: bundle, child: MyPage(), ), ); default: throw Exception('Route is not found'); } } Данный метод вызывает у root NavigatorState (который описан в WidgetsApp) метод push и конфигурирует его относительно RouteBundle который приходит одним из аргументов в данный метод. Теперь нужно реализовать класс RouteBundle. Это просто модель, которая хранит в себе набор полей для конфигурации. enum ContainerType {
/// The parent type is [Scaffold]. /// /// In IOS route with an iOS transition [CupertinoPageRoute]. /// In Android route with an Android transition [MaterialPageRoute]. /// scaffold, /// Used for show child in dialog. /// /// Route with [DialogRoute]. dialog, /// Used for show child in [BottomSheet]. /// /// Route with [ModalBottomSheetRoute]. bottomSheet, /// Used for show child only. /// [AppBar] and other features is not implemented. window, } class RouteBundle { /// Creates a bundle that can be used for [Router]. RouteBundle({ this.route, this.containerType, }); /// The route for current navigation. /// /// See [Routes] for details. final String route; /// The current status of this animation. final ContainerType containerType; } enum ContainerType — тип контейнера, котрый будет задаваться декларативно из вызываемого кода. RouteBundle — класс-холдер данных отвечающих конфигурацию нового route. Как вы могли заметить у я использовал метод _buildRoute. Именно он отвечает за то, кой тип route будет вызван. Route<T> _buildRoute<T>({@required RouteBundle bundle, @required Widget child}) {
assert(bundle.containerType != null, "The bundle.containerType [RouteBundle.containerType] is null"); switch (bundle.containerType) { case ContainerType.scaffold: return CupertinoPageRoute<T>( title: bundle.title, builder: (BuildContext context) => child, settings: RouteSettings(name: bundle.route), ); case ContainerType.dialog: return DialogRoute<T>( title: '123', settings: RouteSettings(name: bundle.route), pageBuilder: (BuildContext context, Animation<double> animation, Animation<double> secondaryAnimation) { return child; }, ); case ContainerType.bottomSheet: return ModalBottomSheetRoute<T>( settings: RouteSettings(name: bundle.route), isScrollControlled: true, builder: (BuildContext context) => child, ); case ContainerType.window: return CupertinoPageRoute<T>( settings: RouteSettings(name: bundle.route), builder: (BuildContext context) => child, ); default: throw Exception('ContainerType is not found'); } } Думаю что в этой функции стоит рассказать о ModalBottomSheetRoute и DialogRoute, которые использую. Исходный код этих route позаимствован из раздела Material исходного кода Flutter. Осталось сделать метод back. @override
Future<bool> back<T>({T data, bool rootNavigator = false}) async { NavigatorState rootState = rootNavigatorKey.currentState; return await (rootState).maybePop<T>(data); } Ну и конечно перед использованием сервиса необходимо передать rootNavigatorKey в App следующим образом: MaterialApp(
navigatorKey: widget.router.rootNavigatorKey, home: Home() ); Кодовая база для нашего сервиса готова, давайте вызовем наш route. Для этого создадим инстанс нашего сервиса и каким-либо образом "прокинуть" в объект, который будет вызывать этот инстанс, например при помощи Dependency Injection. router.routeTo(RouteBundle(route: '/my_page', containerType: ContainerType.window));
Теперь мы имеем единый подход к навигации, позволяющий решить вышеперечисленные проблемы:
Итог В Фреймворке Flutter существуют различные методы для навигации, которые дают преимущества и недостатки. Ну и конечно полезные ссылки: Мой телеграм канал Мои друзья Flutter Dev Podcast Вакансии Flutter разработчиков =========== Источник: habr.com =========== Похожие новости:
Разработка под Android ), #_razrabotka_pod_ios ( Разработка под iOS ) |
|
Вы не можете начинать темы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Вы не можете отвечать на сообщения
Вы не можете редактировать свои сообщения
Вы не можете удалять свои сообщения
Вы не можете голосовать в опросах
Вы не можете прикреплять файлы к сообщениям
Вы не можете скачивать файлы
Текущее время: 23-Ноя 03:33
Часовой пояс: UTC + 5