1、介绍
当我们想在一个或多个页面/组件/widget 之间共享状态/数据,我们可以使用 Flutter 种的状态管理来实现。目前 Flutter 种的状态管理方案有 redux,bloc,state,provider,Getx。
provider 是官方提供的状态管理方案,主要功能就是状态管理;Getx 是第三方状态管理插件,不仅具有状态管理功能,还具有路由管理,主体管理,国际化多语言管理,obx 局部更新,网络请求,数据验证等功能,相比于其他插件,具有简单,功能强大,高性能。
2、Getx
Getx 是 Flutter 上的一个轻量且强大的解决方案,Getx 为我们提供了高性能的状态管理、智能的依赖注入和便捷的路由管理。
1、Getx 有3个基本原则:
性能:Getx 专注于性能和最小资源消耗。GetX 打包后的 apk 占用大小和运行时的内存占用与其他状态管理插件不相上下。
效率:Getx 的语法非常简捷,并保持了极高的性能,能极大缩短你的开发时长。
结构:Getx 可以将界面、逻辑、依赖和路由完全解耦,用起来更清爽,逻辑更清晰,代码更容易维护 。
2、Getx 并不臃肺,却很轻量。如果你只使用状态管理,只有状态管理模块会被编译,其他没用到的东西都不会被编译到你的代码中。它拥有众多的功能,但这些功能都在独立的容器中,只有在使用后才会启动。
3、Getx 有一个庞大的生态系统,能够在 Android、ios、Web、Mac、Linux、Windows 和你的服务器上用同样的代码运行。通过 GetServer 可以在你的后端完全重用你在前端写的代码。
3、配置
参考文档 >>>
1、pubspec.yaml 导入依赖库
dependencies:
get: ^4.6.5
2、引入头文件
import 'package:get/get.dart';
3、将项目中 MaterialApp() 方法改为 GetMaterialApp() 方法。
4、Getx 的状态管理
1、 Get.defaultDialog
defaultDialog 属性
Future<T?> defaultDialog<T>({
String title = "Alert", //弹框标题
EdgeInsetsGeometry? titlePadding, //标题内边距,默认(EdgeInsets.all(8))
TextStyle? titleStyle, //标题样式
Widget? content, //弹框内容,该属性设置后 middleText 将无效
EdgeInsetsGeometry? contentPadding, //内容边距,默认(EdgeInsets.all(8))
void Function()? onConfirm, //确认按钮回调
void Function()? onCancel, //取消按钮回调
void Function()? onCustom, //自定义按钮回调
Color? cancelTextColor, //取消按钮文字颜色
Color? confirmTextColor, //确认按钮文字颜色
String? textConfirm, //确认按钮文字
String? textCancel, //取消按钮文字
String? textCustom, //自定义按钮文字
Widget? confirm, //确认按钮组件
Widget? cancel, //取消按钮组件
Widget? custom, //自定义按钮组件
Color? backgroundColor, //弹框背景色
bool barrierDismissible = true, //是否可以通过点击背景关闭弹框
Color? buttonColor, //按钮文字颜色
String middleText = "Dialog made in 3 lines of code", //内容区域显示的文字
TextStyle? middleTextStyle, //内容区域显示的文字的样式
double radius = 20.0, //弹框圆角
List<Widget>? actions, //增加额外子组件
Future<bool> Function()? onWillPop, //拦截关闭之前做的一些操作
GlobalKey<NavigatorState>? navigatorKey, //用于打开对话框的 Key
})
使用
///Getx 的使用
import 'package:flutter/material.dart';
import 'package:get/get.dart';//1.引入头文件
void main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return const GetMaterialApp( //2.更改 MaritalApp 为 GetMaterialApp
home: HomePage(),
);
}
}
class HomePage extends StatefulWidget {
const HomePage({super.key});
void showAlertDialog(context) async {
var result = await showDialog(
barrierDismissible: false, //表示点击灰色背景的时候是杳消失弹出框
context: context,
builder: (context) {
return AlertDialog(
title: const Text("标题"),
content: const Text("内容"),
actions: [
TextButton(
onPressed: () {
// Navigator.popAndPushNamed(context, "OK");
Navigator.of(context).pop("确定");
},
child: const Text("确定")),
TextButton(
onPressed: () {
// Navigator.popAndPushNamed(context, "取消");
Navigator.of(context).pop("取消");
},
child: const Text("取消")),
],
);
});
print("----------");
print(result);
}
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Getx'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
widget.showAlertDialog(context);
},
child: const Text("默认 Dialog")),
ElevatedButton(
onPressed: () {
Get.defaultDialog(
title: "标题",
// content: const Text("内容"),
middleText: "内容",
confirm: ElevatedButton(
onPressed: () {
print("确定");
Get.back();
},
child: const Text("确定")),
cancel: ElevatedButton(
onPressed: () {
print("取消");
Get.back();
},
child: const Text("取消")));
},
child: const Text("Getx 默认 Dialog")),
],
),
),
);
}
}
2、Get.snackbar
属性
SnackbarController snackbar(
String title, //弹框标题
String message, { //弹框消息
Color? colorText, //title 和 message 的文字颜色
Duration? duration = const Duration(seconds: 3), //弹框弹出的持续时间
bool instantInit = true, //为 false 可以将 snackbar 放在 initState
SnackPosition? snackPosition, //弹出位置,TOP 和 BOTTOM,默认 TOP
Widget? titleText, //标题组件,使用该属性会使 title 属性失效
Widget? messageText, //内容组件,使用该属性会使 message 属性失效
Widget? icon, //弹出时图标,显示在 title 和 message 左侧
bool? shouldIconPulse, //弹出时图标是否闪烁,默认 false
double? maxWidth, //Snackbar最大的宽度
EdgeInsets? margin, //Snackbar外边距,默认zero
EdgeInsets? padding, //Snackbar内边距,默认EdgeInsets.all(16)
double? borderRadius, //边框圆角大小,默认15
Color? borderColor, //边框的颜色,必须设置 borderWidth,否则无效果
double? borderWidth, //边框的线条宽度
Color? backgroundColor, //Snackbar背景颜色,默认Colors.grey.withOpacity(0.2)
Color? leftBarIndicatorColor, //左侧指示器的颜色
List<BoxShadow>? boxShadows, //Snackbar阴影颜色
Gradient? backgroundGradient, //背景的线性颜色
TextButton? mainButton, //主要按钮,一般显示发送、确认按钮
void Function(GetSnackBar)? onTap, //点击Snackbar事件回调
bool? isDismissible, //是否开启 Snackbar 手势关闭,可配合 dismissDirection 使用
bool? showProgressIndicator, //是否显示进度条指示器,默认false
DismissDirection? dismissDirection, //Snackbar关闭的方向
AnimationController? progressIndicatorController, //进度条指示器的动画控制器
Color? progressIndicatorBackgroundColor, //进度条指示器的背景颜色
Animation<Color>? progressIndicatorValueColor, //进度条指示器的背景颜色
SnackStyle? snackStyle, //Snackbar是否会附加到屏幕边缘
Curve? forwardAnimationCurve, //Snackbar 弹出的动画,默认Curves.easeOutCirc
Curve? reverseAnimationCurve, //Snackbar 消失的动画,默认Curves.easeOutCirc
Duration? animationDuration, //Snackbar弹出和小时的动画时长,默认1秒
double? barBlur, //Snackbar 背景的模糊度
double? overlayBlur, //弹出时的毛玻璃效果值,默认0
void Function(SnackbarStatus?)? snackbarStatus, //Snackbar 弹出或消失时的事件回调(即将打开、已打开、即将关闭、已关闭)
Color? overlayColor, //弹出时的毛玻璃的背景颜色
Form? userInputForm, //用户输入表单
})
使用
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Getx'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Get.snackbar("title", "message",
snackPosition: SnackPosition.BOTTOM);
},
child: const Text("Getx snackbar")),
],
),
),
);
}
}
3、Get.bottomSheet
属性
Future<T?> bottomSheet<T>(
Widget bottomsheet, { //弹出的Widget组件
Color? backgroundColor, //bottomsheet的背景颜色
double? elevation, //bottomsheet的阴影
bool persistent = true, //是否添加到路由中
ShapeBorder? shape, //边框形状,一般用于圆角效果
Clip? clipBehavior, //裁剪的方式
Color? barrierColor, //弹出层的背景颜色
bool? ignoreSafeArea, //是否忽略安全适配
bool isScrollControlled = false, //是否支持全屏弹出,默认false
bool useRootNavigator = false, //是否使用根导航
bool isDismissible = true, //点击背景是否可关闭,默认ture
bool enableDrag = true, //是否可以拖动关闭,默认true
RouteSettings? settings, //路由设置
Duration? enterBottomSheetDuration, //bottomsheet进入时的动画时间
Duration? exitBottomSheetDuration, //bottomsheet退出时的动画时间
})
使用
class HomePage extends StatefulWidget {
const HomePage({super.key});
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('Getx'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
ElevatedButton(
onPressed: () {
Get.bottomSheet(Container(
color: Get.isDarkMode ? Colors.black45 : Colors.white,
height: 250,
child: Column(
children: [
ListTile(
leading: Icon(Icons.wb_sunny_outlined,
color: Get.isDarkMode
? Colors.white54
: Colors.black26), // Icon
title: Text(
"白天模式",
style: TextStyle(
color: Get.isDarkMode
? Colors.white54
: Colors.black87),
),
onTap: () {
Get.changeTheme(ThemeData.light());
Get.back();
},
),
ListTile(
leading: Icon(Icons.wb_sunny,
color: Get.isDarkMode
? Colors.white54
: Colors.black), // Icon
title: Text(
"夜晚模式",
style: TextStyle(
color: Get.isDarkMode
? Colors.white54
: Colors.black),
),
onTap: () {
Get.changeTheme(ThemeData.dark());
Get.back();
},
),
],
),
));
},
child: const Text("Getx bottomSheet 切换主题")),
],
),
),
);
}
}
4、嵌套
使用 Get 可以让 Flutter 的嵌套导航更加简单,它并不需要使用 context,而是通过 Id 找到导航栈。
注:通常情况下,不建议使用或尽量少用 NestedNavigators。若确实需要,要注意多个导航堆栈对内存的消耗。
Navigator(
key: Get.nestedKey(1),
initialRoute: '/',
onGenerateRoute: (settings) {
if (settings.name == '/') {
return GetPageRoute(
page: () => Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(
child: TextButton(
color: Colors.blue,
onPressed: () {
Get.toNamed('/second', id: 1);
},
child: Text("Go to second"),
),
),
),
);
} else if (settings.name == '/second') {
return GetPageRoute(
page: () => Center(
child: Scaffold(
appBar: AppBar(
title: Text("Main"),
),
body: Center(child: Text("second")),
),
),
);
}
});
5、GetX 路由管理
GetX 为我们封装了 Navigation,无需 context 可进行跳转,使用 GetX 进行路由跳转非常的简单只需要调用Get.to()即可进行路由跳转,GetX 路由跳转简化了跳转动画设置、动画时长定义、动画曲线设置。
1、普通路由导航
1、导航到新的页面
Get.to(NextScreen());
2、关闭 SnackBars、Dialogs、BottomSheets 或任何你通常会用 Navigator.pop(context) 关闭的东西。
Get.back();
3、进入下一个页面,但没有返回上一个页面的选项
Get.off(NextScreen());
4、进入下一个界面并取消之前的所有路由
Get.offAll(NextScreen());
5、要导航到下一条路由,并在返回后立即接收或更新数据
var data = await Get.to(Payment());
6、在另一个页面上,发送前一个路由的数据并使用它
Get.back(result: 'success');
if(data == 'success') madeAnything();
7、与标准导航的关系 , 只要把 Navigator(大写)改成 navigator(小写),就可以拥有标准导航的所有功能,而不需要使用 context
// 默认的Flutter导航
Navigator.of(context).push(
context,
MaterialPageRoute(
builder: (BuildContext context) {
return HomePage();
},
),
);
// 使用Flutter语法获得,而不需要context。
navigator.push(
MaterialPageRoute(
builder: (_) {
return HomePage();
},
),
);
// get语法
Get.to(HomePage());
2、别名路由导航
1、导航到下一个页面
Get.toNamed("/NextScreen");
2、浏览并删除前一个页面
Get.offNamed("/NextScreen");
3、浏览并删除所有以前的页面
Get.offAllNamed("/NextScreen");
4、要定义路由,使用 GetMaterialApp
void main() {
runApp(GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
GetPage(name: '/third', page: () => Third(), transition: Transition.zoom),
],
));
}
5、定义动画效果
void main() {
runApp(GetMaterialApp(
//全局动画效果
defaultTransition: Transition.rightToLeftWithFade,
//定义路由
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(
name: '/second',
page: () => Second(),
//局部动画效果
transition: Transition.rightToLeftWithFade,
),
GetPage(name: '/third', page: () => Third(), transition: Transition.zoom),
],
));
}
6、处理到未定义路线的导航(404错误),可以在GetMaterialApp中定义unknownRoute页面。
void main() {
runApp(GetMaterialApp(
unknownRoute: GetPage(name: '/notfound', page: () => UnknownRoutePage()),
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => MyHomePage()),
GetPage(name: '/second', page: () => Second()),
],
));
}
7、发送数据到别名路由
只要发送你想要的参数即可。Get 在这里接受任何东西,无论是一个字符串,一个 Map,一个List,甚至一个类的实例。通过 Get.arguments 接受参数
Get.toNamed("/NextScreen", arguments: 'Get is the best');
类或控制器上:
print(Get.arguments);
//print out: Get is the best
8、动态网页链接
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("/second/34954");
在第二个页面上,通过参数获取数据
print(Get.parameters['user']);
// out: 34954
发送多个参数
Get.toNamed("/profile/34954?flag=true");
在第二个屏幕上,通常按参数获取数据
print(Get.parameters['user']);
print(Get.parameters['flag']);
// out: 34954 true
可以使用 Get.toNamed() 来导航别名路由,不需要任何 context (可以直接从 BLoC 或 Controller 类中调用路由),当应用程序被编译到 web 时,路由将出现在URL中。
6、中间件
1、Middleware 介绍
中间件是介于应用系统和系统软件之间的一类软件,它使用系统软件所提供的基础服务,衔接网络上应用系统的各个部分或不同的应用,能够达到资源共享,功能共享的目的。
2、中间件配置
void main(List<String> args) {
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
// home: HomePage(),
getPages: [
GetPage(
name: "/home",
page: () => HomePage(),
middlewares: [GetMiddleware(priority: 1), GetMiddleware(priority: 2)]
)
],
);
}
}
1、在 main.dart 中的 GetMaterialApp/getPages/GetPage 进行配置。
2、middlewares 是个数组,可配置多个,并且有优先级(优先级越低越先执行)。
3、middlewares 的配置,在要跳转的页面进行配置,不是跳转前页面配置。
3、定义 GetMiddleWare 过程
/*
定义中间件
1.继承自 GetMiddleware
2.设置优先级(优先级越低越先执行)
3.重写 GetMiddleware 方法
*/
class MyMiddleWare extends GetMiddleware {
@override
int get priority => -1;
@override
RouteSettings? redirect(String? route) {//重定向,当正在搜索被调用的路由时,将调用该函数
// return super.redirect(route);
return RouteSettings(name: AppRoutes.login);
}
@override
GetPage? onPageCalled(GetPage? page) {//创建任何内容之前调用该函数
// return super.onPageCalled(page);
// return page?.copy(name: AppRoutes.login);
return GetPage(name: AppRoutes.login, page: () => LoginWidge());
}
@override
List<Bindings>? onBindingsStart(List<Bindings>? bindings) {//这个函数将在绑定初始化之前调用,在此可以更改此页面的绑定
// return super.onBindingsStart(bindings);
//添加操作...
bindings?.add(LoginBiding());
return bindings;
}
@override
GetPageBuilder? onPageBuildStart(GetPageBuilder? page) {//此函数将在绑定初始化之后立即调用,在此可以在创建绑定之后和创建页面小部件之前执行操作
// return super.onPageBuildStart(page);
return page;
}
@override
Widget onPageBuilt(Widget page) {//该函数将在 GetPage.page 之后立即调用,并提供函数的结果,并获取将显示的小部件
// return super.onPageBuilt(page);
return page;
}
@override
void onPageDispose() {//在处理完页面的所有相关对象后立即调用
super.onPageDispose();
}
}
4、使用
1、如果想通过监听 Get 事件来触发动作,可以通过 routingCallback 来实现。
return GetMaterialApp(routingCallback: (routing) {
if (routing.current == '/second') {
openAds();
}
});
2、如果没有使用 GetMaterialApp,可以使用手动 API 来附加 Middleware 观察器。
void main() {
runApp(
MaterialApp(
onGenerateRoute: Router.generateRoute,
initialRoute: "/",
navigatorKey: Get.key,
navigatorObservers: [
GetObserver(MiddleWare.observer),//此处
],
),
);
}
3、创建一个 MiddleWare 类
class MiddleWare {
static observer(Routing routing) {
///除了可以监听路由外,还可以监听每个页面上的 SnackBars、Dialogs 、Bottomsheets。
if (routing.current == '/second' && !routing.isSnackbar) {
Get.snackbar("Hi", "You are on second route");
} else if (routing.current == '/third') {
print('last route called');
}
}
}
在代码上使用Get:
class First extends StatelessWidget {
@overrideWidget
build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('First Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/second");
},
),
),
);
}
}
class Second extends StatelessWidget {
@overrideWidget
build(BuildContext context) {
return Scaffold(
appBar: AppBar(
leading: IconButton(
icon: Icon(Icons.add),
onPressed: () {
Get.snackbar("hi", "i am a modern snackbar");
},
),
title: Text('second Route'),
),
body: Center(
child: ElevatedButton(
child: Text('Open route'),
onPressed: () {
Get.toNamed("/third");
},
),
),
);
}
}
class Third extends StatelessWidget {
@overrideWidget
build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: Text("Third Route"),
),
body: Center(
child: ElevatedButton(
onPressed: () {
Get.back();
},
child: Text('Go back!'),
),
),
);
}
}
7、响应式
目前,Flutter 有几种状态管理器。但是,它们中的大多数都涉及到使用 ChangeNotifier 来更新 widget, 这对于中大型应用的性能来说是一个很糟糕的方法。
Get 是一个微框架,而不仅仅是一个状态管理器,既可以单独使用,也可以与其他状恋管理器結合使用。Get 有两个不同的状态管理器:响应式状态管理器、简单的状态管理器。
1、响应式状态管理器
使用示例
class HomePage extends StatelessWidget {
//以 Int 为例
final RxInt _counter = 0.obs; // RxInt(0);//也可以这么写
/*
其他类型
final Rxstring _username = "zhangsan".obs; //RxString("zhangsan");
final RxList _list = ["张三","李四"].obs;
*/
HomePage({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
floatingActionButton: FloatingActionButton(
child: const Icon(Icons.add),
onPressed: () {
_counter.value++;
}),
appBar: AppBar(
title: const Text('Title'),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Obx(() => Text(
"${_counter.value}",
style: const TextStyle(fontSize: 20, color: Colors.black),
))
],
),
),
);
}
}