日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

Flutter:Navigator2.0介绍及使用

發布時間:2024/4/15 编程问答 31 豆豆
生活随笔 收集整理的這篇文章主要介紹了 Flutter:Navigator2.0介绍及使用 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

目錄

APP

RouteInformationParser

RouterDelegate

問題

The Navigator.pages must not be empty to use the Navigator.pages API

瀏覽器的回退按鈕

總結

源碼


Navigator1.0

我們學習flutter一開始接觸的路由管理就是Navigator1.0,它非常方便,使用簡單,如下:

class MyApp extends StatelessWidget {@overrideWidget build(BuildContext context) {return MaterialApp(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),onGenerateRoute: (RouteSettings settings){return PageRouteBuilder(settings: settings,pageBuilder: (BuildContext context, Animation<double> animation,Animation<double> secondaryAnimation) {if(settings.name == "pageB"){return PageB();}else if(settings.name == "pageC"){return PageC();}else{return Container();}});},// routes: {// "pageB" : (BuildContext context) => PageB(),// "pageC" : (BuildContext context) => PageC()// },home: PageA(),);} }

通過onGenerateRoute或routes來注冊路由,使用時通過Navigator.of(context).pushNamed()或者其他函數即可。

Navigator1.0使用簡單,但是問題也一樣,只有push、pop等幾個簡單操作,對于復雜場景就無能為力了,比如web開發時地址欄或后退鍵的處理。

所以google后來又推出了Navigator2.0

Navigator2.0

Navigator1.0是通過Navigator來管理處理路由,而Navigator2.0則是通過Router來處理的,但是也需要Navigator,實際上是用Router對Navigator包裹起來。Router相對來說功能就強大很多了,同時使用起來也復雜很多。

關于Navigator2.0的原理,網上已經有很多文章了,但是我發現這些文章在使用實例上都不是很清楚,或者說示例過于復雜。應該是大部分參考google官方文檔簡單翻譯的,但是其實我們正常場景使用并不是那么復雜,而且大部分都沒有講清楚。所以本篇文章不討論原理,只用最簡單的示例來展示如果使用Navigator2.0,或者說如何快速的從Navigator1.0轉成Navigator2.0。

APP

首先創建MaterialApp方式有了改變,通過MaterialApp.router()來創建,如下:

class MyApp extends StatelessWidget {final delegate = MyRouteDelegate();@overrideWidget build(BuildContext context) {return MaterialApp.router(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),routerDelegate: delegate,routeInformationParser: MyRouteParser(),);} }

通過這種方式我們需要設置routerDelegate和routeInformationParser,這樣就需要實現這兩個類。

RouteInformationParser

創建一個類繼承RouteInformationParser,主要的作用是包裝解析路由信息,這里有一個最簡單的方式,如下:

class MyRouteParser extends RouteInformationParser<String> {@overrideFuture<String> parseRouteInformation(RouteInformation routeInformation) {return SynchronousFuture(routeInformation.location);}@overrideRouteInformation restoreRouteInformation(String configuration) {return RouteInformation(location: configuration);} }

我們的路由信息都由一個字符串承載,可以用url的形式,這樣方便處理。

RouterDelegate

RouterDelegate是最重要的部分,這里實現路由切換的邏輯,繼承RouterDelegate的類需要實現下面的函數:

void addListener(listener) void removeListener(listener)Widget build(BuildContext context)Future<bool> popRoute() Future<void> setNewRoutePath(T configuration)

其中addListener和removeListener是來自RouterDelegate的繼承Listenable。

build一般返回的是一個Navigator。

popRoute實現后退邏輯

setNewRoutePath實現新頁面的邏輯

單單這么說肯定一頭霧水,我們用一個示例來實現它,具體代碼如下:

class MyRouteDelegate extends RouterDelegate<String> with PopNavigatorRouterDelegateMixin<String>, ChangeNotifier{@overrideGlobalKey<NavigatorState> navigatorKey = GlobalKey<NavigatorState>();@overrideString get currentConfiguration => _stack.isNotEmpty ? _stack.last : null;final _stack = <String>[];@overrideWidget build(BuildContext context) {return Navigator(key: navigatorKey,pages: [for (final url in _stack)getPage(url)],onPopPage: (route, result){if (_stack.isNotEmpty) {_stack.removeLast();notifyListeners();}return route.didPop(result);},);}Page getPage(String url){return MaterialPage(name: url,arguments: null,child: getWidget(url));}Widget getWidget(String name){switch(name){case "pageB":return PageB();case "pageC":return PageC();default:return PageA();}}@overrideFuture<void> setNewRoutePath(String config) {if(config == "/"){_stack.clear();}if(_stack.isEmpty || config != _stack.last) {_stack.add(config);notifyListeners();}return SynchronousFuture<void>(null);} }

首先我們不僅繼承RouterDelegate,同時還繼承ChangeNotifier,這樣就不必實現addListener和removeListener了。

注意:如果這里手動實現了addListener和removeListener但是并沒有實現代碼,這樣會導致頁面無法切換,因為路由變化沒有通知。現象就是點擊切換頁面的按鈕無反應,build不執行。

然后又繼承了PopNavigatorRouterDelegateMixin,它實現了popRoute函數,所以這個函數也可以不用實現。但是繼承它后需要實現navigatorKey,如上第一行。

通過上面兩個繼承,我們只需要實現setNewRoutePath和build兩個函數即可。先看setNewRoutePath的代碼:

@overrideFuture<void> setNewRoutePath(String config) {if(config == "/"){_stack.clear();}if(_stack.isEmpty || config != _stack.last) {_stack.add(config);notifyListeners();}return SynchronousFuture<void>(null);}

_stack是一個列表,用來存儲所有路由信息,因為前面我們的路由信息用String承載,所以_stack是一個字符串列表。

在這個函數里將新路由添加進_stack,然后調用notifyListeners()通知路由變化。

注意這里的兩個邏輯,如果是首頁則先清空;如果新頁面與上一頁一摸一樣,則忽略,因為發現在web上setNewRoutePath會被重復調用。

然后是build函數,如下:

@overrideWidget build(BuildContext context) {return Navigator(key: navigatorKey,pages: [for (final url in _stack)getPage(url)],onPopPage: (route, result){if (_stack.isNotEmpty) {_stack.removeLast();notifyListeners();}return route.didPop(result);},);}

?返回一個Navigator,設置pages和onPopPage。

在onPopPage中實現回退邏輯,可以看到將列表中最后一個remove掉,然后notifyListeners()同時路由變化。上面我們提到PopNavigatorRouterDelegateMixin實現了popRoute函數,它的實現代碼最終就會調用到onPopPage這里。

pages則是一個Page列表,是當前已經打開的所有頁面,所以用一個for循環來創建,我自己定義了一個getPage函數:

Page getPage(String url){return MaterialPage(name: url,arguments: null,child: getWidget(url));}Widget getWidget(String name){switch(name){case "pageB":return PageB();case "pageC":return PageC();default:return PageA();}}

注意:因為我們的示例中路由沒有參數,只有路由名稱,所以上面對url沒有進行處理。但是實際使用的時候,在getPage函數一開始就應該對url進行處理,提取出name和參數,并將參數整理成Object設置給arguments,這樣頁面中就可以用之前的方式(ModalRoute.of(context).settings.arguments)獲取,不用改變太多。

這里我定義了三個頁面,其中PageA是默認頁面。三個頁面都很簡單,每個頁面有兩個按鈕,一個打開新頁面,一個回退。

打開新頁面用

Router.of(context).routerDelegate.setNewRoutePath("pageB");

代替了之前Navigator1.0中的

Navigator.of(context).pushNamed("pageB");

回退則使用

Router.of(context).routerDelegate.popRoute();

代替了之前Navigator1.0中的

Navigator.of(context).pop();

這樣頁面內的改動很小,可以很快的轉到Navigator2.0。

到這里還差最后一步,實現RouterDelegate中字段currentConfiguration的get方法,如下:

@override String get currentConfiguration => _stack.isNotEmpty ? _stack.last : null;

如果不實現這里,雖然頁面可以切換,但是路由信息并沒有更新,比如flutter web的應用在瀏覽器中,頁面正常切換,但是地址欄并沒有變化。只有實現了這個get函數,當路由發生變化的時候,其他類才能通過這個函數獲取到最新路由。

上面就是Navigator2.0的簡單使用,相對于官方的示例更簡單一些,也更容易理解核心部分,尤其方便從Navigator1.0升級到Navigator2.0。

問題

這個過程還是出現不少問題的,記錄一下:

The Navigator.pages must not be empty to use the Navigator.pages API

報錯如下:

════════ Exception caught by widget library ════════════════════════════════════════════════════════ The following assertion was thrown: The Navigator.pages must not be empty to use the Navigator.pages APIWhen the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 906:28 get current packages/flutter/src/widgets/navigator.dart 3345:33 <fn> packages/flutter/src/widgets/navigator.dart 3361:14 initState packages/flutter/src/widgets/framework.dart 4632:57 [_firstBuild] packages/flutter/src/widgets/framework.dart 4469:5 mount ... ════════════════════════════════════════════════════════════════════════════════════════════════════════════ Exception caught by widgets library ═══════════════════════════════════════════════════════ Navigator.onGenerateRoute was null, but the route named "/" was referenced. The relevant error-causing widget was: MaterialApp file:///Users/bennu/fluttertest/lib/main.dart:62:24 ════════════════════════════════════════════════════════════════════════════════════════════════════════════ Exception caught by widget library ════════════════════════════════════════════════════════ The following assertion was thrown: The Navigator.pages must not be empty to use the Navigator.pages APIWhen the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 906:28 get current packages/flutter/src/widgets/navigator.dart 3345:33 <fn> packages/flutter/src/widgets/navigator.dart 3361:14 initState packages/flutter/src/widgets/framework.dart 4632:57 [_firstBuild] packages/flutter/src/widgets/framework.dart 4469:5 mount ... ════════════════════════════════════════════════════════════════════════════════════════════════════════════ Exception caught by widgets library ═══════════════════════════════════════════════════════ Navigator.onGenerateRoute was null, but the route named "/" was referenced. The relevant error-causing widget was: MaterialApp file:///Users/bennu/fluttertest/lib/main.dart:62:24 ════════════════════════════════════════════════════════════════════════════════════════════════════════════ Exception caught by widget library ════════════════════════════════════════════════════════ The following assertion was thrown: A HeroController can not be shared by multiple Navigators. The Navigators that share the same HeroController are:- NavigatorState#1f365(lifecycle state: initialized) - NavigatorState#9f699(lifecycle state: initialized) Please create a HeroControllerScope for each Navigator or use a HeroControllerScope.none to prevent subtree from receiving a HeroController. When the exception was thrown, this was the stack: dart-sdk/lib/_internal/js_dev_runtime/patch/core_patch.dart 906:28 get current packages/flutter/src/widgets/navigator.dart 3501:41 <fn> packages/flutter/src/scheduler/binding.dart 1144:15 [_invokeFrameCallback] packages/flutter/src/scheduler/binding.dart 1090:9 handleDrawFrame packages/flutter/src/scheduler/binding.dart 865:7 <fn> ... ════════════════════════════════════════════════════════════════════════════════════════════════════

這里涉及到一開始App的創建,回顧一下代碼:

class MyApp extends StatelessWidget {final delegate = MyRouteDelegate();@overrideWidget build(BuildContext context) {return MaterialApp.router(title: 'Flutter Demo',theme: ThemeData(primarySwatch: Colors.blue,visualDensity: VisualDensity.adaptivePlatformDensity,),routerDelegate: delegate,routeInformationParser: MyRouteParser(),);} }

注意MyRouteDelegate并不是在build中創建的,而是在初始化時就創建了。如果在build中才創建就會出現上面的問題,如果像上面代碼一樣在初始化創建就沒有這個問題了。

瀏覽器的回退按鈕

經過測試發現,瀏覽器的后退按鈕點擊后并不執行pop操作,而是執行setNewRoutePath,這樣就會導致回退的時候實際上_stack并沒有移除當前頁面,反而將上一個頁面重新添加進來了,這樣_stack路徑就亂了。

這個問題有個官方issues:https://github.com/flutter/flutter/issues/71122

其中官方提到:

the?browser?backward?button?no?longer?tie?to?the?didpopRoute?in?navigator?2.0.?it?is?now?acting?as?deeplinking.?Whenever?backward?or?forward?button?is?pressed,?the?web?engine?will?get?the?new?url?and?send?that?to?the?framework?through?didpushRoute.

BackButtonDispatcher?is?for?android?back?button,?it?will?only?be?triggered?in?android.

這里涉及的BackButtonDispatcher也是Navigator2.0的功能,可以攔截處理返回鍵,但是通過上面可以看出這個功能只對android的返回鍵有效。而在web上,無論是前進還是后退鍵,都是當初新的url處理,會執行didpushRoute,所以就執行到了setNewRoutePath,而不是pop。

issues中也提到了,目前官方沒有解決這個問題,不過已經列入todo列表了,目前想要解決這個問題需要我們自己手動開發一個plugin,可能需要在native層處理,即在html中通過history處理并暴露api給flutter,比較復雜,所以目前這個問題并沒有很好的解決方法。

總結

通過上面可以看出,Navigator2.0相對來說復雜很多,開發和學習成本大大提高,這也是很多人詬病的原因,所以有人認為Navigator2.0是一個失敗的改造,這也導致目前大家很少使用它。

源碼

關注公眾號:BennuCTech,發送“Navi2”獲取源碼。

?

超強干貨來襲 云風專訪:近40年碼齡,通宵達旦的技術人生

總結

以上是生活随笔為你收集整理的Flutter:Navigator2.0介绍及使用的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。