Flutter_学习记录_状态管理之GetX
1. 状态管理、Flutter Getx介绍
1.1 状态管理
通俗的讲:当我们想在多个页面(组件/Widget)之间共享状态(数据),或者一个页面(组件/Widget)中的多个子组件之间共享状态(数据),这个时候我们就可以用Flutter中的状态管理来管理统一的状态(数据),实现不同组件之间的传值和数据共享。
现在Flutter的状态管理方案很多:redux、bloc、state、provider、Getx
。
provider
是官方提供的状态管理解决方案,主要功能就是状态管理。
Getx
是第三方的状态管理插件,不仅具有状态管理的功能,还具有路由管理、主题管理、国际化多语言管理、Obx局部更新、网络请求、数据验证等功能,相比其他状态管理插件Getx 简单、功能强大并且高性能。
1.2、Flutter Getx介绍
GetX
是 Flutter 上的一个轻量且强大的解决方案,Getx
为我们提供了高性能的状态管理、智能的依赖注入和便捷的路由管理。
GetX 有3个基本原则:
- 性能: GetX 专注于性能和最小资源消耗。GetX 打包后的apk占用大小和运行时的内存占用与其他状态管理插件不相上下。
- 效率: GetX 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
- 结构: GetX 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护
GetX 并不臃肿,却很轻量。如果只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
Getx有一个庞大的生态系统,能够在Android、iOS、Web、Mac、Linux、Windows和你的服务器上用同样的代码运行。 通过Get Server 可以在你的后端完全重用你在前端写的代码。
2. GetX 的简单使用
2.1 项目中集成GetX
官网:https://pub.dev/packages/get
中文文档:https://github.com/jonataslaw/getx/blob/master/README.zh-cn.md
添加 GetX 的插件
导入头文件
import 'package:get/get.dart';
在
mian.dart
的文件中,将主入口的MaterialApp
修改成GetMaterialApp
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
// `MaterialApp` 修改成`GetMaterialApp`
return GetMaterialApp(
debugShowCheckedModeBanner: true,
home: Home(),
theme: ThemeData(
appBarTheme: AppBarTheme(
backgroundColor: Colors.yellow,
)
),
);
}
}
2.2 显示GetX 默认的弹框defaultDialog
void _getXDefaultDialog() {
Get.defaultDialog(
title: "默认弹窗",
middleText: "弹窗的内容",
cancel: ElevatedButton(onPressed: (){
Get.back();
}, child: Text("取消")),
confirm: ElevatedButton(onPressed: (){
Get.back();
}, child: Text("确认")),
radius: 15.0
);
}
Dialog属性和说明
2.3 显示GetX Snackbar
void _getXSnackbar() {
Get.snackbar(
"提示标题",
"提示内容",
snackPosition: SnackPosition.BOTTOM,
borderRadius: 10.0
);
}
Snackbar属性和说明
2.4 显示GetX BottomSheet
void _getXBottomSheet() {
Get.bottomSheet(
Container(
color: Get.isDarkMode ? Colors.white12 : Colors.white,
height: 200,
child: Column(
children: [
ListTile(
onTap: (){
// 改变主题模式
Get.changeTheme(ThemeData.light());
Get.back();
},
leading: Icon(Icons.sunny_snowing),
title: Text("白天模式", style: TextStyle(color: Get.isDarkMode ? Colors.white : Colors.black),),
),
ListTile(
onTap: (){
// 改变主题模式
Get.changeTheme(ThemeData.dark());
Get.back();
},
leading: Icon(Icons.dark_mode),
title: Text("黑夜模式", style: TextStyle(color: Get.isDarkMode ? Colors.white : Colors.black)),
)
],
),
)
);
}
BottomSheet属性和说明
2.5 用GetX 修改主题模式
// 将主题修改成黑夜模式
Get.changeTheme(ThemeData.dark());
// 将主题修改成白天模式
Get.changeTheme(ThemeData.light());
3. GetX 的路由管理
3.1 Flutter Getx 配置路由以及动画
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: true,
// home: Home(),
theme: ThemeData(
appBarTheme: AppBarTheme(
backgroundColor: Colors.yellow, // 设置导航栏颜色为蓝色
)
),
// 设置初始化页面
initialRoute: "/",
// 设置路由信息
getPages:[
GetPage(name: "/", page: () => Home()),
GetPage(name: "/login", page: () => GetLoginDemo()),
GetPage(name: "/register", page: () => GetRegisterDemo()),
GetPage(name: "/shop", page: () => GetShopDemo()),
GetPage(name: "/shopMiddle", page: () => GetShopDemo(), middlewares: [GetShopmiddlewareDemo()]),
] ,
// 设置跳转动画
defaultTransition: Transition.rightToLeft,
);
}
}
代码解读:
getPages
是一个数组,里面放着GetPage
类型的对象,name
表示路由的名字,page
表示跳转的页面initialRoute
表示初始化的页面defaultTransition
表示页面跳转的动画效果
3.2 GetX 跳转的常见方法:
调用
to
方法切换路由
Get.to(Home());
调用
Get.toNamed()
跳转到命名路由
// 不带参数的跳转
Get.toNamed("/login");
// 带参数的跳转
Get.toNamed("/shop",arguments: {
"id":20
});
Getx 路由跳转传值以及接受数据
路由配置
getPages:[
GetPage(name: "/shop", page: () => GetShopDemo())
]
跳转传值
Get.toNamed("/shop", arguments: {
"id": 111111
});
Get.back();
返回到上一级页面
Get.back();
Get.offAll();
返回到根
Get.offAll(Home());
进入下一个页面,但没有返回上一个页面的选项(用于闪屏页,登录页面等):
Get.off(NextScreen());
3.3 Getx 中间件配置
新建一个
Get_ShopMiddleware_demo.dart
文件
import 'dart:math';
import 'package:flutter/src/widgets/navigator.dart';
import 'package:get/get.dart';
class GetShopmiddlewareDemo extends GetMiddleware {
// 重写跳转页面,例如点击购物,但是如果没有登录,就直接跳转登录页面;登录了就跳转商品页面
RouteSettings? redirect(String? route) {
print("redirect");
int randomIndex = Random().nextInt(3);
if (randomIndex == 1) {
return super.redirect(route);
} else {
return RouteSettings(name: "/login");
}
}
}
GetPage配置路由,在
middlewares
中添加中间件
// 设置路由信息
getPages:[
GetPage(name: "/shopMiddle", page: () => GetShopDemo(), middlewares: [GetShopmiddlewareDemo()]),
]
使用
Get.toNamed("/shopMiddle", arguments: {
"id": 111111
});
4.Flutter Getx 状态管理
4.1 状态管理
目前,Flutter有几种状态管理器。但是,它们中的大多数都涉及到使用ChangeNotifier
来更新widget,
这对于中大型应用的性能来说是一个很糟糕的方法。在Flutter的官方文档中查看到,ChangeNotifier
应该使用1个或最多2个监听器,这使得它们实际上无法用于任何中等或大型应用。
Get 并不是比任何其他状态管理器更好或更差,而是说分析这些要点以及下面的要点来选择只用Get,还是与其他状态管理器结合使用。
Get不是其他状态管理器的敌人,因为Get是一个微框架,而不仅仅是一个状态管理器,既可以单独使用,也可以与其他状态管理器结合使用。
Get有两个不同的状态管理器:响应式状态管理器、简单的状态管理器。
4.2 响应式状态管理器的使用
这部分的写作,参考的是这个文章:https://juejin.cn/post/7020598013986865182
4.2.1 声明响应式变量
- 使用
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
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>();
3、
这种更实用、更简单、更可取的方法,只需添加 .obs 作为value的属性。(推荐使用)
final name = ''.obs;
final isLogged = false.obs;
final count = 0.obs;
final balance = 0.0.obs;
final number = 0.obs;
final items = <String>[].obs;
final myMap = <String, int>{}.obs;
// 自定义类 - 可以是任何类
final user = User().obs;
4.2.2 自定义类的使用
第一种自定义类的使用:
定义包含Rx属性的自定义类
// 1. 定义一个包含Rx属性的类型
class Person {
RxString userName;
RxInt age;
Person({required this.userName, required this.age});
}
// 2. 声明变量
final Person _person = Person(userName: "猪猪".obs, age: 20.obs);
// 3. 使用
ListTile(
// 用 Obx() 来更新局部的UI
title: Obx((){
return Text("person-姓名:${_person.userName}");
}),
),
// 4. 改变数据:改变Person 的姓名字段
_person.userName.value = "${_person.userName} + 拼接$_count";
第二种自定义类的使用:
常规属性的自定义类
// 1. 定义一个常规属性的自定义类
class Animal {
String kindName;
int count;
Animal({required this.kindName, required this.count});
}
// 2. 声明变量
final Rx<Animal> _animal = Animal(kindName: "小猫", count: 1).obs;
// 3. 使用
ListTile(
// 用 Obx() 来更新局部的UI
title: Obx((){
return Text("animal-动物名:${_animal.value.kindName}");
}),
)
// 4. 改变数据:改变Animal 的种类字段
_animal.value.kindName = "${_animal.value.kindName} + 拼接$_count";
_animal.refresh();
4.2.3 完整案例
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetDatastatusDemo extends StatefulWidget {
const GetDatastatusDemo({super.key});
State<GetDatastatusDemo> createState() => _GetDatastatusDemoState();
}
class _GetDatastatusDemoState extends State<GetDatastatusDemo> {
// 1.1 定义int类型的数据
final RxInt _count = 0.obs;
// 2.1 定义String类型的数据
final RxString _titleString = "这是字符串".obs;
// 3.1 定义List<String> 类型的数据
final List<String> _listString = ["1", "2"].obs;
// 4.1 定义一个内部带有obs数据的模型
final Person _person = Person(userName: "猪猪".obs, age: 20.obs);
// 5.1 定义一个常规的模型
final Rx<Animal> _animal = Animal(kindName: "小猫", count: 1).obs;
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("数据的状态管理"),
),
body: ListView(
children: [
ListTile(
// 1.2 用 Obx() 来更新局部的UI
title: Obx((){
return Text("count: ${_count.value}");
}),
),
ListTile(
// 2.2 用 Obx() 来更新局部的UI
title: Obx((){
return Text("字符串:$_titleString");
}),
),
ListTile(
// 3.2 用 Obx() 来更新局部的UI
title: Obx((){
return Text("字符串:${_listString.toString()}");
}),
),
ListTile(
// 4.2 用 Obx() 来更新局部的UI
title: Obx((){
return Text("person-姓名:${_person.userName}");
}),
),
ListTile(
// 5.2 用 Obx() 来更新局部的UI
title: Obx((){
return Text("animal-动物名:${_animal.value.kindName}");
}),
),
],
),
floatingActionButton: FloatingActionButton(
onPressed: (){
// 1.3 改变数据
_count.value ++;
// 2.3 改变数据
_titleString.value = "$_titleString + 拼接$_count";
// 3.3 改变数据
_listString.add("拼接$_count");
// 4.3 改变Person 的姓名字段
_person.userName.value = "${_person.userName} + 拼接$_count";
// 5.3 改变Animal 的种类字段
_animal.value.kindName = "${_animal.value.kindName} + 拼接$_count";
_animal.refresh();
},
child: Icon(Icons.add),
),
);
}
}
//-------定义一个内部变量为obs类型的模型类
class Person {
RxString userName;
RxInt age;
Person({required this.userName, required this.age});
}
//-------定义一个常规模型
class Animal {
String kindName;
int count;
Animal({required this.kindName, required this.count});
}
4.2.4 效果图如下:
4.3 Flutter Getx 简单的状态管理(依赖管理) GetxController 和 Binding
4.3.1 Getx 依赖管理简介
Get有一个简单而强大的依赖管理器,它允许你只用1行代码就能检索到与你的Bloc或Controller相同的类,无需Provider context,无需inheritedWidget。
Controller controller = Get.put(Controller());
Get会自动找到想要的数据,甚至不需要任何额外的依赖关系。
Controller controller = Get.find();
//是的,它看起来像魔术,Get会找到你的控制器,并将其提供给你。你可以实例化100万个控制器,Get 总会给你正确的控制器。
4.3.2 Binding
在我们使用 GetX 状态管理器的时候
,往往每次都是用需要手动实例化一个控制器,这样的话基本页面都需要实例化一次,这样就太麻烦了,而 Binding
能解决上述问题,可以在项目初始化时把所有需要进行状态管理的控制器进行统一初始化,简单介绍一下几个最常用的:
Get.put(): 不使用控制器实例也会被创建
Get.lazyPut(): 懒加载方式创建实例,只有在使用时才创建
Get.putAsync(): Get.put() 的异步版版本
Get.create(): 每次使用都会创建一个新的实例
4.3.3 用GetxController 和 Binding 结合的使用
- 创建一个继承
GetxController
的控制器Countcontroller
import 'package:get/get.dart';
class Countcontroller extends GetxController {
var count = 0.obs;
void increase() {
count++;
// 需要调用update来通知更新
update();
}
void decrease() {
if (count > 1) {
count--;
}
// 需要调用update来通知更新
update();
}
}
- 创建一个 实现
Bindings
的控制器Bindcontroller
import 'package:demoapp/Demo/Get/CountController.dart';
import 'package:get/get.dart';
class Bindcontroller implements Bindings {
void dependencies() {
// 用 lazyPut 关联 Countcontroller
Get.lazyPut<Countcontroller>(() => Countcontroller());
}
}
- 在程序的入口(程序入口,需要将
MaterialApp
修改成GetMaterialApp
, 前面已经提到了),用initialBinding
初始化Bindcontroller的值
import 'package:demoapp/Demo/Get/BindController.dart';
import 'package:demoapp/Router/AppRouter.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
debugShowCheckedModeBanner: true,
// home: Home(),
theme: ThemeData(
appBarTheme: AppBarTheme(
backgroundColor: Colors.yellow, // 设置导航栏颜色为蓝色
)
),
// 设置初始化页面
initialRoute: "/",
// 设置路由信息
getPages: Approuter.appRouterPages() ,
// 设置跳转动画
defaultTransition: Transition.rightToLeft,
// 绑定控制器
initialBinding: Bindcontroller(),
);
}
}
- 创建两个页面,来实现 页面数据的共享。
功能:第一个页面获取到第二个页面的数据
第一个页面:
import 'package:demoapp/Demo/Get/CountController.dart';
import 'package:demoapp/Router/AppRouter.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetEasystatusDemo extends StatelessWidget {
// Get.find() 来找到共享的数据的页面,因为用了Bind 懒加载lazyput 所以不会出问题
final Countcontroller _countCtr = Get.find();
GetEasystatusDemo({super.key});
// getX 简单式数据管理
void _getXEasyDataStatus() {
// 用Get.toName 进行页面的跳转,Approuter.getEasyStatueTwoName 是抽取出来的页面的名字的定义,方便使用
Get.toNamed(Approuter.getEasyStatueTwoName);
}
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("简单的状态管理"),
),
body: Center(child: Padding(
padding: EdgeInsets.all(5),
child: Row(
mainAxisAlignment: MainAxisAlignment.center,
spacing: 20.0,
children: [
ElevatedButton(onPressed: _getXEasyDataStatus, child: Text("简单状态管理")),
// 用obx 来刷新局部数据
Obx((){ return Text("count = ${_countCtr.count}"); })
],
),
)),
);
}
}
第二页页面:
import 'package:demoapp/Demo/Get/CountController.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetEasystatusSecondDemo extends StatelessWidget {
// Get.find() 来找到共享的数据的页面,因为用了Bind 懒加载lazyput 所以不用重新调用Get.put()方法
final Countcontroller _countCtr = Get.find();
GetEasystatusSecondDemo({super.key});
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("简单的状态管理"),
),
body: Center(
child: Obx((){
return Text("count = ${_countCtr.count}");
})
),
floatingActionButton: FloatingActionButton(
onPressed: (){
// 让count +1
_countCtr.increase();
},
child: Icon(Icons.add),
),
);
}
}
4.3.4 效果图如下
4.4 GetView 和 GetXController 和 Bindings 的结合使用
4.4.1 GetView 的介绍
GetView
只是对已注册的 Controller
有一个名为 controller
的getter的 const Stateless 的 Widget
,如果我们只有单个控制器作为依赖项
,那我们就可以使用 GetView ,而不是使用StatelessWidget ,并且避免了写 Get.Find()
。
4.4.2 GetView 和 GetXController 和 Bindings结构的分析
GetView
主要是专注于页面的搭建和UI的更新GetXController
主要是用于抽离数据, 维护和更新数据Bindings
主要是懒加载GetXController
,避免写Get.put()
,让代码比较优雅
4.4.3 GetView 和 GetXController 和 Bindings 案例的使用
- 新建一个维护数据的
GetXController
类
import 'package:get/get.dart';
class ShopController extends GetxController {
RxList dataList = [].obs;
void onInit() {
print("onInit");
super.onInit();
getDataRequest();
}
void onClose() {
print("onClose");
super.onClose();
}
void getDataRequest() {
dataList.add("新增数据");
}
}
- 新建一个
Bindings
的类
import 'package:demoapp/Demo/Get/Controller/ShopController.dart';
import 'package:get/get.dart';
class ShopBindings implements Bindings {
void dependencies() {
Get.lazyPut<ShopController>(() => ShopController());
}
}
- 新建一个
GetView
的页面类:
3.1 需要集成于:GetView
3.2 需要关联:<ShopController>
3.3 用controller
来获取 关联的<ShopController>
3.4 使用的时候,用controller
来获取ShopController
对应的数据与方法
3.5 用obx来
实现局部刷新
import 'package:demoapp/Demo/Get/Controller/ShopController.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetShoppageDemo extends GetView<ShopController> {
const GetShoppageDemo({super.key});
Widget build(BuildContext context) {
print(Get.arguments);
return Scaffold(
appBar: AppBar(
title: Text("GetView的使用"),
),
body: Obx((){
return ListView(
children: controller.dataList.map((value){
return ListTile(
title: Text(value),
);
}).toList(),
);
}),
floatingActionButton: FloatingActionButton(
onPressed: (){
controller.getDataRequest();
},
child: Icon(Icons.add),
),
);
}
}
- 配置路由:
4.1 用binding
字段将ShopBindings()
关联上,这样就不需要在第3步
的时候,单独写Get.put()
方法,让代码更优雅一点。
// getShopPageName 这个是自己定义的字符串
GetPage(name: getShopPageName, page: ()=> GetShoppageDemo(), binding: ShopBindings()),
4.4.4 效果图
5. GetX 自定义语言包 国际化配置
在我们使用系统自带 MaterialApp 来实现国际化配置,需要进行很多配置,而且还需要手动去依赖第三方组件,而使用 GetX 来实现国际化配置,你只需要一行代码即可实现切换
,接下来看一下具体实现。
5.1 定义一个语言包
import 'package:get/get_navigation/src/root/internacionalization.dart';
class Messages extends Translations {
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
5.2 应用程序入口配置
- translations: 国际化配置文件
- locale: 设置默认语言,不设置的话为系统当前语言
- fallbackLocale:添加一个回调语言选项,以备上面指定的语言翻译不存在
return GetMaterialApp(
// 你的翻译
translations: Messages(),
// 将会按照此处指定的语言翻译
locale: Locale('zh', 'CN'),
// 添加一个回调语言选项,以备上面指定的语 言翻译不存在
fallbackLocale: Locale('en', 'US'),
);
5.3 调用语言包
只要将 .tr
追加到指定的键上,就会使用 Get.locale 和 Get.fallbackLocale
的当前值进行翻译。
Text('hello'.tr);
5.4 改变语言
调用 Get.updateLocale(locale)
来更新语言环境。然后翻译会自动使用新的locale。
更新后所有页面生效!
var locale = Locale('en', 'US');
Get.updateLocale(locale);
5.5 完整代码
import 'package:get/get.dart';
class Messages extends Translations {
Map<String, Map<String, String>> get keys => {
'zh_CN': {
'hello': '你好 世界',
},
'de_DE': {
'hello': 'Hallo Welt',
}
};
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import './language/message.dart';
void main() {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
Widget build(BuildContext context) {
return GetMaterialApp(
title: 'Flutter Demo',
translations: Messages(),
locale: const Locale('zh', 'CN'),
fallbackLocale: const Locale('en', 'US'),
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const MyHomePage(),
);
}
}
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class MyHomePage extends StatefulWidget {
const MyHomePage({super.key});
State<MyHomePage> createState() => _MyHomePageState();
}
class _MyHomePageState extends State<MyHomePage> {
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text('title'.tr),
const SizedBox(),
Text('hello'.tr),
ElevatedButton(
onPressed: () {
var locale = const Locale('zh', 'CN');
Get.updateLocale(locale);
},
child: const Text("切换到中文")),
const SizedBox(
height: 20,
),
ElevatedButton(
onPressed: () {
var locale = const Locale('en', 'US');
Get.updateLocale(locale);
},
child: const Text("切换到英文")),
],
),
),
);
}
}