Эта статья была впервые опубликована в публичном аккаунте WeChat."Меанд Ни",нажмите, чтобы прочитать.
С выходом последней версии,Flutter 1.22Различные новые функции в .Не дает разработчикам гибкого способа прямого управления стеком маршрутизации., даже кажется устаревшим, совсем не Flutter.
As mentioned by a participant in one of Flutter's user studies, the API also feels outdated and not very Flutter-y.
А в Navigator 2.0 представлен новый набор декларативных API, которые, в отличие от предыдущих, могут реализовыватьПредставляет исторические страницы маршрутизации в приложении в виде декларативного и неизменного списка страниц., который преобразуется в Маршруты Навигатора в реальном коде, которыйОн совпадает с принципом разбора неизменяемых виджетов в элементы и рендеринга их на странице во Flutter., но Flutter заполнен. В этой статье мы начнем с Navigator 1.0 и постепенно разберемся с реализацией Navigator 2.0.
Содержание этой статьи переведено с:Изучение новой системы навигации и маршрутизации Flutter
Для основной части, пожалуйста, обратитесь к последней статье:Flutter Navigator2.0 Полное руководство и принципиальный анализ
Navigator 1.0
До появления Navigator 2.0 мы обычно использовали следующие два класса в нашем приложении для управления каждой страницей:
-
Navigator, которая управляет группой
Route
объект. -
Route,Один
Route
обычно может представлять страницу, котораяNavigator
Держите, два обычно используемых класса реализации:MaterialPageRoute
а такжеCupertinoPageRoute
.
Route
Может быть добавлен или извлечен через именованные маршруты и маршруты компонентов (анонимные маршруты).Navigator
стек маршрутизации. Давайте кратко рассмотрим предыдущее традиционное использование.
маршрутизация компонентов
При реализации традиционной маршрутизации каждая страница Flutter состоит изNavigator
Организация, наложенная друг на друга в «стек маршрутизации», обычно используемые корневые компоненты.MaterialApp
а такжеCupertinoApp
дно черезNavigator
Чтобы добиться управления глобальной маршрутизацией, мы можем передатьNavigator.of()
илиNavigator.push()
,Navigator.pop()
и другие интерфейсы для обеспечения навигации между несколькими страницами, конкретные методы заключаются в следующем:
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
}
class Nav2App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
home: HomeScreen(),
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('View Details'),
onPressed: () {
// 打开页面
Navigator.push(
context,
MaterialPageRoute(builder: (context) {
return DetailScreen();
}),
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('Pop!'),
onPressed: () {
// 弹出页面
Navigator.pop(context);
},
),
),
);
}
}
при звонкеpush()
метод,DetailScreen
компоненты будут размещены вHomeScreen
сверху, как показано.
На данный момент предыдущая страница (HomeScreen
) все еще находится в дереве компонентов, поэтому, когдаDetailScreen
Его состояние по-прежнему сохраняется при открытии.
именованный маршрут
FLUTTER также поддерживает открытие страницы по именованной маршрутизации, а имя каждой страницы состоит из «таблицы маршрутизации» и передает параметр (routes).MaterialApp
,CupertinoApp
,следующим образом:
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
}
class Nav2App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
// 路由表
routes: {
'/': (context) => HomeScreen(),
'/details': (context) => DetailScreen(),
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('View Details'),
onPressed: () {
Navigator.pushNamed(
context,
'/details',
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
),
);
}
}
При использовании именованных маршрутов необходимо предварительно определить страницы, которые необходимо открыть.Хотя мы также можем передавать данные между страницами, этот метод нативно не поддерживает прямой разбор параметров маршрутизации.Например, нельзя использовать форму ссылки в веб-приложениях. ./details/:id
название маршрута.
onGenerateRoute
Конечно, мы можем использоватьonGenerateRoute
принять полный путь маршрута, как показано ниже:
onGenerateRoute: (settings) {
// Handle '/'
if (settings.name == '/') {
return MaterialPageRoute(builder: (context) => HomeScreen());
}
// Handle '/details/:id'
var uri = Uri.parse(settings.name);
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'details') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
}
return MaterialPageRoute(builder: (context) => UnknownScreen());
},
Полный код выглядит следующим образом:
import 'package:flutter/material.dart';
void main() {
runApp(Nav2App());
}
class Nav2App extends StatelessWidget {
@override
Widget build(BuildContext context) {
return MaterialApp(
onGenerateRoute: (settings) {
// Handle '/'
if (settings.name == '/') {
return MaterialPageRoute(builder: (context) => HomeScreen());
}
// Handle '/details/:id'
var uri = Uri.parse(settings.name);
if (uri.pathSegments.length == 2 &&
uri.pathSegments.first == 'details') {
var id = uri.pathSegments[1];
return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
}
return MaterialPageRoute(builder: (context) => UnknownScreen());
},
);
}
}
class HomeScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: FlatButton(
child: Text('View Details'),
onPressed: () {
Navigator.pushNamed(
context,
'/details/1',
);
},
),
),
);
}
}
class DetailScreen extends StatelessWidget {
String id;
DetailScreen({
this.id,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('Viewing details for item $id'),
FlatButton(
child: Text('Pop!'),
onPressed: () {
Navigator.pop(context);
},
),
],
),
),
);
}
}
class UnknownScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('404!'),
),
);
}
}
Здесь мы можем пройтиRouteSettings
тип объектаsettings
может получитьNavigator.pushNamed
Параметры, передаваемые при вызове.
Navigator 2.0
Navigator 2.0 предоставляет ряд новых интерфейсов, которые могут реализовывать состояние маршрутизации как часть состояния приложения и могут реализовывать функцию разбора параметров через базовый API.Новые API следующие:
-
Page, используется для представления
Navigator
Информация о конфигурации каждой страницы в стеке маршрутизации. -
Router, используемый для формулировки
Navigator
Список отображаемых страниц, как правило, список страниц меняется в зависимости от состояния системы или приложения. -
RouteInformationParser,держать
RouteInformationProvider
который предоставилRouteInformation
, который может быть проанализирован до определенного нами типа данных. -
RouterDelegate, который определяет поведение маршрутизации в приложении, например, как Router узнает об изменениях в состоянии приложения и как реагировать. Основная задача - следить
RouteInformationParser
состояние приложения и сборка из текущего списка страниц. -
BackButtonDispatcher, ответить на кнопку "Назад" и уведомить
Router
На рисунке ниже показаноRouterDelegate
а такжеRouter
,RouteInformationParser
и принцип взаимодействия с государством,
Общий процесс выглядит следующим образом:
- Когда система открывает новую страницу (например,
“books / 2”
)Время,RouteInformationParser
преобразует его в конкретный тип данных T в приложении (например,BooksRoutePath
). - Тип данных будет передан
RouterDelegate
изsetNewRoutePath
метод, где мы можем обновить состояние маршрутизации (например, установивselectedBookId
) и звонитеnotifyListeners
Ответьте на действие. -
notifyListeners
уведомитRouter
реконструкцияRouterDelegate
(пройти черезbuild()
метод). -
RouterDelegate.build()
вернуть новыйNavigator
экземпляр и, наконец, отобразить страницу, которую мы хотим открыть (например,selectedBookId
).
Навигатор 2.0 в действии
Далее давайте используем Navigator 2.0 для небольшого упражнения. Мы реализуем приложение Flutter. Когда приложение действует в Интернете, состояние маршрутизации будет соответствовать URL-соединению в браузере, и оно также может обрабатывать резервный вариант браузера. кнопку следующим образом:
Далее используйтеflutter channel master
Переключите Flutter на основную версию,Создайте проект Flutter, который поддерживает веб-приложения.,lib/main.dart
Код в следующем:
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
void initState() {
super.initState();
}
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Books App',
home: Navigator(
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: Scaffold(),
)
],
onPopPage: (route, result) => route.didPop(result),
),
);
}
}
Pages
Navigator
принять одинpages
параметр, если список страниц изменяется,Navigator
Также необходимо обновить текущий стек маршрутизации для поддержания синхронизации.Давайте воспользуемся этим свойством для разработки приложения, которое может отображать список книг во вновь созданном проекте
_BooksAppState
содержит два параметра состояния: список книг и выбранную в данный момент книгу:
class _BooksAppState extends State<BooksApp> {
// New:
Book _selectedBook;
bool show404 = false;
List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];
// ...
Затем в_BooksAppState
, который возвращаетPage
список объектовNavigator
:
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Books App',
home: Navigator(
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
],
),
);
}
void _handleBookTapped(Book book) {
setState(() {
_selectedBook = book;
});
}
// ...
class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
Так как это приложение будет иметь две страницы (список книг и страница для деталей), если книга выбрана (используяcollection if), страница сведений будет добавлена:
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
// New:
if (show404)
MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
else if (_selectedBook != null)
MaterialPage(
key: ValueKey(_selectedBook),
child: BookDetailsScreen(book: _selectedBook))
],
Обратите внимание, что ключ здесь будет определяться значением в объекте книги какMaterialPage
Уникальный идентификатор объекта , т. е. объекта книги, отличается от приведенного здесь.MaterialPage
Просто другой. Без уникального ключа платформа не может определить, когда анимировать переходы между разными страницами.
Мы также можем наследоватьPage
Например, чтобы реализовать пользовательское поведение, добавьте на эту страницу пользовательскую анимацию перехода:
class BookDetailsPage extends Page {
final Book book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
Route createRoute(BuildContext context) {
return PageRouteBuilder(
settings: this,
pageBuilder: (context, animation, animation2) {
final tween = Tween(begin: Offset(0.0, 1.0), end: Offset.zero);
final curveTween = CurveTween(curve: Curves.easeInOut);
return SlideTransition(
position: animation.drive(curveTween).drive(tween),
child: BookDetailsScreen(
key: ValueKey(book),
book: book,
),
);
},
);
}
}
Также важно отметить, что передается только параметр pages, а неonPopPage
Он также сообщит об ошибке, он принимает функцию обратного вызова, каждый разNavigator.pop()
Эта функция запускается при вызове, где мы можем обновить состояние маршрутизации.
Наконец, вpages
Не предоставленonPopPage
В случае обратного вызова предоставление аргументов является ошибкой. каждый раз, когда его зовутNavigator.pop()
вызвать эту функцию. Он должен использоваться для обновления состояния (изменения списка страниц), здесь нам нужно вызватьdidPop
метод, чтобы определить, был ли поп успешным:
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
setState(() {
_selectedBook = null;
});
return true;
},
Мы также должны проверить, не происходит ли всплывание, прежде чем обновлять состояние приложения. Здесь мы использовалиsetState
способ уведомить Flutter о вызовеbuild()
метод, метод_selectedBook
Если он равен нулю, это означает, что нужно отобразить страницу со списком книг.
Полный код выглядит следующим образом:
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
Book _selectedBook;
List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];
@override
Widget build(BuildContext context) {
return MaterialApp(
title: 'Books App',
home: Navigator(
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (_selectedBook != null) BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
setState(() {
_selectedBook = null;
});
return true;
},
),
);
}
void _handleBookTapped(Book book) {
setState(() {
_selectedBook = book;
});
}
}
class BookDetailsPage extends Page {
final Book book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (BuildContext context) {
return BookDetailsScreen(book: book);
},
);
}
}
class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
В настоящее время мы внедрили декларативное управление маршрутизацией, в настоящее время мы не можем обрабатывать кнопку «Назад» в браузере, а также не можем синхронизировать ссылки в адресном блоке браузера.
Router
В этом разделе мы реализуемRouteInformationParser
, RouterDelegate
Обновите статус маршрутизации для синхронизации со ссылками в адресной строке браузера.
тип данных
Сначала нам нужно пройтиRouteInformationParser
Разобрать информацию о маршрутизации в указанный тип данных:
class BookRoutePath {
final int id;
final bool isUnknown;
BookRoutePath.home()
: id = null,
isUnknown = false;
BookRoutePath.details(this.id) : isUnknown = false;
BookRoutePath.unknown()
: id = null,
isUnknown = true;
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
}
В этом приложении вы можете использоватьBookRoutePath
Класс для представления пути маршрутизации в приложении, мы также можем реализовать родительский и дочерний классы для связи других типов маршрутной информации.
RouterDelegate
Далее мы реализуемRouterDelegate
подклассBookRouterDelegate
:
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
@override
Widget build(BuildContext context) {
// TODO
throw UnimplementedError();
}
@override
// TODO
GlobalKey<NavigatorState> get navigatorKey => throw UnimplementedError();
@override
Future<void> setNewRoutePath(BookRoutePath configuration) {
// TODO
throw UnimplementedError();
}
}
BookRouterDelegate
Общий типBookRoutePath
, который содержит все состояния, необходимые для выбора отображаемой страницы.
В этот момент мы можем_BooksAppState
Логика маршрутизации вBookRouterDelegate
, здесь мы создаемGlobalKey
объект, а также в нем хранятся другие состояния:
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
Book _selectedBook;
bool show404 = false;
List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
// ...
Чтобы отобразить правильный путь в URL-адресе, нам также необходимо вернутьBookRoutePath
Объект:
BookRoutePath get currentConfiguration {
if (show404) {
return BookRoutePath.unknown();
}
return _selectedBook == null
? BookRoutePath.home()
: BookRoutePath.details(books.indexOf(_selectedBook));
}
под,build
метод возвращаетNavigator
Компоненты:
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (show404)
MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
_selectedBook = null;
show404 = false;
notifyListeners();
return true;
},
);
}
Поскольку класс не является компонентом, онChangeNotifier
реализовано, так что здесьonPopPage
необходимо использовать методnotifyListeners
заменятьsetState
изменить состояние, когдаRouterDelegate
Когда запускается обновление состояния,Router
также вызоветRouterDelegate
изcurrentConfiguration
метод и вызовbuild
метод создания новогоNavigator
компоненты.
_handleBookTapped
также необходимо использовать методnotifyListeners
заменятьsetState
:
void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}
После открытия новой страницыRouter
позвонюsetNewRoutePath
способ обновления состояния маршрутизации приложения:
@override
Future<void> setNewRoutePath(BookRoutePath path) async {
if (path.isUnknown) {
_selectedBook = null;
show404 = true;
return;
}
if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
show404 = true;
return;
}
_selectedBook = books[path.id];
} else {
_selectedBook = null;
}
show404 = false;
}
RouteInformationParser
RouteInformationParser
Внутри содержит функцию ловушки, которая принимает информацию о маршрутизации (RouteInformation
), мы можем преобразовать его в указанный тип данных здесь (BookRoutePath
) и проанализировано с использованием Uri:
class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
@override
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle '/'
if (uri.pathSegments.length == 0) {
return BookRoutePath.home();
}
// Handle '/book/:id'
if (uri.pathSegments.length == 2) {
if (uri.pathSegments[0] != 'book') return BookRoutePath.unknown();
var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return BookRoutePath.unknown();
return BookRoutePath.details(id);
}
// Handle unknown routes
return BookRoutePath.unknown();
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
}
return null;
}
}
Эта реализация предназначена только для этого приложения и не является обычным решением для синтаксического анализа маршрута Конкретный принцип будет подробно объяснен позже. Наконец, чтобы использовать эти определенные классы, нам также нужно использовать новыйMaterialApp.router
Конструкторы и проходят в их соответствующих реализациях:
return MaterialApp.router(
title: 'Books App',
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
);
Полный код выглядит следующим образом:
import 'package:flutter/material.dart';
void main() {
runApp(BooksApp());
}
class Book {
final String title;
final String author;
Book(this.title, this.author);
}
class BooksApp extends StatefulWidget {
@override
State<StatefulWidget> createState() => _BooksAppState();
}
class _BooksAppState extends State<BooksApp> {
BookRouterDelegate _routerDelegate = BookRouterDelegate();
BookRouteInformationParser _routeInformationParser =
BookRouteInformationParser();
@override
Widget build(BuildContext context) {
return MaterialApp.router(
title: 'Books App',
routerDelegate: _routerDelegate,
routeInformationParser: _routeInformationParser,
);
}
}
class BookRouteInformationParser extends RouteInformationParser<BookRoutePath> {
@override
Future<BookRoutePath> parseRouteInformation(
RouteInformation routeInformation) async {
final uri = Uri.parse(routeInformation.location);
// Handle '/'
if (uri.pathSegments.length == 0) {
return BookRoutePath.home();
}
// Handle '/book/:id'
if (uri.pathSegments.length == 2) {
if (uri.pathSegments[0] != 'book') return BookRoutePath.unknown();
var remaining = uri.pathSegments[1];
var id = int.tryParse(remaining);
if (id == null) return BookRoutePath.unknown();
return BookRoutePath.details(id);
}
// Handle unknown routes
return BookRoutePath.unknown();
}
@override
RouteInformation restoreRouteInformation(BookRoutePath path) {
if (path.isUnknown) {
return RouteInformation(location: '/404');
}
if (path.isHomePage) {
return RouteInformation(location: '/');
}
if (path.isDetailsPage) {
return RouteInformation(location: '/book/${path.id}');
}
return null;
}
}
class BookRouterDelegate extends RouterDelegate<BookRoutePath>
with ChangeNotifier, PopNavigatorRouterDelegateMixin<BookRoutePath> {
final GlobalKey<NavigatorState> navigatorKey;
Book _selectedBook;
bool show404 = false;
List<Book> books = [
Book('Stranger in a Strange Land', 'Robert A. Heinlein'),
Book('Foundation', 'Isaac Asimov'),
Book('Fahrenheit 451', 'Ray Bradbury'),
];
BookRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>();
BookRoutePath get currentConfiguration {
if (show404) {
return BookRoutePath.unknown();
}
return _selectedBook == null
? BookRoutePath.home()
: BookRoutePath.details(books.indexOf(_selectedBook));
}
@override
Widget build(BuildContext context) {
return Navigator(
key: navigatorKey,
pages: [
MaterialPage(
key: ValueKey('BooksListPage'),
child: BooksListScreen(
books: books,
onTapped: _handleBookTapped,
),
),
if (show404)
MaterialPage(key: ValueKey('UnknownPage'), child: UnknownScreen())
else if (_selectedBook != null)
BookDetailsPage(book: _selectedBook)
],
onPopPage: (route, result) {
if (!route.didPop(result)) {
return false;
}
// Update the list of pages by setting _selectedBook to null
_selectedBook = null;
show404 = false;
notifyListeners();
return true;
},
);
}
@override
Future<void> setNewRoutePath(BookRoutePath path) async {
if (path.isUnknown) {
_selectedBook = null;
show404 = true;
return;
}
if (path.isDetailsPage) {
if (path.id < 0 || path.id > books.length - 1) {
show404 = true;
return;
}
_selectedBook = books[path.id];
} else {
_selectedBook = null;
}
show404 = false;
}
void _handleBookTapped(Book book) {
_selectedBook = book;
notifyListeners();
}
}
class BookDetailsPage extends Page {
final Book book;
BookDetailsPage({
this.book,
}) : super(key: ValueKey(book));
Route createRoute(BuildContext context) {
return MaterialPageRoute(
settings: this,
builder: (BuildContext context) {
return BookDetailsScreen(book: book);
},
);
}
}
class BookRoutePath {
final int id;
final bool isUnknown;
BookRoutePath.home()
: id = null,
isUnknown = false;
BookRoutePath.details(this.id) : isUnknown = false;
BookRoutePath.unknown()
: id = null,
isUnknown = true;
bool get isHomePage => id == null;
bool get isDetailsPage => id != null;
}
class BooksListScreen extends StatelessWidget {
final List<Book> books;
final ValueChanged<Book> onTapped;
BooksListScreen({
@required this.books,
@required this.onTapped,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: ListView(
children: [
for (var book in books)
ListTile(
title: Text(book.title),
subtitle: Text(book.author),
onTap: () => onTapped(book),
)
],
),
);
}
}
class BookDetailsScreen extends StatelessWidget {
final Book book;
BookDetailsScreen({
@required this.book,
});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Padding(
padding: const EdgeInsets.all(8.0),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
if (book != null) ...[
Text(book.title, style: Theme.of(context).textTheme.headline6),
Text(book.author, style: Theme.of(context).textTheme.subtitle1),
],
],
),
),
);
}
}
class UnknownScreen extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(),
body: Center(
child: Text('404!'),
),
);
}
}
Теперь запуск примера в Chrome покажет страницу маршрута текущей посещенной ссылки, а также перейдет на правильную страницу после изменения URL-адреса вручную.
TransitionDelegate
Мы также можем пройтиTransitionDelegate
Внедрение пользовательских анимаций маршрутизации.
Во-первых, чтобыNavigator
пройти в обычаеTransitionDelegate
Объект:
// New:
TransitionDelegate transitionDelegate = NoAnimationTransitionDelegate();
child: Navigator(
key: navigatorKey,
// New:
transitionDelegate: transitionDelegate,
Например, следующая реализация отключает все анимации перехода по маршруту:
class NoAnimationTransitionDelegate extends TransitionDelegate<void> {
@override
Iterable<RouteTransitionRecord> resolve({
List<RouteTransitionRecord> newPageRouteHistory,
Map<RouteTransitionRecord, RouteTransitionRecord>
locationToExitingPageRoute,
Map<RouteTransitionRecord, List<RouteTransitionRecord>>
pageRouteToPagelessRoutes,
}) {
final results = <RouteTransitionRecord>[];
for (final pageRoute in newPageRouteHistory) {
if (pageRoute.isWaitingForEnteringDecision) {
pageRoute.markForAdd();
}
results.add(pageRoute);
}
for (final exitingPageRoute in locationToExitingPageRoute.values) {
if (exitingPageRoute.isWaitingForExitingDecision) {
exitingPageRoute.markForRemove();
final pagelessRoutes = pageRouteToPagelessRoutes[exitingPageRoute];
if (pagelessRoutes != null) {
for (final pagelessRoute in pagelessRoutes) {
pagelessRoute.markForRemove();
}
}
}
results.add(exitingPageRoute);
}
return results;
}
}
здесь,NoAnimationTransitionDelegate
переписанныйTransitionDelegate
изresolve()
метод, который может указать, будут ли отображаться различные анимации перехода на страницу маршрутизации.Существует несколько следующих методов:
-
markForPush
, использовать анимацию перехода при открытии страницы. -
markForAdd
, без использования анимации перехода при открытии страницы. -
markForPop
, используйте анимацию при появлении страницы и уведомляйте приложение о том, что событие передаетсяAppRouterDelegate
изonPopPage
функция. -
markForComplete
, анимация перехода не используется при открытии страницы, и приложение также уведомляется. -
markForRemove
, анимация перехода не используется, когда страница всплывает, и приложение не уведомляется.
Этот класс влияет только на последний декларативный API, поэтому кнопка «Назад» по-прежнему показывает анимацию перехода.
Общий процесс таков: здесь просматриваются все объявленные страницы, включая новые страницы (newPageRouteHistory
) и существующие страницы (locationToExitingPageRoute
), отметьте их, используя несколько из вышеперечисленных способов. использовать на новой страницеmarkForAdd
убрать анимацию открытого перехода,locationToExitingPageRoute
использовать вmarkForRemove
Анимации перехода не отображаются, а приложения не уведомляются.Посмотреть полный пример.
дальнейшее чтение
Flutter Navigator2.0 Полное руководство и принципиальный анализ
Изучение новой системы навигации и маршрутизации Flutter
Смотреть! Flutter 1.22 официально выпущен
Три важных дерева во Flutter (процесс рендеринга, ограничения компоновки и т. д.)
Оригинальный текст моего WeChat:Tickets.WeChat.QQ.com/Yes/F U9EF HJ4X…
Добро пожаловать на обмен и учиться со мной.