Route — это абстрактный класс во Flutter, представляющий отдельный экран или страницу в навигационной истории Navigator. Конкретные реализации Route — MaterialPageRoute, CupertinoPageRoute и PlatformRoute — определяют, как экран отображается и какая анимация используется при переходе. В отличие от обычного виджета, Route имеет собственный жизненный цикл с методами didPush, didPop, didReplace и didChangeNext. По данным Flutter API Reference (2026), каждый Route управляет ModalBarrier, учитывает платформенные особенности (свайп-назад на iOS) и обеспечивает изоляцию состояния между экранами.
Главное
Route — это основа навигационной системы Flutter. Каждый экран, который пользователь видит в приложении, представлен объектом Route, находящимся в стеке Navigator. Route абстрагирует экран от управляющего кода: Navigator добавляет и удаляет Route, а внутри Route находится интерфейс, который видит пользователь. Такая архитектура разделяет ответственность между навигацией и отображением.
В отличие от простой замены виджетов, Route предоставляет сервисы, недоступные обычным виджетам: ModalBarrier (затенение фона при открытом диалоге), управление анимацией входа и выхода, обработка аппаратной кнопки «Назад» на Android и интеграция с Hero-анимацией для плавных переходов между экранами.
По данным Flutter Cookbook (2026), Route является ключевым элементом для Hero-анимации: Hero-виджет на одном Route автоматически анимирует переход к Hero-виджету на следующем Route, создавая эффект «летящего» элемента. Это возможно именно потому, что Route сохраняет оба экрана в Overlay во время анимации.
Route является основой как для простых мобильных приложений (через Navigator 1.0), так и для сложных сценариев с глубокими ссылками (Navigator 2.0). В Navigator 2.0 Route представляет собой Page, который RouterDelegate конвертирует из конфигурации маршрута. Таким образом, понимание Route необходимо для работы с любой системой навигации Flutter — независимо от выбранного подхода или пакета.
Flutter предоставляет несколько встроенных реализаций Route, каждая из которых адаптирует поведение под конкретную платформу. Выбор правильного типа Route влияет на пользовательский опыт: Material-анимация на Android и Cupertino-анимация на iOS создают ощущение «родного» приложения.
| Тип Route | Анимация | Платформа | Особенности |
|---|---|---|---|
| MaterialPageRoute | Слайд снизу вверх | Android, десктоп | Тень при переходе, автоматическая обработка SafeArea |
| CupertinoPageRoute | Слайд справа налево | iOS, iPadOS | Свайп-назад жестом, прозрачный фон при переходе |
| PlatformRoute | Автоматический выбор | Все платформы | Выбирает тип на основе TargetPlatform |
| PageRouteBuilder | Кастомная | Все платформы | Полный контроль над анимацией через AnimationController |
MaterialPageRoute — наиболее часто используемая реализация Route. Она анимирует вход нового экрана снизу вверх с постепенным появлением. При выходе экран анимируется сверху вниз, возвращаясь в исходное положение. Панель инструментов (AppBar) и тело экрана анимируются раздельно, создавая эффект иерархии.
CupertinoPageRoute имитирует UINavigationController из iOS. Новый экран въезжает справа, закрывая предыдущий. Ключевая особенность — поддержка интерактивного свайп-назад жестом, которая реализована через CupertinoBackGestureDetector. Этот жест обрабатывается даже в середине анимации, обеспечивая естественное поведение, знакомое пользователям iPhone.
Route имеет собственный жизненный цикл, который отличается от жизненного цикла обычного StatefulWidget. Понимание этого цикла необходимо для правильной инициализации данных, подписки на стримы и освобождения ресурсов при закрытии экрана.
Жизненный цикл Route состоит из четырёх основных стадий. Transition — Route создаётся и анимируется при входе (вызывается didPush). Active — Route полностью отображается и взаимодействует с пользователем. Inactive — другой Route перекрывает текущий (диалог, нижний экран), но Route остаётся в стеке. Disposed — Route удаляется из стека и уничтожается, вызывается didPop и dispose.
Методы жизненного цикла Route могут быть переопределены в кастомной реализации. Например, didPop вызывается, когда Route удаляется из стека — здесь можно сохранить черновик данных. didChangeNext вызывается, когда следующий Route в стеке изменился — полезно для обновления UI при изменении истории навигации.
По данным Flutter API Route.didPop (2026), важно не путать жизненный цикл Route с жизненным циклом State внутри Route. StatefulWidget внутри Route имеет собственный initState и dispose, которые вызываются в рамках стадии Transition и Disposed соответственно. Route живёт дольше, чем его внутренний State — Route остаётся в Overlay даже когда его виджеты временно скрыты другим Route.
Route предоставляет механизмы для передачи данных как на вход (при создании), так и на выход (при завершении). Правильная передача данных через Route избавляет от глобальных переменных и InheritedWidget, делая навигацию типобезопасной и предсказуемой.
Для передачи данных на новый экран используется конструктор принимающего виджета или аргумент arguments в Navigator.pushNamed. Внутри Route данные доступны через RouteSettings.arguments, который хранится в объекте Route. Этот подход работает для всех типов Route — MaterialPageRoute, CupertinoPageRoute и кастомных реализаций.
Для возврата данных используется второй аргумент Navigator.pop(context, result). Navigator.push возвращает Future<Т>, которая завершается со значением, переданным в pop. Если pop вызван без аргумента, Future завершается с null. Этот механизм — аналог startActivityForResult в Android и completion handler в iOS, но реализованный через Dart Futures.
При прямом вызове Navigator.push с MaterialPageRoute данные передаются через конструктор целевого экрана. MethodChannel не используется — это чистое Dart-взаимодействие. Такой подход предпочтителен для типобезопасной передачи сложных объектов.
Рассмотрим пример создания кастомного Route с собственной анимацией и передачей данных. PageRouteBuilder позволяет определить анимацию входа и выхода с полным контролем над кривой анимации и длительностью.
// Custom Route with slide animation
Navigator.push(context, PageRouteBuilder(
pageBuilder: (context, animation, secondaryAnimation) {
return DetailPage(productId: '42');
},
transitionsBuilder: (context, animation, secondaryAnimation, child) {
const begin = Offset(0.0, 0.3);
const end = Offset.zero;
final tween = Tween(begin: begin, end: end);
final offsetAnimation = animation.drive(tween);
return SlideTransition(position: offsetAnimation, child: child);
},
transitionDuration: const Duration(milliseconds: 400),
));
// Return data from screen
ElevatedButton(
onPressed: () => Navigator.of(context).pop({'selected': true, 'id': '42'}),
child: const Text('Select'),
);
// Get result on calling screen
final result = await Navigator.push(context, MaterialPageRoute(
builder: (context) => const SelectionPage(),
));
if (result != null) {
print('Selected: ${result['selected']}');
}
В примере PageRouteBuilder определяет кастомную анимацию слайда снизу с прозрачностью. transitionDuration задаёт скорость анимации. Код также демонстрирует передачу результата: детальный экран возвращает Map с выбором пользователя, а вызывающий экран получает эти данные через Future от push. Route обеспечивает полную изоляцию: черновики на детальном экране не влияют на состояние списка.
Часто задаваемые вопросы
Route — это объект, управляющий экраном на уровне навигации: он хранит анимацию, ModalBarrier и жизненный цикл. Widget — это описание части интерфейса. Route содержит Widget внутри себя, но также предоставляет сервисы (Overlay-слой, Hero-анимация), недоступные обычным виджетам. Один Route может содержать сложную иерархию виджетов любой глубины.
Используйте PageRouteBuilder с параметрами pageBuilder (построение экрана) и transitionsBuilder (определение анимации). В transitionsBuilder доступны animation (0.0–1.0) и secondaryAnimation для параллельных анимаций. Для полного контроля создайте подкласс Route и переопределите buildPage, createAnimationController и buildTransitions, что даст доступ к низкоуровневым AnimationController.
Для передачи сложных объектов используйте конструктор целевого экрана при прямом Navigator.push или аргумент arguments при pushNamed. Убедитесь, что объект сериализуем (Map, JSON или кастомный класс). Для типобезопасной передачи во Flutter используются freezed или json_serializable модели, которые гарантируют корректную десериализацию при передаче через RouteSettings.
Route.dispose не вызывается, если Route остаётся в стеке Navigator. Например, при открытии нового Route старый Route переходит в неактивное состояние (Inactive), но не уничтожается — он остаётся в стеке для быстрого возврата. Dispose вызывается только при удалении Route из стека через pop, pushReplacement или pushAndRemoveUntil. Для освобождения ресурсов используйте dispose State внутри Route, а не dispose самого Route.
Используйте ModalRoute.of(context) для получения текущего Route из BuildContext. Свойство ModalRoute.isActive показывает, является ли Route текущим видимым экраном. ModalRoute.isCurrent — true, если Route является вершиной стека. Для наблюдения за изменениями стека подпишитесь на Navigator наблюдателей через RouteAware и RouteObserver, которые уведомляют о смене активного Route.
Итоги
Мы разработаем мобильное приложение под ключ
IT Sectr создаёт приложения для iOS и Android для стартапов и бизнеса с 2017 года. Мы проконсультируем вас и предложим наилучшее решение.
Читайте также