Flutter Navigator2.0的原理和Web端實(shí)踐

1.背景與動(dòng)機(jī)

Navigator 2.0推出之前乖坠,Flutter主要通過Navigator 1.0和其提供的 API(如push(), pop(), pushNamed()等)來管理頁面路由歌焦。然而端考,Navigator 1.0存在一些局限性颠印,如難以實(shí)現(xiàn)復(fù)雜的頁面操作(如移除棧內(nèi)中間頁面、交換頁面等)洪乍、不支持嵌套路由以及無法滿足全平臺(尤其是Web平臺)的新需求狂塘。因此,Flutter官方團(tuán)隊(duì)決定對路由系統(tǒng)進(jìn)行改造呼伸,推出了Navigator 2.0身冀。

2.主要特性

  • 聲明式API
    Navigator 2.0提供的聲明式API使得路由管理更加直觀和易于理解。開發(fā)者只需聲明頁面的配置信息括享,而無需編寫復(fù)雜的導(dǎo)航邏輯代碼搂根。這種方式不僅減少了代碼量,還提高了代碼的可讀性和可維護(hù)性铃辖。
  • 嵌套路由
    Navigator 2.0滿足了嵌套路由的需求場景剩愧,允許開發(fā)者在應(yīng)用中創(chuàng)建嵌套的路由結(jié)構(gòu)。這使得應(yīng)用的結(jié)構(gòu)更加清晰娇斩,同時(shí)也提高了頁面導(dǎo)航的靈活性仁卷。
  • 全平臺支持
    Navigator 2.0提供的API能夠滿足不同平臺(如iOS穴翩、AndroidWeb等)的導(dǎo)航需求锦积,使得開發(fā)者能夠更加方便地構(gòu)建跨平臺的應(yīng)用芒帕。
  • 強(qiáng)大的頁面操作能力
    Navigator 2.0提供了更加豐富的頁面操作能力,如移除棧內(nèi)中間頁面丰介、交換頁面等背蟆。這些操作在Navigator 1.0中很難實(shí)現(xiàn)或需要編寫復(fù)雜的代碼,而在Navigator 2.0中則變得簡單直接哮幢。

3.核心組件

  • Router
    Navigator 2.0中带膀,Router組件是路由管理的核心。它負(fù)責(zé)根據(jù)當(dāng)前的路由信息(RouteInformation)和路由信息解析器(RouteInformationParser)來構(gòu)建和更新UI橙垢。Router組件接收三個(gè)主要參數(shù):
    1.routeInformationProvider:提供當(dāng)前的路由信息垛叨。
    2.routeInformationParser:將路由信息解析為路由配置。
    3.routerDelegate:根據(jù)路由配置構(gòu)建和更新UI柜某。
  • RouteInformationProvider
    RouteInformationProvider是一個(gè)提供當(dāng)前路由信息的組件嗽元。它通常與平臺相關(guān)的路由信息源(如瀏覽器的URLAndroidIntent等)集成喂击,以獲取當(dāng)前的路由信息还棱。
  • RouteInformationParser
    RouteInformationParser負(fù)責(zé)將RouteInformation解析為RouteConfiguration。這個(gè)過程允許開發(fā)者根據(jù)路由信息的格式(如URL)來定義如何將其映射到應(yīng)用內(nèi)的路由配置惭等。
  • RouterDelegate
    RouterDelegate是與UI構(gòu)建緊密相關(guān)的組件。它必須實(shí)現(xiàn)RouterDelegate接口办铡,并提供兩個(gè)主要方法:
    1.build(BuildContext context):根據(jù)當(dāng)前的路由配置構(gòu)建UI辞做。
    2.setNewRoutePath(List<RouteSettings> configuration):設(shè)置新的路由路徑,并更新UI寡具。
    3.Future<bool> popRoute() :實(shí)現(xiàn)后退邏輯秤茅。

4.簡單實(shí)例

首先通過MaterialApp.router()來創(chuàng)建MaterialApp:

class MyApp extends StatelessWidget {  
  @override  
  Widget build(BuildContext context) {  
    final routerDelegate = MyRouterDelegate();  
    final routeInformationParser = MyRouteInformationParser();  
  
    return MaterialApp.router(  
      title: 'Flutter Navigator 2.0 Demo',  
      theme: ThemeData(  
        primarySwatch: Colors.blue,  
      ),  
      routerDelegate: routerDelegate,  
      routeInformationParser: routeInformationParser,  
    );  
  }  
} 

需要定義一個(gè)RouterDelegate對象和一個(gè)RouteInformationParser對象。其中根據(jù)路由配置構(gòu)建和更新UI童叠,RouteInformationParser負(fù)責(zé)將RouteInformation解析為RouteConfiguration框喳。
RouterDelegate可以傳個(gè)泛型,定義其currentConfiguration對象的類型厦坛。

class MyRouterDelegate extends RouterDelegate<String>  
  with PopNavigatorRouterDelegateMixin<String>, ChangeNotifier {  
  final GlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();  
  private List<String> _pages = ['/home'];  
  
  @override  
  Widget build(BuildContext context) {  
    return Navigator(  
      key: navigatorKey,  
      pages: _pages.map((route) => MaterialPage(  
        key: Key(route),  
        child: generatePage(route),  
      )).toList(),  
      onPopPage: (route, result) {  
        if (!route.didPop(result)) {  
          return false;  
        }  
  
        _pages.removeLast();  
        notifyListeners();  
        return true;  
      },  
    );  
  }  
  
  @override  
  Future<void> setNewRoutePath(String path) async {  
    if (!_pages.contains(path)) {  
      _pages.add(path);  
      notifyListeners();  
    }  
  }  
  
  Widget generatePage(String route) {  
    switch (route) {  
      case '/home':  
        return HomePage();  
      case '/details':  
        // 這里可以傳遞參數(shù)五垮,例如 DetailsPage(arguments: someData)  
        return DetailsPage();  
      default:  
        return NotFoundPage();  
    }  
  }  
  
  @override  
  String get currentConfiguration => _pages.last;  
}

其中build()一般返回的是一個(gè)Navigator對象,popRoute()實(shí)現(xiàn)后退邏輯杜秸,setNewRoutePath()實(shí)現(xiàn)新頁面的邏輯放仗。
定義了一個(gè)_pages數(shù)組對象,記錄每個(gè)路由的path撬碟,可以理解為是一個(gè)路由棧诞挨,這個(gè)路由棧對我們來說非常友好莉撇,在有復(fù)雜的業(yè)務(wù)邏輯時(shí),我們可以自行定義相應(yīng)的棧管理邏輯惶傻。currentConfiguration返回的是棧頂?shù)?code>page信息棍郎。
創(chuàng)建一個(gè)類繼承RouteInformationParser,主要的作用是包裝解析路由信息银室,這里有一個(gè)最簡單的方式涂佃,如下:

class MyRouteInformationParser extends RouteInformationParser<String> {  
  @override  
  Future<String> parseRouteInformation(RouteInformation routeInformation) {  
    final uri = Uri.parse(routeInformation.location);  
    return SynchronousFuture(uri.path);  
  }  
  
  @override  
  RouteInformation restoreRouteInformation(String configuration) {  
    return RouteInformation(location: configuration);  
  }  
} 

好的,接下來我們看一下調(diào)用:

class HomePage extends StatelessWidget {  
  @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
      appBar: AppBar(title: Text('Home')),  
      body: Center(  
        child: ElevatedButton(  
          onPressed: () {  
            Router.of(context).routerDelegate.setNewRoutePath("/details");
          },  
          child: Text('Go to Details'),  
        ),  
      ),  
    );  
  }  
}  
  
class DetailsPage extends StatelessWidget {  
  @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
      appBar: AppBar(title: Text('Details')),  
      body: Center(  
        child: Text('This is Details Page'),  
      ),  
    );  
  }  
} 

class NotFoundPage extends StatelessWidget {  
  @override  
  Widget build(BuildContext context) {  
    return Scaffold(  
      appBar: AppBar(title: Text('Not Found')),  
      body: Center(  
        child: Text('Page not found'),  
      ),  
    );  
  }  
}

非常簡單粮揉,直接調(diào)用Router.of(context).routerDelegate.setNewRoutePath()即可巡李。

到此為止,一個(gè)使用Navigator2.0的最簡單的路由實(shí)例就完成了扶认。和Navigator1.0相比侨拦,看上去繁雜了不少。但是可以根據(jù)業(yè)務(wù)需求自定義路由棧進(jìn)行管理辐宾,大大的提升了靈活性狱从。接來看我們看一下Navigator2.0是如何對路由進(jìn)行實(shí)現(xiàn)的。

5.源碼簡析

我們在使用Navigator2.0時(shí)叠纹,是通過MaterialApp.router()創(chuàng)建的MaterialApp對象季研,之前章節(jié)提到過,傳了RouteInformationParserRouterDelegate這兩個(gè)對象誉察。當(dāng)傳遞了RouterDelegate對象時(shí)与涡,_MaterialAppState中的_usesRouter會(huì)被設(shè)置為true

 bool get _usesRouter => widget.routerDelegate != null || widget.routerConfig != null;

build()時(shí)持偏,通過WidgetsApp.router()方法創(chuàng)建了一個(gè)WidgetsApp對象:

if (_usesRouter) {
      return WidgetsApp.router(
        key: GlobalObjectKey(this),
        routeInformationProvider: widget.routeInformationProvider,
        routeInformationParser: widget.routeInformationParser,
        routerDelegate: widget.routerDelegate,
        routerConfig: widget.routerConfig,
        backButtonDispatcher: widget.backButtonDispatcher,
        builder: _materialBuilder,
        title: widget.title,
        onGenerateTitle: widget.onGenerateTitle,
        textStyle: _errorTextStyle,
        color: materialColor,
        locale: widget.locale,
        localizationsDelegates: _localizationsDelegates,
        localeResolutionCallback: widget.localeResolutionCallback,
        localeListResolutionCallback: widget.localeListResolutionCallback,
        supportedLocales: widget.supportedLocales,
        showPerformanceOverlay: widget.showPerformanceOverlay,
        checkerboardRasterCacheImages: widget.checkerboardRasterCacheImages,
        checkerboardOffscreenLayers: widget.checkerboardOffscreenLayers,
        showSemanticsDebugger: widget.showSemanticsDebugger,
        debugShowCheckedModeBanner: widget.debugShowCheckedModeBanner,
        inspectorSelectButtonBuilder: _inspectorSelectButtonBuilder,
        shortcuts: widget.shortcuts,
        actions: widget.actions,
        restorationScopeId: widget.restorationScopeId,
      );
    }

_WidgetsAppState中根據(jù)routerDelegate設(shè)置了成員變量_usesRouterWithDelegates的值:

bool get _usesRouterWithDelegates => widget.routerDelegate != null;

build()時(shí)會(huì)創(chuàng)建一個(gè)Router對象驼卖,其中Router繼承了StatefulWidget

  @override
  Widget build(BuildContext context) {
    Widget? routing;
    if (_usesRouterWithDelegates) {
      routing = Router<Object>(
        restorationScopeId: 'router',
        routeInformationProvider: _effectiveRouteInformationProvider,
        routeInformationParser: widget.routeInformationParser,
        routerDelegate: widget.routerDelegate!,
        backButtonDispatcher: _effectiveBackButtonDispatcher,
      );
    } 
......
  }

在上一章節(jié)的實(shí)例中我們可得知,頁面的切換都是依靠RouterDelegate對象進(jìn)行的鸿秆。
每當(dāng)切換到新的頁面時(shí)酌畜,都會(huì)調(diào)用setNewRoutePath()方法,因此我們來看一下setNewRoutePath()是什么時(shí)候被調(diào)用的卿叽,有兩處:
第一處:

  void _handleRouteInformationProviderNotification() {
    _routeParsePending = true;
    _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
  }
  _RouteSetter<T> _processParsedRouteInformation(Object? transaction, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
    return (T data) async {
      if (_currentRouterTransaction != transaction) {
        return;
      }
      await delegateRouteSetter()(data);
      if (_currentRouterTransaction == transaction) {
        _rebuild();
      }
    };
  }

我們看看_handleRouteInformationProviderNotification的調(diào)用時(shí)機(jī):

  @override
  void initState() {
    super.initState();
    widget.routeInformationProvider?.addListener(_handleRouteInformationProviderNotification);
    widget.backButtonDispatcher?.addCallback(_handleBackButtonDispatcherNotification);
    widget.routerDelegate.addListener(_handleRouterDelegateNotification);
  }

我們可以看到在initState()時(shí)桥胞,也就是在Router被初始化的時(shí)候由widget.routeInformationProvider來監(jiān)聽一些狀態(tài)實(shí)現(xiàn)新頁面的切換。我們來看一下routeInformationProvider考婴。RouteInformationProvider在我們自己沒有創(chuàng)建的情況下贩虾,系統(tǒng)會(huì)默認(rèn)為我們創(chuàng)建一個(gè)PlatformRouteInformationProvider對象。它實(shí)際上是個(gè)ChangeNotifier沥阱。系統(tǒng)會(huì)監(jiān)聽每一幀的信號發(fā)送整胃,調(diào)用其父類routerReportsNewRouteInformation()方法,我們看看它的實(shí)現(xiàn):

  @override
  void routerReportsNewRouteInformation(RouteInformation routeInformation, {RouteInformationReportingType type = RouteInformationReportingType.none}) {
    final bool replace =
      type == RouteInformationReportingType.neglect ||
      (type == RouteInformationReportingType.none &&
      _equals(_valueInEngine.uri, routeInformation.uri));
    SystemNavigator.selectMultiEntryHistory();
    SystemNavigator.routeInformationUpdated(
      uri: routeInformation.uri,
      state: routeInformation.state,
      replace: replace,
    );
    _value = routeInformation;
    _valueInEngine = routeInformation;
  }

其中SystemNavigator.selectMultiEntryHistory()的實(shí)現(xiàn)如下:

  /// Selects the multiple-entry history mode.
  ///
  /// On web, this switches the browser history model to one that tracks all
  /// updates to [routeInformationUpdated] to form a history stack. This is the
  /// default.
  ///
  /// Currently, this is ignored on other platforms.
  ///
  /// See also:
  ///
  ///  * [selectSingleEntryHistory], which forces the history to only have one
  ///    entry.
  static Future<void> selectMultiEntryHistory() {
    return SystemChannels.navigation.invokeMethod<void>('selectMultiEntryHistory');
  }

這個(gè)方法是由各個(gè)平臺自行實(shí)現(xiàn)的。從注釋中我們可得知如果是在Web平臺下屁使,它會(huì)切換成history模式在岂,并從history stack中追蹤所有的變化。 在history發(fā)生變化時(shí)蛮寂,會(huì)發(fā)送信號給Flutter層等待處理蔽午。SystemNavigator.routeInformationUpdated()方法是用來更新路由的,我們先不做分析酬蹋。
接著我們回到PlatformRouteInformationProvider及老,看看它什么時(shí)候會(huì)執(zhí)行notifyListeners()方法:

  @override
  Future<bool> didPushRouteInformation(RouteInformation routeInformation) async {
    assert(hasListeners);
    _platformReportsNewRouteInformation(routeInformation);
    return true;
  }
  void _platformReportsNewRouteInformation(RouteInformation routeInformation) {
    if (_value == routeInformation) {
      return;
    }
    _value = routeInformation;
    _valueInEngine = routeInformation;
    notifyListeners();
  }

在監(jiān)聽到有push路由的情況下時(shí),會(huì)調(diào)用notifyListeners()范抓,從而實(shí)現(xiàn)頁面的切換骄恶。
我們再來看第二處調(diào)用setNewRoutePath()的地方:

  @override
  void didChangeDependencies() {
    _routeParsePending = true;
    super.didChangeDependencies();
    // The super.didChangeDependencies may have parsed the route information.
    // This can happen if the didChangeDependencies is triggered by state
    // restoration or first build.
    if (widget.routeInformationProvider != null && _routeParsePending) {
      _processRouteInformation(widget.routeInformationProvider!.value, () => widget.routerDelegate.setNewRoutePath);
    }
    _routeParsePending = false;
    _maybeNeedToReportRouteInformation();
  }
  void _processRouteInformation(RouteInformation information, ValueGetter<_RouteSetter<T>> delegateRouteSetter) {
    assert(_routeParsePending);
    _routeParsePending = false;
    _currentRouterTransaction = Object();
    widget.routeInformationParser!
      .parseRouteInformationWithDependencies(information, context)
      .then<void>(_processParsedRouteInformation(_currentRouterTransaction, delegateRouteSetter));
  }

parseRouteInformationWithDependencies()方法中調(diào)用的parseRouteInformation()其實(shí)就是我們自定義RouteInformationParser來進(jìn)行的實(shí)現(xiàn)。

  Future<T> parseRouteInformationWithDependencies(RouteInformation routeInformation, BuildContext context) {
    return parseRouteInformation(routeInformation);
  }

看到當(dāng)其與父的依賴關(guān)系被改變的時(shí)候會(huì)調(diào)用setNewRoutePath()匕垫。大概率就是App初始化的時(shí)候被調(diào)用一次僧鲁。

6.根據(jù)狐友業(yè)務(wù)的Web端實(shí)踐

我們的Flutter團(tuán)隊(duì)會(huì)承擔(dān)一些運(yùn)營活動(dòng)的H5需求。在實(shí)現(xiàn)時(shí)我們對路由有如下需求:
1.可以根據(jù)業(yè)務(wù)自由的管理路由棧象泵。
2.分享鏈接只能分享出去默認(rèn)入口鏈接寞秃,不希望中間的路由鏈接被分享出去。
3.不管有多少個(gè)路由頁面偶惠,history始終不變春寿,在響應(yīng)瀏覽器返回鍵時(shí)不響應(yīng)路由棧的pop操作。
在之前使用Navigator1.0時(shí)體驗(yàn)并不太好忽孽,一個(gè)是不夠靈活绑改,另外還需對分享出去的鏈接做處理。因此我們利用Navigator2.0設(shè)計(jì)了一套新的路由:

MyRouterDelegate delegate = MyRouterDelegate();

  @override
  Widget build(BuildContext context) {
     return MaterialApp.router(
        debugShowCheckedModeBanner: false,
        routeInformationParser: MyRouteParser(),
        routerDelegate: delegate,
      );
  }

Parser實(shí)現(xiàn)非常簡單:

class MyRouteParser extends RouteInformationParser<RouteSettings> {
  @override
  ///parseRouteInformation() 方法的作用就是接受系統(tǒng)傳遞給我們的路由信息 routeInformation
  Future<RouteSettings> parseRouteInformation(
      RouteInformation routeInformation) {
    // Uri uri = Uri.parse(routeInformation.location??"/");
    return SynchronousFuture(RouteSettings(name: routeInformation.location));
  }

  @override
  ///恢復(fù)路由信息
  RouteInformation restoreRouteInformation(RouteSettings configuration) {
    return RouteInformation(location: configuration.name);
  }
}

Delegate的實(shí)現(xiàn)如下:

import 'package:ai_chatchallenge/router/exit_util.dart';
import 'package:ai_chatchallenge/router/navigator_util.dart';
import 'package:ai_chatchallenge/router/my_router_arg.dart';
import 'package:flutter/material.dart';

import 'route_page_config.dart';

class MyRouterDelegate extends RouterDelegate<RouteSettings>
    with PopNavigatorRouterDelegateMixin<RouteSettings>, ChangeNotifier {
  ///頁面棧
  List<Page> _stack = [];

  //當(dāng)前的界面信息
  RouteSettings _setting = RouteSettings(
      name: RouterName.rootPage,
      arguments: BaseArgument()..name = RouterName.rootPage);

  //重寫navigatorKey
  @override
  GlobalKey<NavigatorState> navigatorKey;

  MyRouterDelegate() : navigatorKey = GlobalKey<NavigatorState>() {
    //初始化兩個(gè)方法 一個(gè)是push頁面 另一個(gè)是替換頁面

    NavigatorUtil()
        .registerRouteJump(RouteJumpFunction(onJumpTo: (RouteSettings setting) {
      // _setting = setting;
      // changePage();
      addPage(name: setting.name, arguments: setting.arguments);
    }, onReplaceAndJumpTo: (RouteSettings setting) {
      if (_stack.isNotEmpty) {
        _stack.removeLast();
      }
      _setting = setting;
      changePage();
    }, onClearStack: () {
      _stack.clear();
      _setting = RouteSettings(
          name: RouterName.rootPage,
          arguments: BaseArgument()..name = RouterName.rootPage);
      changePage();
    }, onBack: () {
      if (_stack.isNotEmpty) {
        _stack.removeLast();
        if (_stack.isNotEmpty) {
          _setting = _stack.last;
        } else {
          _setting = RouteSettings(
              name: RouterName.rootPage,
              arguments: BaseArgument()..name = RouterName.rootPage);
        }

        changePage();
      }
    }));
  }

  @override
  RouteSettings? get currentConfiguration {
    return _stack.last;
  }

  @override
  Future<bool> popRoute() {
    if (_stack.length > 1) {
      _stack.removeLast();
      _setting = _stack.last;
      changePage();
      //非最后一個(gè)頁面
      return Future.value(true);
    }
    //最后一個(gè)頁面確認(rèn)退出操作
    return _confirmExit();
  }

  Future<bool> _confirmExit() async {
    bool result = ExitUtil.doubleCheckExit(navigatorKey.currentContext!);
    // bool result = await ExitUtil.backToDesktop();
    return !result;
  }

  void addPage({required name, arguments}) {
    _setting = RouteSettings(name: name, arguments: arguments);
    changePage();
  }

  @override
  Widget build(BuildContext context) {
    return WillPopScope(
      //解決物理返回建無效的問題
      onWillPop: () async => !await navigatorKey.currentState!.maybePop(),
      child: Navigator(
        key: navigatorKey,
        pages: _stack,
        onPopPage: _onPopPage,
      ),
    );
  }

  /// 按下返回的回調(diào)
  bool _onPopPage(Route<dynamic> route, dynamic result) {
    debugPrint("這里的試試");
    if (!route.didPop(result)) {
      return false;
    }
    return true;
  }

  changePage() {
    int index = getCurrentIndex(_stack, _setting!);
    List<Page> tempPages = _stack;

    if (index != -1) {
      // 要求棧中只允許有一個(gè)同樣的頁面的實(shí)例 否則開發(fā)模式熱更新會(huì)報(bào)錯(cuò)
      // 要打開的頁面在棧中已存在兄一,則將該頁面和它上面的所有頁面進(jìn)行出棧
      tempPages = tempPages.sublist(0, index);
      // 或者刪除之前存在棧里的頁面,重新創(chuàng)建
      // tempPages.removeAt(index);
    }
    Page page;

    if (_setting?.arguments is BaseArgument) {
      if ((_setting?.arguments as BaseArgument).name == RouterName.rootPage) {
        _stack.clear();
      }
    } else {
      if (_setting?.name == RouterName.rootPage) {
        _stack.clear();
      }
    }
    page = buildPage(name: _setting?.name, arguments: _setting?.arguments);
    tempPages = [...tempPages, page];
    NavigatorUtil().notify(tempPages, _stack);

    _stack = tempPages;
    notifyListeners();
  }

  @override
  Future<void> setInitialRoutePath(RouteSettings configuration) {
    return super.setInitialRoutePath(_setting);
  }

  @override
  Future<void> setNewRoutePath(RouteSettings configuration) async {
    if (configuration.arguments is BaseArgument) {
      if ((configuration.arguments as BaseArgument).name ==
          RouterName.rootPage) {
        _stack.clear();
      }
    } else {
      if (configuration.name == RouterName.rootPage) {
        _stack.clear();
      }
    }
    addPage(name: configuration.name, arguments: configuration.arguments);
  }
}

其中_stack是我們的路由棧绢淀,_settingRouteSettings,每執(zhí)行一個(gè)新的路由跳轉(zhuǎn)瘾腰,都會(huì)創(chuàng)建一個(gè)RouteSettings對象并賦值給_setting,最終在插入_stack里覆履。buildPage()的實(shí)現(xiàn)如下:

//建造頁面
buildPage({required name, arguments}) {
  return MaterialPage(
      child: getPageChild(name: name, arguments: arguments),
      arguments: arguments,
      name: name,
      key: ValueKey(
          arguments is BaseArgument ? (arguments as BaseArgument).name : name));
}

其中MaterialPage繼承了Page蹋盆。getPageChild()實(shí)現(xiàn)如下:

Widget getPageChild({required name, arguments}) {
  Widget page;
  Map? arg;
  if (arguments is Map) {
    arg = arguments;
  }
  if (arguments is BaseArgument) {
    switch ((arguments as BaseArgument).name) {
      case RouterName.rootPage:
        page = TestHomePage();
        break;
      case RouterName.testChild1Page:
        page = TestChildPage1(
          argument: arguments.arguments as TestChild1PageArgument,
        );
        break;
      case RouterName.testChild2Page:
        page = TestChildPage2();
        break;
      default:
        page = TestHomePage();
    }
  } else {
    page = TestHomePage();
  }

  return page;
}

class RouterName {
  static const rootPage = "/";
  static const testChild1Page = "/testChild1Page";
  static const testChild2Page = "/testChild2Page";
}

我們可以看到,在真正返回Widget時(shí)硝全,我們并沒有使用傳入的name參數(shù)栖雾,而是BaseArgumentname參數(shù),這是為什么呢伟众?這是在于我們?yōu)榱藢?shí)現(xiàn)無論頁面怎么跳轉(zhuǎn)析藕,從頭到尾瀏覽器只保留一個(gè)history,因此我們在頁面跳轉(zhuǎn)時(shí)RouteSettingsname并不發(fā)生變化凳厢,通過其arguments里面的參數(shù)變化返回不同的Widget账胧。這樣在路由跳轉(zhuǎn)時(shí)竞慢,其實(shí)MaterialPage由于name一直會(huì)被直接復(fù)用,從而不會(huì)創(chuàng)建新的MaterialPage也就不會(huì)產(chǎn)生history治泥。
NavigatorUtil是由業(yè)務(wù)調(diào)用的筹煮,創(chuàng)建跳轉(zhuǎn)方法的抽象類,提供了onJumpTo()居夹,onReplaceAndJumpTo()败潦,onClearStack()onBack()四個(gè)方法供業(yè)務(wù)調(diào)用准脂,我們可以看一下onJumpTo()的實(shí)現(xiàn):

  @override
  void onJumpTo(
      {required name,
      Object? stackArguments,
      Map<String, dynamic>? historyArgMap,
      BuildContext? context}) {
      var arg = BaseArgument();
      arg.name = name;
      arg.arguments = stackArguments;
      RouteSettings settings =
          RouteSettings(name: RouterName.rootPage, arguments: arg);
      return _function!.onJumpTo!(settings);
  }

可以看到在創(chuàng)建RouteSettings對象時(shí)劫扒,nameRouterName.rootPagearg時(shí)由業(yè)務(wù)傳的真正的跳轉(zhuǎn)頁面相關(guān)的參數(shù)狸膏。
我們看一下業(yè)務(wù)的調(diào)用:

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
          children: [
            Text("TestHomePage"),
            Text("history length is : " + window.history.length.toString()),
            Text("href: " + WebUtil.get().getWindow().location.href),
            TextButton(
                onPressed: () {
                  var arg = TestChild1PageArgument()..isSuccess = "false";
                  NavigatorUtil().onJumpTo(
                      name: RouterName.testChild1Page,
                      stackArguments: arg,
                      historyArgMap: arg.toJson(),
                      context: context);
                },
                child: Text("Go to TestChildPage1"))
          ],
        ),
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
          children: [
            Text("TestChildPage1"),
            Text("history length is : " + window.history.length.toString()),
            Text("href: " + WebUtil.get().getWindow().location.href),
            TextButton(
                onPressed: () {
                  NavigatorUtil().onJumpTo(
                      name: RouterName.testChild2Page, context: context);
                },
                child: Text("Go to TestChildPage2")),
            TextButton(
                onPressed: () {
                  NavigatorUtil().onBack();
                },
                child: Text("Back to TestHomePage")),
          ],
        ),
      ),
    );
  }
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: Container(
        child: Column(
          children: [
            Text("TestChildPage2"),
            Text("history length is : " + window.history.length.toString()),
            Text("href: " + WebUtil.get().getWindow().location.href),
            TextButton(
                onPressed: () {
                  NavigatorUtil().onBack();
                },
                child: Text("Back to TestChild1page")),
            TextButton(
                onPressed: () {
                  NavigatorUtil().onClearStack();
                },
                child: Text("Back to Root")),
          ],
        ),
      ),
    );
  }

我們看一下截圖展示:


截屏2024-07-04 09.32.28.png

截屏2024-07-04 09.32.40.png

截屏2024-07-04 09.32.46.png

在這個(gè)過程中href不會(huì)發(fā)生變化沟饥,history也不會(huì)發(fā)生變化,完全符合我們的預(yù)期环戈。

6.總結(jié)

FlutterNavigator 2.0引入了聲明式的API闷板,使頁面路由管理更加靈活和強(qiáng)大。相較于Navigator 1.0院塞,Navigator 2.0支持更復(fù)雜的路由操作遮晚,如嵌套路由和動(dòng)態(tài)路由配置。它使用不可變的Page對象列表來表示路由歷史拦止,與Flutter的不可變Widgets設(shè)計(jì)理念一致县遣。Navigator 2.0還支持命名路由,通過簡單的路由名稱即可實(shí)現(xiàn)頁面跳轉(zhuǎn)汹族,大大簡化了路由管理的復(fù)雜度萧求。此外,它還提供了更豐富的路由回調(diào)和狀態(tài)管理功能顶瞒,使開發(fā)者能夠更輕松地構(gòu)建復(fù)雜的Flutter應(yīng)用夸政。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市榴徐,隨后出現(xiàn)的幾起案子守问,更是在濱河造成了極大的恐慌,老刑警劉巖坑资,帶你破解...
    沈念sama閱讀 206,482評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件耗帕,死亡現(xiàn)場離奇詭異,居然都是意外死亡袱贮,警方通過查閱死者的電腦和手機(jī)仿便,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,377評論 2 382
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人嗽仪,你說我怎么就攤上這事荒勇。” “怎么了钦幔?”我有些...
    開封第一講書人閱讀 152,762評論 0 342
  • 文/不壞的土叔 我叫張陵枕屉,是天一觀的道長。 經(jīng)常有香客問我鲤氢,道長搀擂,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,273評論 1 279
  • 正文 為了忘掉前任卷玉,我火速辦了婚禮哨颂,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘相种。我一直安慰自己威恼,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,289評論 5 373
  • 文/花漫 我一把揭開白布寝并。 她就那樣靜靜地躺著箫措,像睡著了一般。 火紅的嫁衣襯著肌膚如雪衬潦。 梳的紋絲不亂的頭發(fā)上斤蔓,一...
    開封第一講書人閱讀 49,046評論 1 285
  • 那天,我揣著相機(jī)與錄音镀岛,去河邊找鬼弦牡。 笑死,一個(gè)胖子當(dāng)著我的面吹牛漂羊,可吹牛的內(nèi)容都是我干的驾锰。 我是一名探鬼主播,決...
    沈念sama閱讀 38,351評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼走越,長吁一口氣:“原來是場噩夢啊……” “哼椭豫!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起旨指,我...
    開封第一講書人閱讀 36,988評論 0 259
  • 序言:老撾萬榮一對情侶失蹤赏酥,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淤毛,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,476評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡算柳,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 35,948評論 2 324
  • 正文 我和宋清朗相戀三年低淡,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 38,064評論 1 333
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡蔗蹋,死狀恐怖何荚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情猪杭,我是刑警寧澤餐塘,帶...
    沈念sama閱讀 33,712評論 4 323
  • 正文 年R本政府宣布,位于F島的核電站皂吮,受9級特大地震影響戒傻,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜蜂筹,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,261評論 3 307
  • 文/蒙蒙 一需纳、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧艺挪,春花似錦不翩、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,264評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至津坑,卻和暖如春妙蔗,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背国瓮。 一陣腳步聲響...
    開封第一講書人閱讀 31,486評論 1 262
  • 我被黑心中介騙來泰國打工灭必, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人乃摹。 一個(gè)月前我還...
    沈念sama閱讀 45,511評論 2 354
  • 正文 我出身青樓禁漓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親孵睬。 傳聞我的和親對象是個(gè)殘疾皇子播歼,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,802評論 2 345

推薦閱讀更多精彩內(nèi)容