使用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
解决方案
状态管理