GetX介绍
pub-web.flutter-io.cn/packages?so…
关于Get
github中文文档:github.com/jonataslaw/…
-
GetX 是 Flutter 上的一个轻量且强大的解决方案:高性能的状态管理、智能的依赖注入和便捷的路由管理。
-
GetX 有3个基本原则:
- 性能: GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。如果你感兴趣,这里有一个性能测试。
- 效率: GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
- 结构: GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护。
-
GetX 并不臃肿,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
-
Getx有一个庞大的生态系统,能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。 通过Get Server 可以在你的后端完全重用你在前端写的代码。
此外,通过Get CLI,无论是在服务器上还是在前端,整个开发过程都可以完全自动化。
当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组 件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用Flutter中的状态管理来管 理统一的状态(数据),实现不同组件之间的传值和数据共享。
现在Flutter的状态管理方案很多,redux、bloc、state、provider、Getx、riverpod、MobX。
provider是官方提供的状态管理解决方案,主要功能就是状态管理。Getx是第三方的状态管理插件, 不仅具有状态管理的功能,还具有路由管理、主题管理、国际化多语言管理、Obx局部更新、网络请 求、数据验证等功能,相比其他状态管理插件Getx 简单、功能强大并且高性能。
使用GetX
将 GetX 添加到你的 pubspec.yaml 文件中。
dependencies:
get: ^4.3.8
在需要用到的文件中导入,它将被使用。
import 'package:get/get.dart';
Snackbar
如果想在应用程序中触发某些特定的事件后,需要弹出一则快捷消息,那么使用Snackbar则是最佳的选择
Snackbar基本使用
第一步:应用程序入口设置
当我们导入依赖后,在应用程序顶层把GetMaterialApp 作为顶层,如下所示
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: Scaffold(
appBar: AppBar(title: Text("GetX Title"),),
),
);
}
}
第二步:调用snackbar
我们可以通过Get.snackbar() 来显示 snackbar ,如下所示
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: Scaffold(
appBar: AppBar(
title: Text("GetX Title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Get.snackbar("Snackbar 标题", "欢迎使用Snackbar");
},
child: Text("显示 Snackbar"))
],
),
),
),
);
}
}
Snackbar属性和说明
总共38个属性,
| 字段 | 属性 | 描述 |
|---|---|---|
| title | String | 弹出的标题文字 |
| message | String | 弹出的消息文字 |
| colorText | Color | title和message的文字颜色 |
| duration | Duration | Snackbar弹出的持续时间(默认3秒) |
| instantInit | bool | 当false可以把snackbar 放在initState,默认true |
| snackPosition | SnackPosition | 弹出时的位置,有两个选项【TOP,BOTTOM】默认TOP |
| titleText | Widget | 弹出标题的组件,设置该属性会导致title属性失效 |
| messageText | Widget | 弹出消息的组件,设置该属性会导致messageText属性失效 |
| icon | Widget | 弹出时图标,显示在title和message的左侧 |
| shouldIconPulse | bool | 弹出时图标是否闪烁,默认false |
| maxWidth | double | Snackbar最大的宽度 |
| margin | EdgeInsets | Snackbar外边距,默认zero |
| padding | EdgeInsets | Snackbar内边距,默认EdgeInsets.all(16) |
| borderRadius | double | 边框圆角大小,默认15 |
| borderColor | Color | 边框的颜色,必须设置borderWidth,否则无效果 |
| borderWidth | double | 边框的线条宽度 |
| backgroundColor | Color | Snackbar背景颜色,默认Colors.grey.withOpacity(0.2) |
| leftBarIndicatorColor | Color | 左侧指示器的颜色 |
| boxShadows | List | Snackbar阴影颜色 |
| backgroundGradient | Gradient | 背景的线性颜色 |
| mainButton | TextButton | 主要按钮,一般显示发送、确认按钮 |
| onTap | OnTap | 点击Snackbar事件回调 |
| isDismissible | bool | 是否开启Snackbar手势关闭,可配合dismissDirection使用 |
| showProgressIndicator | bool | 是否显示进度条指示器,默认false |
| dismissDirection | SnackDismissDirection | Snackbar关闭的方向 |
| progressIndicatorController | AnimationController | 进度条指示器的动画控制器 |
| progressIndicatorBackgroundColor | Color | 进度条指示器的背景颜色 |
| progressIndicatorValueColor | Animation | 进度条指示器的背景颜色,Animation |
| snackStyle | SnackStyle | Snackbar是否会附加到屏幕边缘 |
| forwardAnimationCurve | Curve | Snackbar弹出的动画,默认Curves.easeOutCirc |
| reverseAnimationCurve | Curve | Snackbar消失的动画,默认Curves.easeOutCirc |
| animationDuration | Duration | Snackbar弹出和小时的动画时长,默认1秒 |
| barBlur | double | Snackbar背景的模糊度 |
| overlayBlur | double | 弹出时的毛玻璃效果值,默认0 |
| snackbarStatus | SnackbarStatusCallback | Snackbar弹出或消失时的事件回调(即将打开、已打开、即将关闭、已关闭) |
| overlayColor | Color | 弹出时的毛玻璃的背景颜色 |
| userInputForm | Form | 用户输入表单 |
底部弹出:
自定义弹出内容:
Dialog
Dialog 底层其实是对AlertDialog进行了封装, 一般用于二次确认的弹出框,比如当点击某个按钮提交资料时,需要用户二次确认,以防止误操作。
Dialog使用
第一步:应用程序入口设置
当我们导入依赖后,在应用程序顶层把GetMaterialApp 作为顶层,如下所示
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/DialogExample/DialogExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: DialogExample(),
);
}
}
第二步:调用Dialog
我们可以通过Get.defaultDialog() 来显示 dialog ,如下所示
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class DialogExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Get.defaultDialog();
},
child: Text("显示 Dialog"))
],
),
),
);
}
}
Dialog属性和说明
总共25个属性
| 字段 | 属性 | 描述 |
|---|---|---|
| title | String | 弹出的标题,默认(Alert) |
| titlePadding | EdgeInsetsGeometry | 标题的内边距,默认(EdgeInsets.all(8)) |
| titleStyle | TextStyle | 标题的样式 |
| middleText | String | 中间内容区域显示的文字 |
| middleTextStyle | TextStyle | 中间内容区域显示的文字样式 |
| content | Widget | 弹出的内容,该值设置后middleText将无效 |
| contentPadding | EdgeInsetsGeometry | 内容的内边距,默认(EdgeInsets.all(8)) |
| onConfirm | VoidCallback | 确认按钮回调 |
| onCancel | VoidCallback | 取消按钮回调 |
| onCustom | VoidCallback | 自定义按钮回调 |
| cancelTextColor | Color | 取消按钮文字的颜色 |
| confirmTextColor | Color | 确认按钮文字的颜色 |
| textConfirm | String | 确认按钮的文字 |
| textCancel | String | 取消按钮的文字 |
| textCustom | String | 自定义按钮的文字 |
| confirm | Widget | 确认按钮的组件 |
| cancel | Widget | 取消按钮的组件 |
| custom | Widget | 自定义按钮的组件 |
| backgroundColor | Color | 弹出框的背景颜色 |
| barrierDismissible | bool | 是否可以通过点击背景关闭弹窗 |
| buttonColor | Color | 按钮的文字颜色,根据按钮类型来设定不同的位置 |
| radius | double | 弹出框的圆角大小,默认20 |
| actions | List | 增加额外的子组件 |
| onWillPop | WillPopCallback | 拦截关闭之前做一些操作 |
| navigatorKey | GlobalKey | 用于打开对话框的key |
默认的内容文字:
自定义样式:
BottomSheet
BottomSheet 是底部弹出的一个组件,常用于单选、验证码二次校验弹窗等,GetX的BottomSheet底部弹出是自定义通过路由push的方法实现底部弹窗的一个效果。
第一步:应用程序入口设置
当我们导入依赖后,在应用程序顶层把GetMaterialApp 作为顶层,如下所示
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/DialogExample/DialogExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: DialogExample(),
);
}
}
第二步:调用BottomSheet
我们可以通过Get.bottomSheet() 来显示 BottomSheet ,如下所示
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class BottomSheetExample extends StatelessWidget {
GlobalKey<NavigatorState> _navKey = GlobalKey();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Title"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(onPressed: () {
Get.bottomSheet(
Container(
child: Wrap(
children: [
ListTile(
leading: Icon(Icons.wb_sunny_outlined),
title: Text("白天模式"),
onTap: () {
Get.changeTheme(ThemeData.light());
},
),
ListTile(
leading: Icon(Icons.wb_sunny),
title: Text("黑夜模式"),
onTap: () {
Get.changeTheme(ThemeData.dark());
},
)
],
),
)
);
}, child: Text("Bottom Sheet"))
],
),
),
);
}
}
切换白天主题颜色: Get.changeTheme(ThemeData.light());
切换黑夜主题颜色: Get.changeTheme(ThemeData.dark());
BottomSheet属性和说明
| 字段 | 属性 | 描述 |
|---|---|---|
| bottomsheet | Widget | 弹出的Widget组件 |
| backgroundColor | Color | bottomsheet的背景颜色 |
| elevation | double | bottomsheet的阴影 |
| persistent | bool | 是否添加到路由中 |
| shape | ShapeBorder | 边框形状,一般用于圆角效果 |
| clipBehavior | Clip | 裁剪的方式 |
| barrierColor | Color | 弹出层的背景颜色 |
| ignoreSafeArea | bool | 是否忽略安全适配 |
| isScrollControlled | bool | 是否支持全屏弹出,默认false |
| useRootNavigator | bool | 是否使用根导航 |
| isDismissible | bool | 点击背景是否可关闭,默认ture |
| enableDrag | bool | 是否可以拖动关闭,默认true |
| settings | RouteSettings | 路由设置 |
| enterBottomSheetDuration | Duration | bottomsheet进入时的动画时间 |
| exitBottomSheetDuration | Duration | bottomsheet退出时的动画时间 |
Navigation路由管理
使用GetX 进行路由跳转非常的简单,只需要调用Get.to()即可进行路由跳转,而系统的路由跳转需要写八行代码,这是不能忍受的事情,而且涉及到跳转动画设置 、动画时长定义、动画曲线 等设置那就更加的复杂,而GetX为我们封装了Navigation,无需context可进行跳转,并且能很方便的使用跳转动画等。
应用程序入口设置:
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/NavigationForNamedExample/NavigationForNamedExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
initialRoute: "/",
defaultTransition: Transition.zoom,
getPages: [
GetPage(name: "/", page: () => MyApp()),
GetPage(name: "/home", page: () => Home()),
GetPage(name: "/my", page: () => My(), transition: Transition.rightToLeft)
],
home: NavigationForNamedExample(),
);
}
}
通过 Get.to方法进行路由跳转:
通过 Get.toNamed方法进行路由跳转:
以前:
Navigator.pushNamed(context, "/login");
通过 Get.back方法进行路由跳转:
以前:
Navigator.of(context).pop();
通过 Get.agruments获取路由传参
Get.offAll(); 返回到根页面
以前:
Navigator.of(context).pushAndRemoveUntil(
MaterialPageRoute(builder: (BuildContext context) {
return const Tabs(index: 4);
}), (route) => false);
使用Getx后:
Get.offAll( const Tabs(index: 4));
Get.off(NextScreen());
进入下一个页面,但没有返回上一个页面的选项(用于闪屏页,登录页面等)。
Get.off(NextScreen());
路由中间件 路由拦截
新建shopMiddleware.dart:
import 'package:flutter/cupertino.dart';
import 'package:get/get.dart';
class ShopMiddleWare extends GetMiddleware {
@override
// 优先级越低越先执行
int? get priority => 1;
// 当被调用路由的页面被搜索时,这个函数将被调用。它将RouteSettings作为重定向的结果。或者给它null,就没有重定向了。
@override
RouteSettings redirect(String ? route){
final authService = Get.find<AuthService>();
return authService.authed.value ? null : RouteSettings(name: '/login')
}
// 在调用页面时,创建任何东西之前,这个函数会先被调用。 您可以使用它来更改页面的某些内容或给它一个新页面。
@override
GetPage onPageCalled(GetPage page) {
final authService = Get.find<AuthService>();
return page.copyWith(title: 'Welcome ${authService.UserName}');
}
// 这个函数将在绑定初始化之前被调用。 在这里,您可以更改此页面的绑定。
@override
List<Bindings> onBindingsStart(List<Bindings> bindings) {
final authService = Get.find<AuthService>();
if (authService.isAdmin) {
bindings.add(AdminBinding());
}
return bindings;
}
// 这个函数将在绑定初始化之后被调用。 在这里,您可以在创建绑定之后和创建页面widget之前执行一些操作。
@override
GetPageBuilder onPageBuildStart(GetPageBuilder page) {
print('bindings are ready');
return page;
}
// OnPageBuilt
// 这个函数将在GetPage.page调用后被调用,并给出函数的结果,并获取将要显示的widget。
// OnPageDispose
// 这个函数将在处理完页面的所有相关对象(Controllers, views, ...)之后被调用。
}
GetPage配置路由
return GetMaterialApp(
...
initialRoute: "/",
defaultTransition: Transition.rightToLeftWithFade,
getPages: [
GetPage(name: "/", page: () => const Tabs()),
...
GetPage(
name: "/shop",
page: () => const ShopPage(),
middlewares: [ShopMiddleWare()]
),
],
);
flutter状态管理的底层原理
-
InheritedWidget
-
Notification
-
StreamBuilder
InheritedWidget
专门负责Widget树中数据共享的功能型Widget,如Provider、scoped_model就是基于它开发。
Provider就是对InheritedWidget的包装,只是让InheritedWidget用起来更加简单且高可复用。我们也知道它有一些缺点
- 容易造成不必要的刷新,使用selector进行优化
- 不支持跨页面(route)的状态,意思是跨树,如果不在一个树中,我们无法获取
- 数据是不可变的,必须结合StatefulWidget、ChangeNotifier或者Steam使用
InheritedWidget和React中的context功能类似,可以实现跨组件数据的传递。
定义一个共享数据的InheritedWidget,需要继承自InheritedWidget
- 这里定义了一个of方法,该方法通过context开始去查找祖先的HYDataWidget(可以查看源码查找过程)
- updateShouldNotify方法是对比新旧HYDataWidget,是否需要对更新相关依赖的Widget
class HYDataWidget extends InheritedWidget {
finalint counter;
HYDataWidget({this.counter, Widget child}): super(child: child);
static HYDataWidget of(BuildContext context) {
return context.dependOnInheritedWidgetOfExactType();
}
@override
bool updateShouldNotify(HYDataWidget oldWidget) {
returnthis.counter != oldWidget.counter;
}
}
创建HYDataWidget,并且传入数据(这里点击按钮会修改数据,并且重新build)
class HYHomePage extends StatefulWidget {
@override
_HYHomePageState createState() => _HYHomePageState();
}
class _HYHomePageState extends State<HYHomePage> {
int data = 100;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("InheritedWidget"),
),
body: HYDataWidget(
counter: data,
child: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: <Widget>[
HYShowData(showdata:data)
],
),
),
),
floatingActionButton: FloatingActionButton(
child: Icon(Icons.add),
onPressed: () {
setState(() {
data++;
});
},
),
);
}
}
在某个Widget中使用共享的数据,并且监听
Provider的基本使用
Provider是目前官方推荐的全局状态管理工具,由社区作者Remi Rousselet 和 Flutter Team共同编写。
引入依赖:
创建一个ChangeNotifier来保存我们的状态
- 这里我们可以使用继承自ChangeNotifier,也可以使用混入,这取决于概率是否需要继承自其它的类
- 我们使用一个私有的_counter,并且提供了getter和setter
- 在setter中我们监听到_counter的改变,就调用notifyListeners方法,通知所有的Consumer进行更新
在Widget Tree中插入ChangeNotifierProvider
我们需要在Widget Tree中插入ChangeNotifierProvider,以便Consumer可以获取到数据:
- 将ChangeNotifierProvider放到了顶层,这样方便在整个应用的任何地方可以使用CounterProvider
在首页中使用Consumer引入和修改状态
- 引入位置一:在body中使用Consumer,Consumer需要传入一个builder回调函数,当数据发生变化时,就会通知依赖数据的Consumer重新调用builder方法来构建;
- 引入位置二:在floatingActionButton中使用Consumer,当点击按钮时,修改CounterNotifier中的counter数据;
通过Provider.of的方式来使用
MultiProvider使用多个状态管理数据
使用Selector进行优化,shouldRebuild返回false不进行rebuild
Notification
与InheritedWidget正好相反,InheritedWidget是从上往下传递数据,Notification是从下往上,但两者都在自己的Widget树中传递,无法跨越树传递。
它是Flutter中跨层数据共享的一种机制,它不是widget,它提供了dispatch方法,来让我们沿着context对应的Element节点向上逐层发送通知
- 定义TestNotification通知的实现
- WidgetNotification 负责通知结果,通过RaisedButton的点击事件,将数据a传递出去,通过Notification提供的dispatch方法向上传递
- WidgetListener通过Widget NotificationListener来监听数据变化,最终通过setState变更数据
- WidgetNotification 实例化了两次,一次在NotificationListener的树内部,一个在NotificationListener的外部,经过测试发现,在外部的WidgetNotification并不能通知到内容变化。
class TestNotification extends Notification {
final int test;
TestNotification(this.test);
}
var a = 0;
// ignore: must_be_immutable
class WidgetNotification extends StatelessWidget {
final String btnText;
WidgetNotification({Key key, this.btnText}) : super(key: key);
@override
Widget build(BuildContext context) {
return Container(
child: RaisedButton(
child: Text(btnText),
onPressed: () {
var b = ++a;
debugPrint(b.toString());
TestNotification(b).dispatch(context);
},
),
);
}
}
class WidgetListener extends StatefulWidget {
@override
_WidgetListenerState createState() => _WidgetListenerState();
}
class _WidgetListenerState extends State {
int _test = 1;
@override
Widget build(BuildContext context) {
return Container(
child: Column(
children: [
NotificationListener(
child: Column(
children: [
Text("监听$_test"),
WidgetNotification(btnText: "子Widget",)
],
),
onNotification: (TestNotification notification) {
setState(() {
_test = notification.test;
});
return true;
},
),
WidgetNotification(btnText: "非子Widget",)
],
),
);
}
}
StreamBuilder
Stream就是个事件流,有点类似于安卓开发中的RxJava,它允许我们从一端发射一个事件,从另外一端去监听事件的变化,通过Stream我们可以在Flutter上设计出基于事件流驱动的响应式代码逻辑。
Stream 是 Dart 中的一个核心部分,它用于接收异步事件序列,这些事件可以包括数据消息或错误。
StreamBuilder 则是一个 widget,它能够根据最新的 Stream快照 (snapshot) 来重建自己。
StreamBuild从字面意思来讲是数据流构建,是一种基于数据流的订阅管理。Stream可以接受任何类型的数据,值、事件、对象、集合、映射、错误、甚至是另一个Stream,通过StreamController中的sink作为入口,往Stream中插入数据,然后通过你的自定义监听StreamSubscription对象,接受数据变化的通知。如果你需要对输出数据进行处理,可以使用StreamTransformer,它可以对输出数据进行过滤、重组、修改、将数据注入其他流等等任何类型的数据操作。
Stream有两种类型:单订阅Stream和广播Stream。单订阅Stream只允许在该Stream的整个生命周期内使用单个监听器,即使第一个subscription被取消了,你也没法在这个流上监听到第二次事件;而广播Stream允许任意个数的subscription,你可以随时随地给它添加subscription,只要新的监听开始工作流,它就能收到新的事件。
单订阅类型实例
import 'dart:async';
void main() {
// 初始化一个单订阅的Stream controller
final StreamController ctrl = StreamController();
// 初始化一个监听
final StreamSubscription subscription = ctrl.stream.listen((data) => print('$data'));
// 往Stream中添加数据
ctrl.sink.add('my name');
ctrl.sink.add(1234);
ctrl.sink.add({'a': 'element A', 'b': 'element B'});
ctrl.sink.add(123.45);
// StreamController用完后需要释放
ctrl.close();
}
广播类stream
import 'dart:async';
void main() {
// 初始化一个int类型的广播Stream controller
final StreamController<int> ctrl = StreamController<int>.broadcast();
// 初始化一个监听,同时通过transform对数据进行简单处理
final StreamSubscription subscription = ctrl.stream
.where((value) => (value % 2 == 0))
.listen((value) => print('$value'));
// 往Stream中添加数据
for(int i=1; i<11; i++){
ctrl.sink.add(i);
}
// StreamController用完后需要释放
ctrl.close();
}
使用streamBuild
StreamBuilder<T>(
key: ...可选...
stream: ...需要监听的stream...
initialData: ...初始数据,尽量不要填null...
builder: (BuildContext context, AsyncSnapshot<T> snapshot){
if (snapshot.hasData){
return ...基于snapshot.hasData返回的控件
}
return ...没有数据的时候返回的控件
},
)
下面是一个模仿官方自带demo“计数器”的一个例子,使用了StreamBuilder,而不需要任何setState:
import 'dart:async';
import 'package:flutter/material.dart';
class CounterPage extends StatefulWidget {
@override
_CounterPageState createState() => _CounterPageState();
}
class _CounterPageState extends State<CounterPage> {
int _counter = 0;
//步骤1:初始化一个StreamController<任何数据> 简单的可以扔一个int,string,开发中经常扔一个网络请求的model进去,具体看你使用场景了。
final StreamController<int> _streamController = StreamController<int>();
@override
void dispose(){
//步骤2.关流,不管流会消耗资源,同时会引起内存泄漏
_streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('Stream version of the Counter App')),
body: Center(
//步骤3.使用StreamBuilder构造器
child: StreamBuilder<int>( // 监听Stream,每次值改变的时候,更新Text中的内容
stream: _streamController.stream,
initialData: _counter,
builder: (BuildContext context, AsyncSnapshot<int> snapshot){
return Text('You hit me: ${snapshot.data} times');
}
),
),
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: (){
// 每次点击按钮,更加_counter的值,同时通过Sink将它发送给Stream;
// 每注入一个值,都会引起StreamBuilder的监听,StreamBuilder重建并刷新counter
//步骤4.往StreamBuilder里添加流,数据变了,就用通知小部件
_streamController.sink.add(++_counter);
},
),
);
}
}
点击“关注”变为“已关注”
1.这个item是StatefulWidget,点击“关注”,然后setstate(){}
2.使用其他的状态管理去实现。
3.使用 StreamBuild 实现。
import 'dart:async';
import 'package:easy_alert/easy_alert.dart';
import 'package:flutter/material.dart';
import 'package:hongka_flutter/app/Manager/IO/hk_request.dart';
import 'package:hongka_flutter/app/Manager/api/ConfigApi.dart';
import 'package:hongka_flutter/app/Modules/basemodel/focuseItemModel.dart';
import 'package:hongka_flutter/app/Modules/home/info_organization.dart';
//我会省略部分代码,并且注释使用步骤
//步骤一:使用 StatefulWidget,为何要用StatefulWidget?待会解释
class FollowsItem extends StatefulWidget {
FocuseItemModel focusItemModel;
String focusType; //不为空就是关注界面,隐藏关注按钮
int index;
String studentId;
FollowsItem(
{Key key,
this.focusItemModel,
this.focusType,
this.index,
this.studentId});
@override
_FollowsItemState createState() => _FollowsItemState();
}
class _FollowsItemState extends State<FollowsItem> {
FocuseItemModel focusItemModel;
String focusType; //不为空就是关注界面,隐藏关注按钮
String headerImage;
double screenWidth, marginLeft = 15.0, marginRight = 15.0, marginAll = 30.0;
int index;
String studentId;
//步骤二:声明StreamController
StreamController<FocuseItemModel> _streamController;
@override
void initState() {
super.initState();
this.focusItemModel = this.widget.focusItemModel;
this.focusType = this.widget.focusType;
this.index = this.widget.index;
this.studentId = this.widget.studentId;
//步骤三实现 StreamController<FocuseItemModel>,FocuseItemModel是我的实体类
_streamController = StreamController<FocuseItemModel>.broadcast();
//步骤四将数据添加到 _streamController
_streamController.sink.add(focusItemModel);
}
@override
void dispose() {
//步骤五:关流
_streamController.close();
super.dispose();
}
@override
Widget build(BuildContext context) {
return GestureDetector(
onTap: () {
_clickItem(context);
},
child: Container(
.....省略无关UI代码
child: Stack(
children: <Widget>[
.....省略无关UI代码
Positioned(
right: 0,
top: 0,
child: Offstage(
offstage: !(focusType == null),
child: GestureDetector(
onTap: () {
//已关注return
if (focusItemModel.focusStatus == 1) {
return;
}
//未关注进行网络请求
focusOrganizationRequest(focusItemModel.positionId);
},
//步骤五:使用StreamBuilder构建
child: StreamBuilder<FocuseItemModel>(
stream: _streamController.stream,//数据流
initialData: focusItemModel,//初始值
builder: (BuildContext context,
AsyncSnapshot<FocuseItemModel> snapshot) {
return Image.asset(
(snapshot?.data?.focusStatus ?? 0) == 1
? 'images/view/recommend_flowered.png'
: 'images/view/recommend_flower.png',
width: 54,
height: 40,
);
}),
)),
)
],
),
),
);
}
///关注红广号
Future focusOrganizationRequest(String positionId) async {
String url = 'xxxx';
var data = {
'studentId': studentId,
'positionId': positionId,
};
var options = await HK_Request().getRequestOptions();
var response = await HK_Request()
.post(url, context, isShowLoading: true, data: data, options: options);
print(response.toString());
if (response['status'] == 200) {
//步骤六:改变model值,并将model 扔到_streamController里,此时数据改变,通知小部件,重新构建
focusItemModel.focusStatus = 1;
_streamController.sink.add(focusItemModel);
Alert.toast(context, '关注成功!',
position: ToastPosition.center, duration: ToastDuration.short);
print('关注红广号成功' + response.toString());
} else {
print('关注红广号错误');
Alert.toast(context, response['msg'],
position: ToastPosition.center, duration: ToastDuration.short);
}
}
GetX Obx响应式状态管理
介绍
响应式编程可能会让很多人感到陌生,因为它很复杂,但是GetX将响应式编程变得非常简单。
- 你不需要创建StreamControllers.
- 你不需要为每个变量创建一个StreamBuilder。
- 你不需要为每个状态创建一个类。
- 你不需要为一个初始值创建一个get。
使用 Get 的响应式编程就像使用 setState 一样简单。
定义Obx变量的三种方式
// 第一种 使用 Rx{Type}
final name = RxString('');
final isLogged = RxBool(false);
final count = RxInt(0);
final balance = RxDouble(0.0);
final items = RxList<String>([]);
final myMap = RxMap<String, int>({});
// 第二种是使用 Rx,规定泛型 Rx<Type>。
final name = Rx<String>('');
final isLogged = Rx<Bool>(false);
final count = Rx<Int>(0);
final balance = Rx<Double>(0.0);
final number = Rx<Num>(0)
final items = Rx<List<String>>([]);
final myMap = Rx<Map<String, int>>({});
//自定义类 - 可以是任何类
final user = Rx<User>();
// 第三种更实用、更简单、更可取的方法,只需添加 .obs 作为value的属性。
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
inal items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 自定义类 - 可以是任何类
final user = User().obs;
计数器案例
第一步:应用程序入口设置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/ObxCountExample/ObxCountExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: ObxCountExample(),
);
}
}
第二步:声明Rx变量以及改变计数器的方法
RxInt count = RxInt(0);
// var count = Rx<double>(0);
// var count = 0.obs;
void increment() {
count++;
}
第三步:使用Obx监听值的改变
Obx(() => Text(
"count的值为: $count",
style: TextStyle(color: Colors.red, fontSize: 30),
)),
完整代码
import 'dart:ffi';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class ObxCountExample extends StatelessWidget {
RxInt count = RxInt(0);
// var count = Rx<double>(0);
// var count = 0.obs;
void increment() {
count++;
}
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Obx---计数器"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Obx(() => Text(
"count的值为: $count",
style: TextStyle(color: Colors.red, fontSize: 30),
)),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () {
increment();
},
child: Text("点我加1"))
],
),
),
);
}
}
效果展示
自定义类案例
第一步:应用程序入口设置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/ObxCustomClassExample/ObxCustomClassExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: ObxCustomClassExample(),
);
}
}
第二步:创建Teacher类
import 'package:get/get.dart';
class Teacher {
// rx 变量
var name = "Jimi".obs;
var age = 18.obs;
// 构造函数创建
// var name;
// var age;
// Teacher({this.name, this.age});
}
第三步:监听Teacher状态改变
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/ObxCustomClassExample/Teacher.dart';
import 'package:get/get.dart';
class ObxCustomClassExample extends StatelessWidget {
var teacher = Teacher();
// final teacher = Teacher(name: "Jimi", age: 18).obs;
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Obx---自定义类"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Obx(() => Text(
"我的名字是 ${teacher.name.value}",
style: TextStyle(color: Colors.red, fontSize: 30),
)),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () {
teacher.name.value = teacher.name.value.toUpperCase();
// teacher.update((val) {
// teacher.value.name = teacher.value.name.toString().toUpperCase();
// });
},
child: Text("转换为大写"))
],
),
),
);
}
}
效果展示
GetxController依赖注入状态管理
在实际的项目开发过程中,我们不可能把UI代码、业务逻辑都放在一起处理,这样对项目的架构、代码的可读性、后期的维护将会是致命的,在GetX为我们提供了GetxController,GetxController主要的作用是用于UI代码与业务逻辑分离开来。
这里主要讲解使用GetxController动态获取数据的三种方式以及更新数据的方式。
第一步:应用程序入口设置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerExample/GetXControllerExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: GetXControllerExample(),
);
}
}
第二步:定义控制器继承自GetxController
import 'package:flutter_getx_example/ObxCustomClassExample/Teacher.dart';
import 'package:get/get.dart';
class MyController extends GetxController {
// 第一种
// var teacher = Teacher();
//
// void convertToUpperCase() {
// teacher.name.value = teacher.name.value.toUpperCase();
// }
// 第二种
// var teacher = Teacher(name: "Jimi", age: 18).obs;
// void convertToUpperCase() {
// teacher.update((val) {
// teacher.value.name = teacher.value.name.toString().toUpperCase();
// });
// }
// 第三种
var teacher = Teacher();
void convertToUpperCase() {
teacher.name.value = teacher.name.value.toUpperCase();
update();
}
}
第三步:实例化控制器并使用
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerExample/MyController.dart';
import 'package:get/get.dart';
class GetXControllerExample extends StatelessWidget {
// 第一种
MyController myController = Get.put(MyController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Obx---GetXController"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
// 第一种
// Obx(() => Text(
// "我的名字是 ${myController.teacher.name}",
// style: TextStyle(color: Colors.red, fontSize: 30),
// )),
// 第二种
// GetX<MyController>(
// init: MyController(),
// builder: (controller) {
// return Text(
// "我的名字是 ${controller.teacher.name}",
// style: TextStyle(color: Colors.green, fontSize: 30),
// );
// },
// ),
// 第三种
GetBuilder<MyController>(
init: myController,
builder: (controller) {
return Text(
"我的名字是 ${controller.teacher.name}",
style: TextStyle(color: Colors.green, fontSize: 30),
);
},
),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () {
// 第一种
myController.convertToUpperCase();
// 第二种
// Get.find<MyController>().convertToUpperCase();
},
child: Text("转换为大写"))
],
),
),
);
}
}
效果展示
GetxController事件监听
这里主要讲解GetxController的事件监听,包括监听单个值、多个值等。
第一步:应用程序入口设置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerWorkersExample/GetXControllerWorkersExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: GetXControllerWorkersExample(),
);
}
}
第二步:定义控制器继承自GetxController
import 'package:get/get.dart';
class WorkersController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
第三步:重写onInit并监听事件
@override
void onInit() {
// TODO: implement onInit
// 监听count的值,当它发生改变的时候调用
ever(count, (callback) => print("ever----$count"));
// 监听多个值,当它们发生改变的时候调用
everAll([count], (callback) => print("everAll----$count"));
// count值改变时调用,只执行一次
once(count, (callback) => print("once----$count"));
// 用户停止打字时1秒后调用,主要是防DDos
debounce(count, (callback) => print("debounce----$count"));
// 忽略3秒内的所有变动
interval(count, (callback) => print("interval----$count"));
super.onInit();
}
第四步:实例化控制器并使用
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerWorkersExample/WorkersConroller.dart';
import 'package:get/get.dart';
class GetXControllerWorkersExample extends StatelessWidget {
WorkersController workersController = Get.put(WorkersController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetXWorkersController"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () => workersController.increment(),
child: Text("增加")
),
Padding(
padding: EdgeInsets.all(16),
child: TextField(
onChanged: (val) {
workersController.increment();
},
),
)
],
),
),
);
}
}
效果展示
控制台输出结果
flutter: ever----1
flutter: everAll----1
flutter: once----1
[GETX] Worker [once] called
flutter: debounce----1
flutter: interval----1
flutter: ever----2
flutter: everAll----2
flutter: debounce----2
flutter: interval----2
flutter: ever----3
flutter: everAll----3
flutter: ever----4
flutter: everAll----4
flutter: ever----5
flutter: everAll----5
flutter: interval----5
flutter: debounce----5
GetxController生命周期
这里主要讲解GetxController的生命周期,包括初始化、加载完成、销毁等。
第一步:应用程序入口设置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerLifecycleMethodsExample/GetXControllerLifecycleMethodExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: GetXControllerLifecycleMethodExample(),
);
}
}
第二步:定义控制器继承自GetxController
import 'package:get/get.dart';
class MyLifecycleController extends GetxController {
var count = 0;
void increment() async {
await Future.delayed(Duration(milliseconds: 3000));
count++;
update();
}
void cleanTask() {
print("清除了任务");
}
}
第三步:重写GetxController生命周期方法
@override
void onInit() {
// TODO: implement onInit
print("初始化");
super.onInit();
}
@override
void onReady() {
// TODO: implement onReady
print("加载完成");
super.onReady();
}
@override
void onClose() {
// TODO: implement onClose
print("控制器被释放");
super.onClose();
}
第四步:实例化控制器并使用
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerLifecycleMethodsExample/MyLifecycleController.dart';
import 'package:get/get.dart';
class GetXControllerLifecycleMethodExample extends StatelessWidget {
MyLifecycleController myLifecycleController = Get.put(MyLifecycleController());
@override
Widget build(BuildContext context) {
print("build");
return Scaffold(
appBar: AppBar(
title: Text("GetXControllerLifecycleMethod"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GetBuilder<MyLifecycleController>(
initState: (data) => myLifecycleController.increment(),
dispose: (_) => myLifecycleController.cleanTask(),
builder: (controller) {
return Text(
"计数器值为: ${myLifecycleController.count}",
style: TextStyle(color: Colors.green, fontSize: 30),
);
},
),
],
),
),
);
}
}
效果展示
控制台输出结果
flutter: 初始化
[GETX] Instance "MyLifecycleController" has been created
[GETX] Instance "MyLifecycleController" has been initialized
[GETX] Instance "GetMaterialController" has been created
[GETX] Instance "GetMaterialController" has been initialized
flutter: build
flutter: 加载完成
flutter: build
flutter: build
GetxController UniqueID
我们在开发的过程中会碰到一种情况,就是多个地方引用了同一个属性,但我只想单独更新某一个地方,那么就可以用UniqueID来进行区分。
第一步:应用程序入口设置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerUniqueIDExample/GetXControllerUniqueIDExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: GetXControllerUniqueIDExample(),
);
}
}
第二步:定义控制器继承自GetxController,并且定义uniqueID
import 'package:get/get.dart';
class CountController extends GetxController {
var count = 0;
void increment() {
count++;
update(['jimi_count']);
}
}
第三步:实例化控制器并使用
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerUniqueIDExample/CountConroller.dart';
import 'package:get/get.dart';
class GetXControllerUniqueIDExample extends StatelessWidget {
CountController countController = Get.put(CountController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Obx---GetXController"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
GetBuilder<CountController>(
builder: (controller) {
return Text(
"计数器值为: ${controller.count}",
style: TextStyle(color: Colors.red, fontSize: 30),
);
},
),
GetBuilder<CountController>(
id: 'jimi_count',
builder: (controller) {
return Text(
"计数器值为: ${controller.count}",
style: TextStyle(color: Colors.green, fontSize: 30),
);
},
),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () => countController.increment(),
child: Text("增加"))
],
),
),
);
}
}
效果展示
GetX还提供很多创建实例的方法,可根据不同的业务来进行创建
- Get.put(): 不使用控制器实例也会被创建
- Get.lazyPut(): 懒加载方式创建实例,只有在使用时才创建
- Get.putAsync():
Get.put()的异步版版本 - Get.create(): 每次使用都会创建一个新的实例
我们来看一下代码演示
第一步:应用程序入口配置
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/DependecyInjectionExample/DependecyInjectionExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: DependecyInjectionExample(),
);
}
}
第二步:创建控制器
import 'package:flutter_getx_example/ObxCustomClassExample/Teacher.dart';
import 'package:get/get.dart';
class MyController extends GetxController {
var teacher = Teacher();
void convertToUpperCase() {
teacher.name.value = teacher.name.value.toUpperCase();
}
}
第三步:实例化控制器并使用
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerExample/MyController.dart';
import 'package:get/get.dart';
class DependecyInjectionExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
// 即使不使用控制器实例也会被创建
// tag将用于查找具有标签名称的实例
// 控制器在不使用时被处理,但如果永久为真,则实例将在整个应用程序中保持活动状态
MyController myController = Get.put(MyController(), permanent: true);
// MyController myController = Get.put(MyController(), tag: "instancel", permanent: true);
// 实例将在使用时创建
// 它类似于'permanent',区别在于实例在不被使用时被丢弃
// 但是当它再次需要使用时,get 将重新创建实例
// Get.lazyPut(()=> MyController());
// Get.lazyPut(()=> MyController(), tag: "instancel");
// Get.put 异步版本
// Get.putAsync<MyController>(() async => await MyController());
// 每次都将返回一个新的实例
// Get.create<MyController>(() => MyController());
return Scaffold(
appBar: AppBar(
title: Text("GetXController"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
// 实例使用的tag创建
// Get.find<MyController>(tag: 'instancel');
Get.find<MyController>();
},
child: Text("别点我"))
],
),
),
);
}
}
Get Service
这个类就像一个 GetxController,它共享相同的生命周期onInit()、onReady()、onClose()。 但里面没有 "逻辑"。它只是通知GetX的依赖注入系统,这个子类不能从内存中删除。所以如果你需要在你的应用程序的生命周期内对一个类实例进行绝对的持久化,那么就可以使用GetxService。
第一步:创建Service
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
class Service extends GetxService {
Future<void> getCounter() async {
SharedPreferences prefs = await SharedPreferences.getInstance();
int count = (prefs.getInt("counter") ?? 0) + 1;
print("count 的值为: $count");
await prefs.setInt("counter", count);
}
}
第二步:初始化Service
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXServiceExample/GetXServiceExample.dart';
import 'package:flutter_getx_example/GetXServiceExample/Service.dart';
import 'package:get/get.dart';
/// 初始化服务
Future<void> main() async {
await initServices();
runApp(MyApp());
}
Future<void> initServices() async {
print("初始化服务");
await Get.putAsync(() async => await Service());
print("所有服务启动");
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
title: "GetX",
home: GetXServiceExample(),
);
}
}
第三步:调用Service
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXServiceExample/Service.dart';
import 'package:get/get.dart';
class GetXServiceExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Service"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Get.find<Service>().getCounter();
},
child: Text("点我加1"))
],
),
),
);
}
}
GetX Binding
在我们使用GetX状态管理器的时候,往往每次都是用需要手动实例化一个控制器,这样的话基本页面都需要实例化一次,这样就太麻烦了,而Binding 能解决上述问题,可以在项目初始化时把所有需要进行状态管理的控制器进行统一初始化,接下来看代码演示:
第一步:声明需要进行的绑定控制器类
import 'package:flutter_getx_example/GetXBindingExample/controller/BindingHomeController.dart';
import 'package:flutter_getx_example/GetXBindingExample/controller/BindingMyController.dart';
import 'package:get/get.dart';
class AllControllerBinding implements Bindings {
@override
void dependencies() {
// TODO: implement dependencies
Get.lazyPut<BindingMyController>(() => BindingMyController());
Get.lazyPut<BindingHomeController>(() => BindingHomeController());
}
}
import 'package:get/get.dart';
class BindingHomeController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
import 'package:get/get.dart';
class BindingMyController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
第二步:在项目启动时进行初始化绑定
绑定的方式有多种,在不同的情况下有不同的使用方式,这里介绍一种,如果需要更加详细的介绍,观看视频将会是最佳的选择。
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXBindingExample/binding/AllControllerBinding.dart';
import 'package:flutter_getx_example/GetXBindingExample/GetXBindingExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// GetX Binding
return GetMaterialApp(
title: "GetX",
initialBinding: AllControllerBinding(),
home: GetXBindingExample(),
);
}
}
第三步:在页面中使用状态管理器
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXBindingExample/BHome.dart';
import 'package:flutter_getx_example/GetXBindingExample/binding/BHomeControllerBinding.dart';
import 'package:flutter_getx_example/GetXBindingExample/controller/BindingMyController.dart';
import 'package:get/get.dart';
class GetXBindingExample extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetXBinding"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Obx(() => Text(
"计数器的值为 ${Get.find<BindingMyController>().count}",
style: TextStyle(color: Colors.red, fontSize: 30),
)),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () {
Get.find<BindingMyController>().increment();
},
child: Text("点击加1")
),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () {
Get.to(BHome());
// Get.toNamed("/bHome");
// Get.to(BHome(), binding: BHomeControllerBinding());
},
child: Text("跳转去首页")
),
],
),
),
);
}
}
通过路由配置为单独的页面状态管理数据:
效果展示
新闻案例
网络数据请求、模型处理、GetXController,用MVC模型来实现新闻案例
效果展示
请求网络数据
我们新建一个ApiService.dart用于请求网络数据,该数据是一个新闻列表的数据。
import 'dart:convert';
import 'package:dio/dio.dart';
import 'package:flutter_getx_example/GetXApiDataExample/MovieModule/Models/MovieModel.dart';
class ApiService {
static Future<List<MovieModel>> fetchMovie() async {
var response = await Dio().get("http://apis.juhe.cn/fapig/douyin/billboard?type=hot_video&size=50&key=9eb8ac7020d9bea6048db1f4c6b6d028");
if (response.statusCode == 200) {
var jsonString = response.data['result'];
return movieModelFromJson(json.encode(response.data["result"]));
}
}
}
定义模型类
我们新建一个MovieModel.dart用来对网络请求回来的数据进行模型转换。
// To parse this JSON data, do
//
// final movieModel = movieModelFromJson(jsonString);
import 'dart:convert';
List<MovieModel> movieModelFromJson(String str) => List<MovieModel>.from(json.decode(str).map((x) => MovieModel.fromJson(x)));
String movieModelToJson(List<MovieModel> data) => json.encode(List<dynamic>.from(data.map((x) => x.toJson())));
class MovieModel {
MovieModel({
this.title,
this.shareUrl,
this.author,
this.itemCover,
this.hotValue,
this.hotWords,
this.playCount,
this.diggCount,
this.commentCount,
});
String title;
String shareUrl;
String author;
String itemCover;
int hotValue;
String hotWords;
int playCount;
int diggCount;
int commentCount;
factory MovieModel.fromJson(Map<String, dynamic> json) => MovieModel(
title: json["title"],
shareUrl: json["share_url"],
author: json["author"],
itemCover: json["item_cover"],
hotValue: json["hot_value"],
hotWords: json["hot_words"],
playCount: json["play_count"],
diggCount: json["digg_count"],
commentCount: json["comment_count"],
);
Map<String, dynamic> toJson() => {
"title": title,
"share_url": shareUrl,
"author": author,
"item_cover": itemCover,
"hot_value": hotValue,
"hot_words": hotWords,
"play_count": playCount,
"digg_count": diggCount,
"comment_count": commentCount,
};
}
使用控制器对数据进行处理
我们对请求回来的网络数据转化为Model,并在请求前增加一个loading,当网络数据请求回来的时候关闭loading,我们来看一下代码
import 'package:flutter_getx_example/GetXApiDataExample/ApiModule/ApiService.dart';
import 'package:flutter_getx_example/GetXApiDataExample/MovieModule/Models/MovieModel.dart';
import 'package:get/get.dart';
class MovieController extends GetxController {
var isLoading = true.obs;
// ignore: deprecated_member_use
var movieList = List<MovieModel>().obs;
@override
void onInit() {
// TODO: implement onInit
fetchMovie();
super.onInit();
}
void fetchMovie() async {
try {
isLoading(true);
var movie = await ApiService.fetchMovie();
if (movie != null) {
movieList.assignAll(movie);
}
} finally {
isLoading(false);
}
}
}
在视图层展示列表数据
前面我们对网络请求、模型、数据处理进行了处理,我们最终的目的是需要把数据展示到页面上,那我们接着来看一下视图的代码
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXApiDataExample/MovieModule/Controller/MovieController.dart';
import 'package:get/get.dart';
class MovieListView extends StatelessWidget {
final MovieController movieController = Get.put(MovieController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Movie"),
),
body: Obx(() {
if (movieController.isLoading.value) {
return Center(
child: CircularProgressIndicator(),
);
} else {
return ListView.builder(
itemCount: movieController.movieList.length,
itemBuilder: (context, index) {
return Column(
children: [
Row(
children: [
Container(
width: 100,
height: 120,
margin: EdgeInsets.all(10),
child: ClipRRect(
borderRadius: BorderRadius.circular(6),
child: Image.network(
movieController.movieList[index].itemCover,
width: 150,
height: 100,
fit: BoxFit.fill,
// color: Colors.orange,
// colorBlendMode: BlendMode.color,
),
),
),
Flexible(
child: Column(
mainAxisAlignment: MainAxisAlignment.start,
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Text(
movieController.movieList[index].author,
style: TextStyle(
color: Colors.black45,
fontSize: 16
),
)
],
),
),
],
),
Container(
color: Colors.grey.shade300,
height: 2,
)
],
);
},
);
}
}),
);
}
国际化配置
在我们使用系统自带MaterialApp来实现国际化配置,需要进行很多配置,而且还需要手动去依赖第三方组件,而使用GetX来实现国际化配置,你只需要一行代码即可实现切换,接下来我们看一下具体实现。
第一步:应用程序入口配置
- translations: 国际化配置文件
- locale: 设置默认语言,不设置的话为系统当前语言
- fallbackLocale: 配置错误的情况下,使用的语言
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/InternationalizationExample/InternationalizationExample.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
/// 国际化配置
return GetMaterialApp(
title: "GetX",
translations: Messages(),
locale: Locale('zh', 'CN'), //设置默认语言
fallbackLocale: Locale("zh", "CN"), // 在配置错误的情况下,使用的语言
home: InternationalizationExample(),
);
}
}
第二步:创建国际化类
需要继承自Translations并重写keys方法。
import 'package:get/get.dart';
class Messages extends Translations {
@override
// TODO: implement keys
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': "你好, 世界"
},
'en_US': {
'hello': 'hello world'
}
};
}
第三步:创建控制器类,用于切换语言
import 'dart:ui';
import 'package:get/get.dart';
class MessagesController extends GetxController {
void changeLanguage(String languageCode, String countryCode) {
var locale = Locale(languageCode, countryCode);
Get.updateLocale(locale);
}
}
第四步:实例化控制器并使用
import 'package:flutter/material.dart';
import 'package:flutter_getx_example/GetXControllerWorkersExample/WorkersConroller.dart';
import 'package:flutter_getx_example/InternationalizationExample/MessagesCnotroller.dart';
import 'package:get/get.dart';
class InternationalizationExample extends StatelessWidget {
MessagesController messagesController = Get.put(MessagesController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Internationalization"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Text('hello'.tr, style: TextStyle(color: Colors.pink, fontSize: 30)),
ElevatedButton(
onPressed: () => messagesController.changeLanguage('zh', "CN"),
child: Text("切换到中文")
),
SizedBox(height: 20,),
ElevatedButton(
onPressed: () => messagesController.changeLanguage('en', "US"),
child: Text("切换到英文")
),
],
),
),
);
}
}
效果展示
GetUtils
GetUtils 是getx为我们提供一些常用的工具类库,在我们的项目开发中提供了很多便捷的方法。包括值是否为空、是否是数字、是否是视频、图片、音频、PPT、Word、APK、邮箱、手机号码、日期、MD5、SHA1等等。
判断是否是邮箱、手机号、IPV4地址为例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetXUtilsExample extends StatelessWidget {
var textFieldController = TextEditingController();
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("GetX Utils"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
crossAxisAlignment: CrossAxisAlignment.center,
children: [
Padding(
padding: EdgeInsets.all(20),
child: TextField(
controller: textFieldController,
),
),
SizedBox(height: 10,),
Padding(
padding: EdgeInsets.all(10),
child: ElevatedButton(
child: Text("判断是否是邮箱"),
onPressed: () async {
if (GetUtils.isEmail(textFieldController.text)) {
Get.snackbar("正确", "恭喜你, 完全正确", backgroundColor: Colors.greenAccent);
} else {
Get.snackbar(
"邮箱错误",
"请输入正确的邮箱",
backgroundColor: Colors.pink
);
}
},
),
),
Padding(
padding: EdgeInsets.all(10),
child: ElevatedButton(
child: Text("判断是否是手机号"),
onPressed: () async {
if (GetUtils.isPhoneNumber(textFieldController.text)) {
Get.snackbar("正确", "恭喜你, 完全正确", backgroundColor: Colors.greenAccent);
} else {
Get.snackbar(
"手机号错误",
"请输入正确的手机号",
backgroundColor: Colors.pink
);
}
},
),
),
Padding(
padding: EdgeInsets.all(10),
child: ElevatedButton(
child: Text("判断是否是IPv4"),
onPressed: () async {
if (GetUtils.isIPv4(textFieldController.text)) {
Get.snackbar("正确", "恭喜你, 完全正确", backgroundColor: Colors.greenAccent);
} else {
Get.snackbar(
"地址错误",
"请输入正确的IPv4地址",
backgroundColor: Colors.pink
);
}
},
),
),
],
),
),
);
}
}
判断是否是邮箱效果展示
判断是否是手机号码效果展示
判断是否是IPV4地址效果展示
GetStorage
创建一个 GetStorage 实例来开始保存和检索数据。可以在代码开始时初始化或在需要存储或提取数据时进行初始化。 您可以根据需要创建多个实例。
final box = GetStorage();
现在,我们可以使用 GetStorage 实例来存储数据。 对于存储操作,GetStorage 提供了 write() 和 writeIfNull() 方法。
write(key, value) // 存储值是“value”的键值对到本地。
writeIfNull(key, value) //当该键不存在时,存储一个键为“key”,值为“value”的键值对到本地。
以下是示例代码: (例如在登录后存储用户数据以进行后续身份验证)
// 在用户登录成功后存储用户数据
box.write('username', 'johndoe');
box.write('email', 'johndoe@example.com');
同样,检索数据也很容易。 GetStorage 提供了 read() 和 readIfNull() 方法,以检索存储的值。
read(key)//检索给定键的值。如果键不存在,则返回null。
readIfNull(key, value) //与 read() 方法类似,但在不存在键时返回的是“value”。
以下是示例代码:
final username = box.read('username');
final email = box.readIfNull('email', 'no email available');
GetX 其他高级API
// 给出当前页面的args。
Get.arguments
//给出以前的路由名称
Get.previousRoute
// 给出要访问的原始路由,例如,rawRoute.isFirst()
Get.rawRoute
// 允许从GetObserver访问Rounting API。
Get.routing
// 检查 snackbar 是否打开
Get.isSnackbarOpen
// 检查 dialog 是否打开
Get.isDialogOpen
// 检查 bottomsheet 是否打开
Get.isBottomSheetOpen
// 删除一个路由。
Get.removeRoute()
//反复返回,直到表达式返回真。
Get.until()
// 转到下一条路由,并删除所有之前的路由,直到表达式返回true。
Get.offUntil()
// 转到下一个命名的路由,并删除所有之前的路由,直到表达式返回true。
Get.offNamedUntil()
//检查应用程序在哪个平台上运行。
GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia
//检查设备类型
GetPlatform.isMobile
GetPlatform.isDesktop
//所有平台都是独立支持web的!
//你可以知道你是否在浏览器内运行。
//在Windows、iOS、OSX、Android等系统上。
GetPlatform.isWeb
// 相当于.MediaQuery.of(context).size.height,
//但不可改变。
Get.height
Get.width
// 提供当前上下文。
Get.context
// 在你的代码中的任何地方,在前台提供 snackbar/dialog/bottomsheet 的上下文。
Get.contextOverlay
// 注意:以下方法是对上下文的扩展。
// 因为在你的UI的任何地方都可以访问上下文,你可以在UI代码的任何地方使用它。
// 如果你需要一个可改变的高度/宽度(如桌面或浏览器窗口可以缩放),你将需要使用上下文。
context.width
context.height
// 让您可以定义一半的页面、三分之一的页面等。
// 对响应式应用很有用。
// 参数: dividedBy (double) 可选 - 默认值:1
// 参数: reducedBy (double) 可选 - 默认值:0。
context.heightTransformer()
context.widthTransformer()
/// 类似于 MediaQuery.of(context).size。
context.mediaQuerySize()
/// 类似于 MediaQuery.of(context).padding。
context.mediaQueryPadding()
/// 类似于 MediaQuery.of(context).viewPadding。
context.mediaQueryViewPadding()
/// 类似于 MediaQuery.of(context).viewInsets。
context.mediaQueryViewInsets()
/// 类似于 MediaQuery.of(context).orientation;
context.orientation()
///检查设备是否处于横向模式
context.isLandscape()
///检查设备是否处于纵向模式。
context.isPortrait()
///类似于MediaQuery.of(context).devicePixelRatio。
context.devicePixelRatio()
///类似于MediaQuery.of(context).textScaleFactor。
context.textScaleFactor()
///查询设备最短边。
context.mediaQueryShortestSide()
///如果宽度大于800,则为真。
context.showNavbar()
///如果最短边小于600p,则为真。
context.isPhone()
///如果最短边大于600p,则为真。
context.isSmallTablet()
///如果最短边大于720p,则为真。
context.isLargeTablet()
///如果当前设备是平板电脑,则为真
context.isTablet()
///根据页面大小返回一个值<T>。
///可以给值为:
///watch:如果最短边小于300
///mobile:如果最短边小于600
///tablet:如果最短边(shortestSide)小于1200
///desktop:如果宽度大于1200
context.responsiveValue<T>()
GetX Cli
安装Cli脚手架
我们通过命令flutter pub global activate get_cli 进行脚手架的全局安装
设置环境变量
下面路径添加到Path环境变量里面
F:\flutter_windows\flutter_windows_3.3.0-stable\flutter.pub-cache\bin F:\flutter_windows\flutter_windows_3.0.5\flutter\bin\cache\dart-sdk\bin
通过get命令校验是否成功安装
我们可以通过get create project来进行创建工程
输入工程名称、公司域名、选择iOS语言、选择Android语言、是否空安全、是否校验,选完开始创建工程
通过get create page:login来快速创建一个页面,这个页面有controller、view、binding、routes等配置,结构是Getx_pattern
get install安装依赖
第一种:直接安装最新版本
JMdeMacBook-Pro:getx_example jm$ get install dio
Installing package "dio" …
✓ 'Package: dio installed!
Running `flutter pub get` …
$ flutter pub get
Running "flutter pub get" in getx_example... 2,656ms
Time: 5815 Milliseconds
第二种:同时安装多个包
JMdeMacBook-Pro:getx_example jm$ get install path dio
Installing package "path" …
✓ 'Package: path installed!
Installing package "dio" …
✓ 'Package: dio installed!
Running `flutter pub get` …
$ flutter pub get
Running "flutter pub get" in getx_example... 732ms
Time: 7146 Milliseconds
第三种:安装自定版本的包
JMdeMacBook-Pro:getx_example jm$ get install prodiver:5.0.0
Installing package "prodiver" …
✓ 'Package: prodiver installed!
我们可以通过get install flutter_launcher_icons --dev安装开发时所依赖的包
JMdeMacBook-Pro:getx_example jm$ get install flutter_launcher_icons --dev
The [--dev] is not necessary
Installing package "flutter_launcher_icons" …
✓ 'Package: flutter_launcher_icons installed!
Cli卸载包
第一种:卸载某个安装包
JMdeMacBook-Pro:getx_example jm$ get remove http
Removing package: "http"
Package: http is not installed in this application
Running `flutter pub get` …
$ flutter pub get
Running "flutter pub get" in getx_example... 772ms
Time: 2641 Milliseconds
第二种:同时卸载多个包
JMdeMacBook-Pro:getx_example jm$ get remove dio path
Removing package: "dio"
✓ Package: dio removed!
Removing package: "path"
✓ Package: path removed!
Running `flutter pub get` …
$ flutter pub get
Running "flutter pub get" in getx_example... 1,085ms
Time: 3142 Milliseconds
Cli更新脚手架
我们可以通过get update对脚手架进行更新
JMdeMacBook-Pro:getx_example jm$ get update
Latest version of get_cli already installed
Time: 3315 Milliseconds
Cli查看版本号
我们可以通过get -v 查看当前脚手架的版本号
JMdeMacBook-Pro:getx_example jm$ get -v
░██████╗░███████╗████████╗ ░█████╗░██╗░░░░░░██╗
██╔════╝░██╔════╝╚══██╔══╝ ██╔══██╗██║░░░░░░██║
██║░░██╗░█████╗░░░░░██║░░░ ██║░░╚═╝██║░░░░░░██║
██║░░╚██╗██╔══╝░░░░░██║░░░ ██║░░██╗██║░░░░░░██║
╚██████╔╝███████╗░░░██║░░░ ╚█████╔╝███████╗ ██║
░╚═════╝░╚══════╝░░░╚═╝░░░ ░╚════╝░╚══════╝ ╚═╝
Version: 1.6.0
Time: 148 Milliseconds
Cli帮助
当我们忘记了命令的使用方式,我们可以通过get help进行查看帮助。
JMdeMacBook-Pro:getx_example jm$ get help
List available commands:
create:
controller: Generate controller
page: Use to generate pages
project: Use to generate new project
provider: Create a new Provider
screen: Generate new screen
view: Generate view
generate:
locales: Generate translation file from json files
model: generate Class model from json
help: Show this help
init: generate the chosen structure on an existing project:
install: Use to install a package in your project (dependencies):
remove: Use to remove a package in your project (dependencies):
sort: Sort imports and format dart files
update: To update GET_CLI
--version: Shows the current CLI version'
Time: 94 Milliseconds