Маршрутизация трепетания определяется мной

Flutter

Связанное Чтение

На основе Navigator1.0:

предисловие

Flutter 1.22В выпуске реализован Navigator 2.0, который дает разработчикам больше возможностей для выбора: вы можете гибко управлять стеком маршрутизации, обрабатывать ввод в браузере и вкладывать несколькоNavigator, хотя недочеты еще есть, но в принципе можно сделать路由我定. Давайте вместе со мной погрузимся в мир маршрутизации Flutter. Версия исходного кода этой статьиFlutter Stable 1.22.6.

Основы маршрутизации

Navigator

Отвечает за весь стек маршрутизации, который фактически являетсяOverlayнесколько похожеStack, мы часто используем его, чтобы сделатьtoast,Напримерoktoast, по сути, каждая страница представляет собойOverlayEntry.

RouteSettings

Используется для хранения имен маршрутов и параметров

Page

Появился в Navigator2.0, унаследован отRouteSettings. В основном ответственны за собственное творениеRouteИ упомяните ключ, этот ключ сзадиPageОснова для изменения суждения, обратите внимание, что этот ключ должен быть единственным ключом при нормальных обстоятельствах.

Route

В основном отвечает за обработку анимации прыжка, сохранениеRouteSettings(т.е. имя и параметры маршрута). Это также связь наложения, навигатор, _roouteentry.

  • существуетpushметод,RouteПередайте его напрямую, назначьте его_RouteEntryувеличить до_historyсередина
  Future<T> push<T extends Object>(Route<T> route) {
    _history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
    _flushHistoryUpdates();
    _afterNavigation(route);
    return route.popped;
  }
  • NavigatorState.buildЗначение initialEntries в Overlay равно OverlayEntry всех _RouteEntry.route в _history.
  Iterable<OverlayEntry> get _allRouteOverlayEntries sync* {
    for (final _RouteEntry entry in _history)
      yield* entry.route.overlayEntries;
  }
 
 Overlay(
   key: _overlayKey,
    initialEntries: overlay == null ?  _allRouteOverlayEntries.toList(growable: false) : const <OverlayEntry>[],
   ),

RouteTransitionRecord / _RouteEntry

Последний наследует первый, записывая состояние каждого маршрута.

RouteTransitionRecordимеет следующие свойства и методы, которые находятся вTransitionDelegate.resolveустановить в методе

isWaitingForEnteringDecisionотметьте, ожидает ли маршрут выхода на экранisWaitingForExitingDecisionОжидает ли отмеченный маршрут выхода с экрана

метод и из анимация возвращаемый параметр
markForPush() Войти имеют N/A
markForAdd() Войти без N/A
markForPop([dynamic result]) вне имеют имеют
markForComplete([dynamic result]) вне без имеют
markForRemove() вне без без

TransitionDelegate / DefaultTransitionDelegate

RouteTransitionRecordсуществуетTransitionDelegate.resolveв настройках.

1. КогдаPagesИнициировать обновление при его изменении, выполнитьNavigatorState.didUpdateWidget2. В этом методе сравните старый и новыйPages(К, о котором мы упоминали ранее, добавил ключ, который здесь сыграл роль) 3.resolveОпределите, какие из них добавляются, а какие удаляются.

Примерная логика в DefaultTransitionDelegate следующая

Новые страницы старые страницы условие
A=>B A=>B=>C C markForPop
A=>C A=>B=>C B markForComplete
A=>B=>C=>D A=>B=>C D markForPush
A=>B=>D=>C A=>B=>C D markForAdd

NavigatorObserver

Используется для отслеживания push, pop, replace, удаления маршрутизации и смахивания влево для выхода со страницы на платформе ios. Обычно мы можем сделать вход на страницу и выход из скрытых точек в этом, а также решить конфликт между Flutter и родным ios выходом влево в смешанной разработке.

class NavigatorObserver {
  NavigatorState get navigator => _navigator;
  NavigatorState _navigator;
  
  void didPush(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didPop(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didRemove(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didReplace({ Route<dynamic> newRoute, Route<dynamic> oldRoute }) { }

  void didStartUserGesture(Route<dynamic> route, Route<dynamic> previousRoute) { }

  void didStopUserGesture() { }
}

Navigator 1.0

Все используют это уже давно, и я думаю, что все это прекрасно знают.Рекомендуется использовать именованную маршрутизацию, а вonGenerateRouteУнифицированное управление маршрутизацией в обратных вызовах. Ранее правовая аннотация маршрутизации также основывалась наNavigator 1.0Да, если вы не понимаете, вы можете просмотреть его.

Navigator 2.0

Первый взглядNavigatorЧто изменилось в последней конструкции .

  const Navigator({
    Key key,
    // Navigator 2.0 的新东西,由它间接把路由栈暴露给用户
    this.pages = const <Page<dynamic>>[],
    // 当使用代码 pop 或者按浏览器后退按钮的时候的回调,这个时候用户可以自行处理逻辑
    this.onPopPage,
    this.initialRoute,
    this.onGenerateInitialRoutes = Navigator.defaultGenerateInitialRoutes,
    this.onGenerateRoute,
    this.onUnknownRoute,
    // 由于 pages 是暴露给用户了,所以这里可以自己设置页面的过度动画状态,见上面 [TransitionDelegate] 部分。一般来说直接用默认的就好了
    this.transitionDelegate = const DefaultTransitionDelegate<dynamic>(),
    // 是否通知给引擎,主要是在 Web 上面,告诉浏览器 URL 的改变,同步地址
    this.reportsRouteUpdateToEngine = false,
    this.observers = const <NavigatorObserver>[],
  }) 

Навигатор Каштаны

  1. давай подготовимPage, простая реализацияcreateRouteметод
class MyPage extends Page<void> {
  const MyPage({
    @required LocalKey key,
    @required String name,
    @required this.widget,
    Object arguments,
  }) : super(
          key: key,
          name: name,
          arguments: arguments,
        );

  final Widget widget;
  @override
  Route<void> createRoute(BuildContext context) {
    return MaterialPageRoute<void>(
      settings: this,
      builder: (BuildContext context) => widget,
    );
  }
}
  1. Подготовитьpages, это наш стек маршрутизации, инициализированная страница MainPage. Обратите внимание, что ключ должен быть уникальным.
  final List<MyPage> _pages = <MyPage>[
    MyPage(
        name: 'MainPage', widget: const TestPage('MainPage'), key: UniqueKey()),
  ];
  1. Используя Navigator, стоит отметить, что
  • pagesдолжна быть новая коллекция, такая, что вNavigatorState.didUpdateWidgetоценит разницу и обновит
  • onPopPageОбратный вызов может работать в соответствии со своей собственной ситуацией и, наконец,setState,уведомлятьpagesРазнообразие. позвонивnavigatorKey.currentState.pop()Или нажатие кнопки «Назад» на панели приложений вызовет обратный вызов.
    Navigator(
      reportsRouteUpdateToEngine: true,
      key: navigatorKey,
      pages: _pages.toList(),
      onPopPage: (Route<dynamic> route, dynamic result) {
        if (_pages.length > 1) {
          _pages.removeLast();
          setState(() {});
          return route.didPop(result);
        }
        return false;
      },
    ), 
  1. Теперь вы можете манипулировать стеком маршрутизации по своему усмотрению, вот несколько примеров.

Добавить новую страницу, эквивалентнуюpush

    _pages.add(
      MyPage(
          name: 'MainPageA',
          widget: const TestPage('MainPageA'),
          key: UniqueKey()),
    );
    setState(() {});

удалить последний, что эквивалентноpop

    if (_pages.length > 1) {
      _pages.removeLast();
      setState(() {});
    }

Использовать напрямуюNavigatorState.pop()метод, триггерonPopPageПерезвоните

    navigatorKey.currentState.pop();    

Теперь кажется, что мы можем отлично контролировать весь стек маршрутизации, этого достаточно? Ответа точно недостаточно, мы с ним еще не разобрались浏览器输入修改URL, 浏览器返回键,安卓物理返回键,так же какNavigator 嵌套Эта проблема.

Router

Что нового в Navigator 2.0, на первый взгляд все новое.

  const Router({
    Key key,
    this.routeInformationProvider,
    this.routeInformationParser,
    @required this.routerDelegate,
    this.backButtonDispatcher,
  })

RouteInformation

Возможны следующие 2 сценария:

  1. RouteInformationProvider => Router , что происходит, когда доступен новый маршрут, например, при вводе нового URL-адреса в браузере или инициализации маршрута в настройках кода.
  2. Router => RouteInformationProvider, это происходит только тогда, когда механизм уведомлений меняет URL-адрес браузера.
class RouteInformation {

  const RouteInformation({this.location, this.state});
  /// 比如: `/`, `/path`, `/path/to/the/app`.
  final String location;

  /// 当前页面的状态,比如滚动位置,必须是可以序列化的对象.
  final Object state;
}

RouteInformationParser

В основном отвечает за анализRouteInformation,здесьTв общемStringилиRouteSettings, что нам удобно анализировать.

abstract class RouteInformationParser<T> {

  const RouteInformationParser();
  /// 浏览器中输入一个新URL,或者在代码设置初始化路由
  Future<T> parseRouteInformation(RouteInformation routeInformation);
  /// 注意如果 reportsRouteUpdateToEngine 设置为true了,这个必须实现,不能返回 null。
  /// 传入的 T 从 RouterDelegate.currentConfiguration 获得
  RouteInformation restoreRouteInformation(T configuration) => null;
}

RouteInformationProvider

В основном отвечает за уведомление об изменениях RouteInformation.

abstract class RouteInformationProvider extends ValueListenable<RouteInformation> {
  void routerReportsNewRouteInformation(RouteInformation routeInformation) {}
}

Обычно используется для инициализации маршрутизации.

routeInformationProvider: PlatformRouteInformationProvider(
  initialRouteInformation: const RouteInformation(
    location: '/mainpage',  
  ),
),

RouterDelegate

Создать и настроитьNavigatorпрокси, как писалось ранееNavigator demoпочти. Разница в том, чтобы увеличить пару浏览器输入修改URL, 浏览器返回键,安卓物理返回键обработка.

abstract class RouterDelegate<T> extends Listenable {
  
  /// 初始化路由会调用该方法
  Future<void> setInitialRoutePath(T configuration) {

​    return setNewRoutePath(configuration);

  }
  
  /// 新增路由比如 浏览器中输入一个新URL,或者在代码设置初始化路由
  Future<void> setNewRoutePath(T configuration);


  /// `浏览器返回键`,`安卓物理返回键` 会调用该方法
  Future<bool> popRoute();
  
  /// RouteInformationParser.restoreRouteInformation 会
  /// 获取该值用于报告给引擎,特别是在 Web 应用中
  T get currentConfiguration => null;
  
  /// 返回 Navigator
  Widget build(BuildContext context);

}

PopNavigatorRouterDelegateMixin

Чтобы помочь вам реализовать RouterDelegatepopRouteметод, не требуется.

mixin PopNavigatorRouterDelegateMixin<T> on RouterDelegate<T> {
  /// The key used for retrieving the current navigator.
  ///
  /// When using this mixin, be sure to use this key to create the navigator.
  GlobalKey<NavigatorState> get navigatorKey;

  @override
  Future<bool> popRoute() {
    final NavigatorState navigator = navigatorKey?.currentState;
    if (navigator == null)
      return SynchronousFuture<bool>(false);
    return navigator.maybePop();
  }
}

Анализ исходного кода

Я просто говорил о том, какие API решают какие задачи. Здесь мы будем отслеживать, как реализуется официальная реализация. Я ввожу новый URL в браузере.

  • Из последнего шага на картинке видно, что это нативный метод от движка

  • К методу _handleNavigationInvocation:popа такжеpushВсе это здесь, здесь получаются уведомления от движка, включая ввод браузера, нажатия браузером и физическим нажатием кнопки «Назад» в Android.

  Future<dynamic> _handleNavigationInvocation(MethodCall methodCall) {
    switch (methodCall.method) {
      case 'popRoute':
        return handlePopRoute();
      case 'pushRoute':
        return handlePushRoute(methodCall.arguments as String);
      case 'pushRouteInformation':
        return _handlePushRouteInformation(methodCall.arguments as Map<dynamic, dynamic>);
    }
    return Future<dynamic>.value();
  }
  
  • В методе зарегистрированWidgetsBindingObserverОтправьте событие и верните его, когда оно будет найдено и обработано. (гнездо заранее закапывается здесьNavigatorяма)
  Future<void> handlePushRoute(String route) async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPushRoute(route))
        return;
    }
  }
  
 Future<void> handlePopRoute() async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (await observer.didPopRoute())
        return;
    }
    SystemNavigator.pop();
  }
  
  Future<void> _handlePushRouteInformation(Map<dynamic, dynamic> routeArguments) async {
    for (final WidgetsBindingObserver observer in List<WidgetsBindingObserver>.from(_observers)) {
      if (
        await observer.didPushRouteInformation(
          RouteInformation(
            location: routeArguments['location'] as String,
            state: routeArguments['state'] as Object,
          )
        )
      )
      return;
    }
  }  
  • WidgetsApp наследует WidgetsBindingObserver, вWidgetsBinding.instance.addObserver(this)добавить себя в_observersв
class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {

  @override
  void initState() {
    WidgetsBinding.instance.addObserver(this);
  }
  
    @override
  Future<bool> didPushRoute(String route) async {
  }
  • а такжеPlatformRouteInformationProviderДобавьте себя в addListener при инициализации маршрутизатора_observersсередина.
class _RouterState<T> extends State<Router<T>> {
  @override
  void initState() {
    super.initState();
widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
class PlatformRouteInformationProvider extends RouteInformationProvider with WidgetsBindingObserver, ChangeNotifier {
  @override
  void addListener(VoidCallback listener) {
    if (!hasListeners)
      WidgetsBinding.instance.addObserver(this);
    super.addListener(listener);
  }

Наконец-то мы можем получить изменения маршрутизации, которые нам сообщает движок!

BackButtonDispatcher

включатьRootBackButtonDispatcherа такжеChildBackButtonDispatcher. главным образом для решенияNavigator 嵌套, вернемся к проблеме приоритета кнопок.

Возьмите каштан:

1.MyAppЯвляетсяNavigator, начальная страницаNestedMainPage, после нажатия средней кнопкиpushприбытьChildRouterPage. теперь первыйNavigatorТам 2 страницы(NestedMainPage,ChildRouterPage)

2.ChildRouterPageтакжеNavigator, начальная страницаNestedTestPage, после нажатия средней кнопкиpushприбытьTestPageA. теперь первыйNavigatorТам 2 страницы(NestedTestPage,TestPageA)

3. Теперь мы видим, чтоTestPageA, то теперь нажмите安卓物理返回键или浏览器返回键Что такое феномен?

4. Страница возвращается кNestedMainPage, это не должен быть результат, которого все хотят, верно?

Так как же решить эту проблему? Ранее мы знали, что у нас есть способ контролировать安卓物理返回键или浏览器返回键да, это_handleNavigationInvocationв методеpopRouteобратный вызов, но первый, имеющий приоритет, можетpopизNavigator(Помните яму, когда ее раздавали). На самом деле мы можем решить для себяpopRouteНа какой из них действует обратный вызовNavigator. Нам просто нужно сделать следующее на втором маршрутизаторе.

добраться до предыдущегоRouterсерединаbackButtonDispatcher, и получить приоритет.

 Widget build(BuildContext context) {
    final ChildBackButtonDispatcher childBackButtonDispatcher =
        Router.of(context)
            .backButtonDispatcher
            .createChildBackButtonDispatcher();
    childBackButtonDispatcher.takePriority();
    return Router<RouteSettings>(
      backButtonDispatcher: childBackButtonDispatcher,
    );
  }

Анализ исходного кода:

  • очень знакомыйWidgetsBindingObserver,addCallbackдобавляйте себя в монитор когда_observers, ожидая, пока движок доставит событие,WidgetsBindingраспространяется в.
class RootBackButtonDispatcher extends BackButtonDispatcher with WidgetsBindingObserver {
  RootBackButtonDispatcher();

  @override
  void addCallback(ValueGetter<Future<bool>> callback) {
    if (!hasCallbacks)
      WidgetsBinding.instance.addObserver(this);
    super.addCallback(callback);
  }

  @override
  void removeCallback(ValueGetter<Future<bool>> callback) {
    super.removeCallback(callback);
    if (!hasCallbacks)
      WidgetsBinding.instance.removeObserver(this);
  }

  @override
  Future<bool> didPopRoute() => invokeCallback(Future<bool>.value(false));
}
  • вызовtakePriorityГоворя о методе, добавьте его вparentизchildrenСреди них убедитесь, что вы последний, и опустошите свой собственныйchildren.
class ChildBackButtonDispatcher extends BackButtonDispatcher {
  ChildBackButtonDispatcher(this.parent) : assert(parent != null);
  final BackButtonDispatcher parent;
  @protected
  Future<bool> notifiedByParent(Future<bool> defaultValue) {
    return invokeCallback(defaultValue);
  }

  @override
  void takePriority() {
    parent.deferTo(this);
    super.takePriority();
  }
  
  /// BackButtonDispatcher 中的实现,方便讲解
  void deferTo(ChildBackButtonDispatcher child) {
    assert(hasCallbacks);
    _children ??= <ChildBackButtonDispatcher>{} as LinkedHashSet<ChildBackButtonDispatcher>;
    _children.remove(child); // child may or may not be in the set already
    _children.add(child);
  }
  
  /// BackButtonDispatcher 中的实现,方便讲解
  void takePriority() {
    if (_children != null)
      _children.clear();
  }
}
  • существуетinvokeCallbackметод, изchildrenПоследний начинает обход (поэтому вdeferToметод первыйremove,назадadd), посмотрите, кто занимаетсяdidPopRouteСобытие, если оно обрабатывается, останавливается.
  Future<bool> invokeCallback(Future<bool> defaultValue) {
    if (_children != null && _children.isNotEmpty) {
      final List<ChildBackButtonDispatcher> children = _children.toList();
      int childIndex = children.length - 1;

      Future<bool> notifyNextChild(bool result) {
        // If the previous child handles the callback, we returns the result.
        if (result)
          return SynchronousFuture<bool>(result);
        // If the previous child did not handle the callback, we ask the next
        // child to handle the it.
        if (childIndex > 0) {
          childIndex -= 1;
          return children[childIndex]
            .notifiedByParent(defaultValue)
            .then<bool>(notifyNextChild);
        }
        // If none of the child handles the callback, the parent will then handle it.
        return super.invokeCallback(defaultValue);
      }

      return children[childIndex]
        .notifiedByParent(defaultValue)
        .then<bool>(notifyNextChild);
    }
    return super.invokeCallback(defaultValue);
  }

Обзор Навигатора 2.0

  • через паруNavigator.pagesуправления, чтобы полностью овладеть стеком маршрутизации.
  • пройти черезRouterи связанные с нимиApiрешить浏览器输入修改URL, 浏览器返回键,安卓物理返回键проблемы с взаимодействием с нативным, иNavigatorПрокси и настройка.
  • пройти черезBackButtonDispatcherобработанныйNavigator 嵌套Эта проблема.

Кажется, что толстая кишка Навигатора 2.0 идеальна, но все же есть некоторые недостатки в реальном использовании.

  • Нам предстоит многого достичь, не так ли?duangВы можете использовать его сразу?
  • Проблемы с разбором входных параметров вручную в веб-браузерах
  • из-заRouteсуществуетPageизcreateRouteгенерируется в методе, поэтому мы не можем получить к нему прямой доступRoute. Если мы хотим написать аналогичныйpushКак насчет методов с параметрами обратного вызова?
  Future<T> push<T extends Object>(Route<T> route) {
    _history.add(_RouteEntry(route, initialState: _RouteLifecycle.push));
    _flushHistoryUpdates();
    _afterNavigation(route);
    return route.popped;
  }

эм, да,法法路由注解 5.0Навигатор уже отлично поддерживается1.0а также2.0Да, разбросанные цветы!

Правовая аннотация маршрутизации 5.0

добавить цитаты

добавить ссылку наdependencies, и вам нужно аннотировать проект/пакеты дляpubspec.yamlсередина

dev_dependencies:
  ff_annotation_route_core: any
  ff_annotation_route_library: any

воплощать в жизньflutter packages getскачать

добавить аннотацию

пустая конструкция

import 'package:ff_annotation_route/ff_annotation_route.dart';

@FFRoute(
  name: "fluttercandies://mainpage",
  routeName: "MainPage",
)
class MainPage extends StatelessWidget
{
  // ...
}

Построить с параметрами

Инструмент автоматически обрабатывает конструкции с параметрами, и никакой специальной обработки не требуется. Единственная оговорка заключается в том, что вам нужно использоватьargumentImportsУкажите адрес импорта для параметра class/enum. Теперь вместо этого вы можете использовать @FFArgumentImport().

@FFArgumentImport('hide TestMode2')
import 'package:example1/src/model/test_model.dart';
@FFArgumentImport()
import 'package:example1/src/model/test_model1.dart' hide TestMode3;
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';

@FFRoute(
  name: 'flutterCandies://testPageE',
  routeName: 'testPageE',
  description: 'Show how to push new page with arguments(class)',
  // 为了防止 @FFArgumentImport() 不能完全表达的情况, 依然保留 argumentImports。
  // argumentImports: <String>[
  //   'import \'package:example1/src/model/test_model.dart\';',
  //   'import \'package:example1/src/model/test_model1.dart\';',
  // ],
  exts: <String, dynamic>{
    'group': 'Complex',
    'order': 1,
  },
)
class TestPageE extends StatelessWidget {
  const TestPageE({
    this.testMode = const TestMode(
      id: 2,
      isTest: false,
    ),
    this.testMode1,
  });
  factory TestPageE.deafult() => TestPageE(
        testMode: TestMode.deafult(),
      );

  factory TestPageE.required({@required TestMode testMode}) => TestPageE(
        testMode: testMode,
      );

  final TestMode testMode;
  final TestMode1 testMode1;
}

FFRoute

Parameter Description Default
name Имя маршрутизации (например, «/ Настройки») required
showStatusBar Показывать ли строку состояния true
routeName Название страницы, используемой для сбора данных ''
pageRouteType Тип трассы (материальная, купертино, прозрачная) -
description описание маршрута ''
exts Дополнительные параметры расширения. -
argumentImports Импорт некоторых параметров Некоторые параметры являются классами или перечислениями, нужно указать адрес их импорта, теперь вместо них можно использовать @FFArgumentImport() -

makefile

окружающая обстановка

Добавьте путь к корзине dart в вашу систему$PATH.

cache\dart-sdk\bin

Больше информации

Если вы не знаете, вы можете увидетьНаггетс

активация

pub global activate ff_annotation_route

Выполнение заказа

Перейдите в корневой каталог вашего проекта и выполните.

ff_route <command> [arguments]

Параметры команды

Доступные команды:

-h, --[no-]help                   帮助信息。

-p, --path                        执行命令的目录,默认当前目录。

-o, --output                      route 和 helper 文件的输出目录路径,路径相对于主项目的 lib 文件夹。

-n, --name                        路由常量类的名称,默认为 `Routes`。

-g, --git                         扫描 git 引用的 package,你需要指定 package 的名字,多个用 `,` 分开
    --routes-file-output          routes 文件的输出目录路径,路径相对于主项目的lib文件夹
    --const-ignore                使用正则表达式忽略一些const(不是全部const都希望生成)
    --[no-]route-constants        是否在根项目中的 `xxx_route.dart` 生成全部路由的静态常量
    --[no-]package                这个是否是一个 package
    --[no-]supper-arguments       是否生成路由参数帮助类

-s, --[no-]save                   是否保存命令到本地。如果保存了,下一次就只需要执行 `ff_route` 就可以了。

Навигатор аннотаций 1.0

Полный код находится наexampleсередина

Main.dart

import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/material.dart';
import 'example_route.dart';
import 'example_routes.dart';

void main() => runApp(MyApp());

class MyApp extends StatelessWidget {
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      initialRoute: Routes.fluttercandiesMainpage,
      onGenerateRoute: (RouteSettings settings) {
        return onGenerateRoute(
          settings: settings,
          getRouteSettings: getRouteSettings,
          routeSettingsWrapper: (FFRouteSettings ffRouteSettings) {
            if (ffRouteSettings.name == Routes.fluttercandiesMainpage ||
                ffRouteSettings.name ==
                    Routes.fluttercandiesDemogrouppage.name) {
              return ffRouteSettings;
            }
            return ffRouteSettings.copyWith(
                widget: CommonWidget(
              child: ffRouteSettings.widget,
              title: ffRouteSettings.routeName,
            ));
          },
        );
      },
    );
  }
}

Push

Push name
  Navigator.pushNamed(context, Routes.fluttercandiesMainpage /* fluttercandies://mainpage */);
Push name with arguments
  • Аргумент должен бытьMap<String, dynamic>
  Navigator.pushNamed(
    context,
    Routes.flutterCandiesTestPageE,
    arguments: <String, dynamic>{
      constructorName: 'required',
      'testMode': const TestMode(
        id: 100,
        isTest: true,
      ),
    },
  );
  • включить --supper-аргументы
  Navigator.pushNamed(
    context,
    Routes.flutterCandiesTestPageE.name,
    arguments: Routes.flutterCandiesTestPageE.requiredC(
      testMode: const TestMode(
        id: 100,
        isTest: true,
      ),
    ),
  );

Навигатор аннотаций 2.0

полный код в полный код вexample1середина

Main.dart

import 'dart:convert';
import 'package:example1/src/model/test_model.dart';
import 'package:ff_annotation_route_library/ff_annotation_route_library.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'example1_route.dart';
import 'example1_routes.dart';

void main() {
  // 工具将处理简单的类型,但是没法处理全部的
  // 比如在浏览器中输入以下地址
  // http://localhost:64916/#flutterCandies://testPageF?list=[4,5,6]&map={"ddd":123}&testMode={"id":2,"isTest":true}
  // queryParameters 将会根据你自身的情况转换成你对应的类型
  FFConvert.convert = <T>(dynamic value) {
    if (value == null) {
      return null;
    }
    print(T);
    final dynamic output = json.decode(value.toString());
    if (<int>[] is T && output is List<dynamic>) {
      return output.map<int>((dynamic e) => asT<int>(e)).toList() as T;
    } else if (<String, String>{} is T && output is Map<dynamic, dynamic>) {
      return output.map<String, String>((dynamic key, dynamic value) =>
          MapEntry<String, String>(key.toString(), value.toString())) as T;
    } else if (const TestMode() is T && output is Map<dynamic, dynamic>) {
      return TestMode.fromJson(output) as T;
    }

    return json.decode(value.toString()) as T;
  };
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  final FFRouteInformationParser _ffRouteInformationParser =
      FFRouteInformationParser();

  final FFRouterDelegate _ffRouterDelegate = FFRouterDelegate(
    getRouteSettings: getRouteSettings,
    pageWrapper: <T>(FFPage<T> ffPage) {
      return ffPage.copyWith(
        widget: ffPage.name == Routes.fluttercandiesMainpage ||
                ffPage.name == Routes.fluttercandiesDemogrouppage.name
            ? ffPage.widget
            : CommonWidget(
                child: ffPage.widget,
                routeName: ffPage.routeName,
              ),
      );
    },
  );
  // This widget is the root of your application.
  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      title: 'ff_annotation_route demo',
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      // 初始化第一个页面
      routeInformationProvider: PlatformRouteInformationProvider(
        initialRouteInformation: const RouteInformation(
          location: Routes.fluttercandiesMainpage,
        ),
      ),
      routeInformationParser: _ffRouteInformationParser,
      routerDelegate: _ffRouterDelegate,
    );
  }
}

FFRouteInformationParser

В основном используется на веб-платформе, когда вы вводите в браузере, конфигурация маршрутизации преобразуется вRouteSettings, или при возврате в браузер будетRouteSettingsПреобразование в конфигурацию маршрутизации

Например:

xxx?a=1&b=2 <=> RouteSettings(name:'xxx',arguments:<String, dynamic>{'a':'1','b':'2'})

FFRouterDelegate

Делегат для создания и настройки навигации, который обеспечиваетNavigatorаналогичные методы в .

  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageF.name,
    arguments: Routes.flutterCandiesTestPageF.d(
      <int>[1, 2, 3],
      map: <String, String>{'ddd': 'dddd'},
      testMode: const TestMode(id: 1, isTest: true),
    ),
  );

ты сможешьtest_page_c.dartНайдите больше примеров на странице

Push

Push name
  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageA,
  );
Push name with arguments
  • Аргумент должен бытьMap<String, dynamic>
  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageF.name,
    arguments: Routes.flutterCandiesTestPageF.d(
      <int>[1, 2, 3],
      map: <String, String>{'ddd': 'dddd'},
      testMode: const TestMode(id: 1, isTest: true),
    ),
  );
  • включить --supper-аргументы
  FFRouterDelegate.of(context).pushNamed<void>(
    Routes.flutterCandiesTestPageF.name,
    arguments: <String, dynamic>{
        'list': <int>[1, 2, 3],
        'map': <String, String>{'ddd': 'dddd'},
        'testMode': const TestMode(id: 1, isTest: true),
     }
  )

Code Hints

Вы можете использовать маршрут «Routes.flutterCandiesTestPageE» следующим образом и увидеть подсказку кода в редакторе. Включая описание страницы, структуру, тип параметра, имя параметра, требуется ли параметр.

  • По умолчанию
  /// 'This is test page E.'
  ///
  /// [name] : 'flutterCandies://testPageE'
  ///
  /// [routeName] : 'testPageE'
  ///
  /// [description] : 'This is test page E.'
  ///
  /// [constructors] :
  ///
  /// TestPageE : [TestMode testMode, TestMode1 testMode1]
  ///
  /// TestPageE.deafult : []
  ///
  /// TestPageE.required : [TestMode(required) testMode]
  ///
  /// [exts] : {group: Complex, order: 1}
  static const String flutterCandiesTestPageE = 'flutterCandies://testPageE';
  • включить --supper-аргументы
  /// 'This is test page E.'
  ///
  /// [name] : 'flutterCandies://testPageE'
  ///
  /// [routeName] : 'testPageE'
  ///
  /// [description] : 'This is test page E.'
  ///
  /// [constructors] :
  ///
  /// TestPageE : [TestMode testMode, TestMode1 testMode1]
  ///
  /// TestPageE.test : []
  ///
  /// TestPageE.requiredC : [TestMode(required) testMode]
  ///
  /// [exts] : {group: Complex, order: 1}
  static const _FlutterCandiesTestPageE flutterCandiesTestPageE =
      _FlutterCandiesTestPageE();

  class _FlutterCandiesTestPageE {
    const _FlutterCandiesTestPageE();

    String get name => 'flutterCandies://testPageE';

    Map<String, dynamic> d(
            {TestMode testMode = const TestMode(id: 2, isTest: false),
            TestMode1 testMode1}) =>
        <String, dynamic>{
          'testMode': testMode,
          'testMode1': testMode1,
        };

    Map<String, dynamic> test() => const <String, dynamic>{
          'constructorName': 'test',
        };

    Map<String, dynamic> requiredC({@required TestMode testMode}) =>
        <String, dynamic>{
          'testMode': testMode,
          'constructorName': 'requiredC',
        };

    @override
    String toString() => name;
  }

Эпилог

  • 路由我定, В жизни много перекрестков, и нам нужно сделать выбор. Выбор больше, чем усилия.Иногда вы думаете, что есть только один путь, но на самом деле есть и другие варианты. Иногда думаешь, что есть и другие варианты, но на самом деле остается только один путь. Стоя на перекрестке жизни, я надеюсь, что мы больше не будем колебаться, То ли я сейчас, то ли ты в будущем, я не хочу знать это потом.
  • ДелатьFlutterТоже прошло два года.С тех пор, как я начал заниматься один, появилось много мелких партнеров, с которыми можно обсудить,FlutterЯ многому научился и познакомился с большим количеством людей. Я не знаю, как долго программист может писать代码, но от работы над коммуникационным проектом в легкорельсовом транспорте Чунцина до смены карьеры, чтобы писать код, это действительно потому, что мне это нравится. Я считаю, что человек может заниматься тем, что ему нравится, это должно быть некое счастье.
  • Спасибо, что дочитали до этого места, и я надеюсь, что эта статья была вам полезна. Love Flutter, люблю конфеты, добро пожаловать, присоединяйтесьFlutter Candies, чтобы вместе производить милые конфеты FlutterГруппа QQ: 181398081. надевать в последнюю очередьFlutter CandiesВся семейная бочка, действительно ароматная.