使用Bloc的時候,有一個讓我至今爲止十分在乎的問題,沒法真正的跨頁面交互!在反覆的查閱官方文檔後,使用一個全局Bloc的方式,實現了「僞」跨頁面交互,詳細可查看: flutter_bloc使用解析;fish_redux的廣播機制是能夠比較完美的實現跨頁面交互的,我也寫了一篇近萬字介紹如何使用該框架: fish_redux使用詳解,對於中小型項目使用fish_redux,這會必定程度上下降開發效率,最近嘗試了GetX相關功能,解決了個人至關一部分痛點把整篇文章寫完後,我立刻把本身的一個demo裏面全部Bloc代碼全用GetX替換,且去掉了Fluro框架;感受用Getx雖然會省掉大量的模板代碼,但仍是有些重複工做:建立文件夾,建立幾個必備文件,寫那些必需要寫的初始化代碼和類;略微繁瑣,爲了對得起GetX給我開發帶來的巨大便利,我就花了一些時間,給它寫了一個插件! 上面這重複的代碼,文件,文件夾通通能一鍵生成!html
GetX相關優點java
build刷新方法極度簡單!git
BlocBuilder
方法直接寫在頁面頂層(不提倡寫頂層),一個頁面只用寫一次了,不用定點處處寫BlocBuilder
了,手動滑稽.jpg跨頁面交互github
路由管理web
上面單單是build簡寫的優點,就會讓我考慮是否去使用了,並且還能實現跨頁面的功能,這還考慮啥,開搞!redux
下來將全面的介紹GetX的使用,文章也不分篇水閱讀量了,力求一文寫清楚,方便你們隨時查閱api
# getx 狀態管理框架 https://pub.flutter-io.cn/packages/get get: ^3.24.0
GetX地址app
MaterialApp
改爲GetMaterialApp
便可void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( home: CounterGetPage(), ); } }
import 'package:get/get.dart';
吐槽下寫插件的過程,實際寫這種模板代碼生成插件,其實也不難,網上有不少人寫了範例,參考參考思路,能較快的整出來,就是有些配置比較蛋筒。一開始選擇Plugin DevKit模式整的,都已經寫好,可是看官網文檔的時候,官方文檔開頭就說了:建議使用Gradle模式開發插件,又巴拉巴拉列了一堆好處;考慮良久,決定用Gradle模式重寫。框架
這個Gradle模式,最煩的仍是開頭新建項目的時候,那個Gradle死活下載不下來,科學全局上網都不行,而後手動下載了Gradle,指定本地Gradle,開全局再次同步時,會下載一個較大的社區版IDEA,可是使用本地Gradle加載完,存在一個很大的BUG!main文件夾下,不會自動生成Java文件夾!我真是佛了,點擊其它的文件夾,右擊:New -> Plugin DevKit 竟然不會沒有Action選項,差點把我勸退了,換了了七八個版本IDEA試了都不行!Action選項出不來,過了倆天后,晚上無心嘗試在main文件夾下面新建了一個Java文件,而後在這個java文件上右擊:New -> Plugin DevKit,Action選項出現了!真幾把佛了。。。less
還有個巨坑的問題,在Gradle模式下開發插件,把模板代碼文件放在main文件下、放在src下、放在根目錄下,都獲取不到文件裏面的內容,這個真是坑了我很多時間,搜了不少博客,都發現沒寫這個問題,官方文檔範例看了幾遍也沒發現有啥說明,後來找到了一個三年前的項目,翻了翻代碼發現,全部的資源文件都必須放在resources文件夾下,才能讀取到文件內容。。。我勒個去。。。
插件地址
插件效果
說下插件的功能含義
Model:生成GetX的模式,
Function:功能選擇
首頁,固然是實現一個簡單的計數器,來看GetX怎麼將邏輯層和界面層解耦的
來使用插件生成下簡單文件
來看下生成的默認代碼,默認代碼十分簡單,詳細解釋放在倆種狀態管理裏
import 'package:get/get.dart'; class CounterGetLogic extends GetxController { }
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'logic.dart'; class CounterGetPage extends StatelessWidget { final CounterGetLogic logic = Get.put(CounterGetLogic()); @override Widget build(BuildContext context) { return Container(); } }
當數據源變化時,將自動執行刷新組件的方法
logic層
logic
結尾,這裏就定爲了logic
層,固然這點隨我的意向,寫Event,Controller都可.obs
操做,是說明定義了該變量爲響應式變量,當該變量數值變化時,頁面的刷新方法將自動刷新;基礎類型,List,類均可以加.obs
,使其變成響應式變量class CounterGetLogic extends GetxController { var count = 0.obs; ///自增 void increase() => ++count; }
view層
這地方獲取到Logic層的實例後,就可進行操做了,你們可能會想:WTF,爲何實例的操做放在build方法裏?逗我呢?--------- 實際否則,stl是無狀態組件,說明了他就不會被二次重組,因此實例操做只會被執行一次,並且Obx()方法是能夠刷新組件的,完美解決刷新組件問題了
class CounterGetPage extends StatelessWidget { @override Widget build(BuildContext context) { CounterGetLogic logic = Get.put(CounterGetLogic()); return Scaffold( appBar: AppBar(title: const Text('GetX計數器')), body: Center( child: Obx( () => Text('點擊了 ${logic.count.value} 次', style: TextStyle(fontSize: 30.0)), ), ), floatingActionButton: FloatingActionButton( onPressed: () => logic.increase(), child: const Icon(Icons.add), ), ); } }
固然,也能夠這樣寫
class CounterGetPage extends StatelessWidget { final CounterGetLogic logic = Get.put(CounterGetLogic()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('GetX計數器')), body: Center( child: Obx( () => Text('點擊了 ${logic.count.value} 次', style: TextStyle(fontSize: 30.0)), ), ), floatingActionButton: FloatingActionButton( onPressed: () => logic.increase(), child: const Icon(Icons.add), ), ); } }
Obx()
,這樣能夠愉快的處處寫定點刷新操做了Obx()方法刷新的條件
來看下若是把整個類對象設置成響應類型,如何實現更新操做呢?
只要包裹了該響應類變量的Obx(),都會實行刷新操做
,將整個類設置響應類型,須要結合實際場景使用// model // 咱們將使整個類成爲可觀察的,而不是每一個屬性。 class User() { User({this.name = '', this.age = 0}); String name; int age; } // controller final user = User().obs; //當你須要更新user變量時。 user.update( (user) { // 這個參數是你要更新的類自己。 user.name = 'Jonny'; user.age = 18; }); // 更新user變量的另外一種方式。 user(User(name: 'João', age: 35)); // view Obx(()=> Text("Name ${user.value.name}: Age: ${user.value.age}")) // 你也能夠不使用.value來訪問模型值。 user().name; // 注意是user變量,而不是類變量(首字母是小寫的)。
GetBuilder:這是一個極其輕巧的狀態管理器,佔用資源極少!
class CounterEasyGetLogic extends GetxController { var count = 0; void increase() { ++count; update(); } }
class CounterEasyGetPage extends StatelessWidget { final CounterEasyGetLogic logic = Get.put(CounterEasyGetLogic()); @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('計數器-簡單式')), body: Center( child: GetBuilder<CounterEasyGetLogic>( builder: (logicGet) => Text( '點擊了 ${logicGet.count} 次', style: TextStyle(fontSize: 30.0), ), ), ), floatingActionButton: FloatingActionButton( onPressed: () => logic.increase(), child: const Icon(Icons.add), ), ); } }
分析下:GetBuilder這個方法
Get.put()
生成了CounterEasyGetLogic
對象,GetBuilder會自動查找該對象,因此,就能夠不使用init參數分析
GetBuilder
內部其實是對StatefulWidget的封裝,因此佔用資源極小StreamBuilder
,會消耗必定資源使用場景
StreamBuilder
,必將對內存形成較大的壓力,該狀況下,就要考慮使用簡單狀態管理了跨頁面交互,在複雜的場景中,是很是重要的功能,來看看GetX怎麼實現跨頁面事件交互的
常規代碼
logic
class JumpOneLogic extends GetxController { var count = 0.obs; ///跳轉到跨頁面 void toJumpTwo() { Get.toNamed(RouteConfig.jumpTwo, arguments: {'msg': '我是上個頁面傳遞過來的數據'}); } ///跳轉到跨頁面 void increase() => count++; }
view
class JumpOnePage extends StatelessWidget { /// 使用Get.put()實例化你的類,使其對當下的全部子路由可用。 final JumpOneLogic logic = Get.put(JumpOneLogic()); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: Text('跨頁面-One')), floatingActionButton: FloatingActionButton( onPressed: () => logic.toJumpTwo(), child: const Icon(Icons.arrow_forward_outlined), ), body: Center( child: Obx( () => Text('跨頁面-Two點擊了 ${logic.count.value} 次', style: TextStyle(fontSize: 30.0)), ), ), ); } }
這個頁面就是重點了
logic
GetxController
包含比較完整的生命週期回調,能夠在onInit()
接受傳遞的數據;若是接收的數據須要刷新到界面上,請在onReady
回調裏面接收數據操做,onReady
是在addPostFrameCallback
回調中調用,刷新數據的操做在onReady
進行,能保證界面是初始加載完畢後才進行頁面刷新操做的class JumpTwoLogic extends GetxController { var count = 0.obs; var msg = ''.obs; @override void onReady() { var map = Get.arguments; msg.value = map['msg']; super.onReady(); } ///跳轉到跨頁面 void increase() => count++; }
view
Get.find()
,獲取到了以前實例化GetXController,獲取某個模塊的GetXController後就很好作了,能夠經過這個GetXController去調用相應的事件,也能夠經過它,拿到該模塊的數據!class JumpTwoPage extends StatelessWidget { final JumpOneLogic oneLogic = Get.find(); final JumpTwoLogic twoLogic = Get.put(JumpTwoLogic()); @override Widget build(BuildContext context) { return Scaffold( backgroundColor: Colors.white, appBar: AppBar(title: Text('跨頁面-Two')), floatingActionButton: FloatingActionButton( onPressed: () { oneLogic.increase(); twoLogic.increase(); }, child: const Icon(Icons.add), ), body: Center( child: Column(mainAxisSize: MainAxisSize.min, children: [ //計數顯示 Obx( () => Text('跨頁面-Two點擊了 ${twoLogic.count.value} 次', style: TextStyle(fontSize: 30.0)), ), //傳遞數據 Obx( () => Text('傳遞的數據:${twoLogic.msg.value}', style: TextStyle(fontSize: 30.0)), ), ]), ), ); } }
GetX這種的跨頁面交互事件,真的是很是簡單了,侵入性也很是的低,不須要在主入口配置什麼,在複雜的業務場景下,這樣簡單的跨頁面交互方式,就能實現不少事了
咱們可能會遇到過不少複雜的業務場景,在複雜的業務場景下,單單某個模塊關於變量的初始化操做可能就很是多,在這個時候,若是還將state(狀態層)和logic(邏輯層)寫在一塊兒,維護起來可能看的比較暈,這裏將狀態層和邏輯層進行一個拆分,這樣在稍微大一點的項目裏使用GetX,也能保證結構足夠清晰了!
在這裏就繼續用計數器舉例吧!
此處須要劃分三個結構了:state(狀態層),logic(邏輯層),view(界面層)
這裏使用插件生成下模板代碼
來看下生成的模板代碼
class CounterHighGetState { CounterHighGetState() { ///Initialize variables } }
import 'package:get/get.dart'; import 'state.dart'; class CounterHighGetLogic extends GetxController { final state = CounterHighGetState(); }
import 'package:flutter/material.dart'; import 'package:get/get.dart'; import 'logic.dart'; import 'state.dart'; class CounterHighGetPage extends StatelessWidget { final CounterHighGetLogic logic = Get.put(CounterHighGetLogic()); final CounterHighGetState state = Get.find<CounterHighGetLogic>().state; @override Widget build(BuildContext context) { return Container(); } }
爲何寫成這樣三個模塊,須要把State單獨提出來,請速速瀏覽下方
state
RxInt
,沒有使用var
,使用該變量類型的緣由,此處是將全部的操做都放在構造函數裏面初始化,若是直接使用var
沒有立馬賦值,是沒法推導爲Rx
類型,因此這裏直接定義爲RxInt
,實際很簡單,基礎類型將開頭字母大寫,而後加上Rx
前綴便可var
也是能夠的,可是,使用該響應變量的時候.value
沒法提示,須要本身手寫,因此仍是老老實實的寫明Rx具體類型吧class CounterHighGetState { RxInt count; CounterHighGetState() { count = 0.obs; } }
logic
class CounterHighGetLogic extends GetxController { final state = CounterHighGetState(); ///自增 void increase() => ++state.count; }
view
CounterHighGetLogic
被實例化,因此直接使用Get.find<CounterHighGetLogic>()
就能拿到剛剛實例化的邏輯層,而後拿到state,使用單獨的變量接收下class CounterHighGetPage extends StatelessWidget { final CounterHighGetLogic logic = Get.put(CounterHighGetLogic()); final CounterHighGetState state = Get.find<CounterHighGetLogic>().state; @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('計數器-響應式')), body: Center( child: Obx( () => Text('點擊了 ${state.count.value} 次', style: TextStyle(fontSize: 30.0)), ), ), floatingActionButton: FloatingActionButton( onPressed: () => logic.increase(), child: const Icon(Icons.add), ), ); } }
看了上面的改造,屏幕前的你可能想吐槽了:坑比啊,以前簡簡單單的邏輯層,被拆成倆個,還搞得這麼麻煩,你是猴子請來的逗比嗎?你們先別急着吐槽,當業務過於複雜,state層,也是會維護不少東西的,讓咱們看看下面的一個小栗子,下面實例代碼是不能直接運行的,想看詳細運行代碼,請查看項目:flutter_use
class MainState { ///選擇index - 響應式 RxInt selectedIndex; ///控制是否展開 - 響應式 RxBool isUnfold; ///分類按鈕數據源 List<BtnInfo> list; ///Navigation的item信息 List<BtnInfo> itemList; ///PageView頁面 List<Widget> pageList; PageController pageController; MainState() { //初始化index selectedIndex = 0.obs; //默認不展開 isUnfold = false.obs; //PageView頁面 pageList = [ keepAliveWrapper(FunctionPage()), keepAliveWrapper(ExamplePage()), keepAliveWrapper(Center(child: Container())), ]; //item欄目 itemList = [ BtnInfo( title: "功能", icon: Icon(Icons.bubble_chart), ), BtnInfo( title: "範例", icon: Icon(Icons.opacity), ), BtnInfo( title: "設置", icon: Icon(Icons.settings), ), ]; //頁面控制器 pageController = PageController(); } }
class MainLogic extends GetxController { final state = MainState(); ///切換tab void switchTap(int index) { state.selectedIndex.value = index; } ///是否展開側邊欄 void onUnfold(bool unfold) { state.isUnfold.value = !state.isUnfold.value; } }
class MainPage extends StatelessWidget { final MainLogic logic = Get.put(MainLogic()); final MainState state = Get.find<MainLogic>().state; @override Widget build(BuildContext context) { return BaseScaffold( backgroundColor: Colors.white, body: Row(children: [ ///側邊欄區域 Obx( () => SideNavigation( selectedIndex: state.selectedIndex.value, sideItems: state.itemList, onItem: (index) { logic.switchTap(index); state.pageController.jumpToPage(index); }, isUnfold: state.isUnfold.value, onUnfold: (unfold) { logic.onUnfold(unfold); }, ), ), ///Expanded佔滿剩下的空間 Expanded( child: PageView.builder( physics: NeverScrollableScrollPhysics(), itemCount: state.pageList.length, itemBuilder: (context, index) => state.pageList[index], controller: state.pageController, ), ) ]), ); } }
從上面能夠看出,state層裏面的狀態已經較多了,當某些模塊涉及到大量的:提交表單數據,跳轉數據,展現數據等等,state層的代碼會至關的多,相信我,真的是很是多,一旦業務發生變動,還要常常維護修改,就蛋筒了
在複雜的業務下,將狀態層(state)和業務邏輯層(logic)分開,絕對是個明智的舉動
GetX實現了一套用起來十分簡單的路由管理,可使用一種極其簡單的方式導航,也可使用命名路由導航
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( home: MainPage(), ); } }
路由的相關使用
//跳轉新頁面 Get.to(SomePage());
這裏是推薦使用命名路由導航的方式
下面說明下,如何使用
void main() { runApp(MyApp()); } class MyApp extends StatelessWidget { @override Widget build(BuildContext context) { return GetMaterialApp( initialRoute: RouteConfig.main, getPages: RouteConfig.getPages, ); } }
RouteConfig類
class RouteConfig{ ///主頁面 static final String main = "/"; ///dialog頁面 static final String dialog = "/dialog"; ///bloc計數器模塊 static final String counter = "/counter"; ///測試佈局頁面 static final String testLayout = "/testLayout"; ///演示SmartDialog控件 static final String smartDialog = "/smartDialog"; ///Bloc跨頁面傳遞事件 static final String spanOne = "/spanOne"; static final String spanTwo = "/spanOne/spanTwo"; ///GetX 計數器 跨頁面交互 static final String counterGet = "/counterGet"; static final String jumpOne = "/jumpOne"; static final String jumpTwo = "/jumpOne/jumpTwo"; ///別名映射頁面 static final List<GetPage> getPages = [ GetPage(name: main, page: () => MainPage()), GetPage(name: dialog, page: () => Dialog()), GetPage(name: counter, page: () => CounterPage()), GetPage(name: testLayout, page: () => TestLayoutPage()), GetPage(name: smartDialog, page: () => SmartDialogPage()), GetPage(name: spanOne, page: () => SpanOnePage()), GetPage(name: spanTwo, page: () => SpanTwoPage()), GetPage(name: counterGet, page: () => CounterGetPage()), GetPage(name: jumpOne, page: () => JumpOnePage()), GetPage(name: jumpTwo, page: () => JumpTwoPage()), ]; }
請注意命名路由,只須要在api結尾加上Named
便可,舉例:
詳細Api介紹,下面內容來自GetX的README文檔,進行了相關整理
Get.to(NextScreen()); Get.toNamed("/NextScreen");
Get.back();
Get.off(NextScreen()); Get.offNamed("/NextScreen");
Get.offAll(NextScreen()); Get.offAllNamed("/NextScreen");
只要發送你想要的參數便可。Get在這裏接受任何東西,不管是一個字符串,一個Map,一個List,甚至一個類的實例。
Get.to(NextScreen(), arguments: 'Get is the best'); Get.toNamed("/NextScreen", arguments: 'Get is the best');
在你的類或控制器上:
print(Get.arguments); //print out: Get is the best
var data = await Get.to(Payment()); var data = await Get.toNamed("/payment");
Get.back(result: 'success'); // 並使用它,例: if(data == 'success') madeAnything();
// 默認的Flutter導航 Navigator.of(context).push( context, MaterialPageRoute( builder: (BuildContext context) { return HomePage(); }, ), ); // 使用Flutter語法得到,而不須要context。 navigator.push( MaterialPageRoute( builder: (_) { return HomePage(); }, ), ); // get語法 Get.to(HomePage());
動態網頁連接
保證經過url傳參數到頁面
裏Get提供高級動態URL,就像在Web上同樣。Web開發者可能已經在Flutter上想要這個功能了,Get也解決了這個問題。
Get.offAllNamed("/NextScreen?device=phone&id=354&name=Enzo");
在你的controller/bloc/stateful/stateless類上:
print(Get.parameters['id']); // out: 354 print(Get.parameters['name']); // out: Enzo
你也能夠用Get輕鬆接收NamedParameters。
void main() { runApp( GetMaterialApp( initialRoute: '/', getPages: [ GetPage( name: '/', page: () => MyHomePage(), ), GetPage( name: '/profile/', page: () => MyProfile(), ), //你能夠爲有參數的路由定義一個不一樣的頁面,也能夠爲沒有參數的路由定義一個不一樣的頁面,可是你必須在不接收參數的路由上使用斜槓"/",就像上面說的那樣。 GetPage( name: '/profile/:user', page: () => UserProfile(), ), GetPage( name: '/third', page: () => Third(), transition: Transition.cupertino ), ], ) ); }
發送命名路由數據
Get.toNamed("/profile/34954");
在第二個頁面上,經過參數獲取數據
print(Get.parameters['user']); // out: 34954
如今,你須要作的就是使用Get.toNamed()來導航你的命名路由,不須要任何context(你能夠直接從你的BLoC或Controller類中調用你的路由),當你的應用程序被編譯到web時,你的路由將出如今URL中。
引流了,手動滑稽.jpg
解決方案
狀態管理