1. 介绍
Flutter 是一个强大的跨平台移动应用开发框架,它易于学习并构建漂亮的用户界面。 但是,如果你是一个经验丰富的 C#/Java 程序员(就是我~哈哈),也许你会有点不适应,因为 Flutter 是一个响应式编程框架,响应式编程是一种基于异步事件处理和数据流的声明式编程方式。 简单地说就是你要改变一个界面上的元素,并不是像传统语言写法那样,按着顺序写,如:
1txtLabel.value = "";
2
3void onClick() {
4 txtLabel.value = "你点击了按钮";
5}
以上示例是传统语言的常见用法,点击一下就改变了 label 的值,但在 Flutter 中可不是直接这样写就能改变界面元素的,而是通过改变变量的值,然后使用状态管理重建界面元素,如上面的例子中,要改变界面 AppBar
的标题内容:
1String _label = "欢迎";
2
3Scaffold(
4 appBar: AppBar(
5 title: Text(_label),
6),
按传统语言逻辑,会直接改变 AppBar
的 title
属性, 但这个在 Flutter
是做不到的,因为你根本获取不了这个对象,就算你获取到也更改了,但界面也不会被刷新的,这时就要用到状态管理,要在点击事件中直接改变 _label
变量的值,然后再反过来刷新界面,而要做到刷新界面就要使用 setState
方法,如下:
1void _onClick() {
2 setState(() {
3 _label = "你好,代码部落!";
4 });
5}
这就是响应式编程语言的特点,但它的刷新只是局部的,就是你哪里改变就只刷新哪里,这也能提高整体的效率,不用每次都重载整个页面。
所以,如果你想在 Flutter 中更新 UI,就需要更新状态来刷新布局,编程逻辑与 C# 或 Java 会有所不同,但幸运的是有很多状态管理框架可以帮助做到这一点,如 GetX、MobX、BLoC、Redux、Provider 等,不过这些框架中除了 GetX
之外,其他都不那么容易理解和使用。
2. 为什么是 GetX
GetX
专注于性能和最小资源消耗。 GetX
的关键性能优势之一在于其开销非常小。 通过最大限度地减少不必要的重新渲染和重建小部件,GetX
显著地减轻了应用程序的计算负担,从而加快了渲染速度并提高了整体性能。
此外,GetX
利用了高效依赖注入的力量。 其轻量级依赖注入机制可以在不影响性能的情况下创建和管理依赖项。 通过有效管理依赖关系,GetX
有助于消除不必要的对象实例化并确保高效的内存利用。
同时 GetX
简单易用,很容易理解,使用过程中比较接近于传统语言的逻辑。接下来就让我们看看如何使用它吧~~
3. 基本运用
3.1 安装
添加以下依赖到 pubspec.yaml
文件:
1dependencies:
2 get: 4.6.6
然后在你的 dart
文件里导入相关的包
1import 'package:get/get.dart';
3.2 基本架构
GetX
使用的是 MVC 的架构,即可以明确区分了模型、视图和控制器,这也是我喜欢的架构之一。这样做好处是可以很好地将界面布局与后台逻辑分开,因此你可以在控制器里写主要的逻辑代码,然后在视图中调用。如果你学习过 ASP.NET MVC ,对此就绝对不会陌生了。
如以下的简单示例(我之后会另有文章详细说明)
1
2class HomeController extends GetxController {
3 var title = '';
4}
5
6
7class HomePage extends GetView<HomeController> {
8 @override
9 Widget build(BuildContext context) {
10 return MainLayout(
11 appBar: AppBar(
12 title: Text(controller.title),
13 centerTitle: true,
14 ),
15 )
16 }
17}
可以看到,要在视图调用控制器变量,只需直接使用 controller.变量
即可
3.3 状态管理
GetX
的使用很简单,例如,你可直接定义一个变量,然后在其默认值后而添加 .obs
,此时这就变成了一个 GetX
变量, 如:
1var isEnabled = false.obs;
2
3...
4
5
6isEnabled.value = true;
之后在界面布局中使用 Obx(() => )
以监测其改动
1return Scaffold(
2 appBar: AppBar(
3 title: const Text('Test'),
4 centerTitle: true,
5 ),
6 body: Stack(
7 children: [
8 Obx(
9 () => controller.isEnabled.value
10 ? ListView(
11 children: <Widget>[
12 ...
13 ],
14 )
15 : const SizedBox.shrink(),
16 ),
17 ],
18 ),
19 );
20 }
以上的例子中,只要 isEnabled
的值被更新为 true
后(不需要使用 setState
),那么在界面布局中就显示出 ListView
来,否则就什么也不显示,这是不是很简单? :)
3.4 路由管理
导航在一个应用中是非常重要的, GetX
可以帮助你轻松地管理它,以下是其一些用法:
1
2Get.to(NextPage());
3
4
5Get.tonamed('/next');
6
7
8Get.back();
9
10
11Get.off(NextPage());
12
13
14Get.offAll(NextPage());
Ok, 这些只是一部分,你可以到官方网站查看更多详细的用法,而接下来我将继续告诉你其他的使用技巧 :)
4. 其他的用法
对于 GetX
的基本使用,其实官方已有很好的说明,在这里我就不再一一述说了,而我想介绍的是在实际项目中的一些使用技巧和问题
4.1 在 bottomNavigationBar 中使用
在大部分时间里,我们只需在应用启动时,在 main.dart
里绑定相关的依赖,但如果你在使用 bottomNavigationBar
的话就要注意了,你同时还要在导航的主页面绑定相关的依赖,如下面的例子
如下导航文件结构, dashboard 是整个框架主页面, home 是第一个 tab,然后里面有2个产品页面,每个产品页面又再有一个明细页面,而 settings 就是第二个 tab:
1dashboard (bottomNavigationBar 的主页面)
2
3
4
5
6
7
因此,我们也要在 dashboard
里绑定相关依赖
1class DashboardBinding extends Bindings {
2 @override
3 void dependencies() {
4 Get.lazyPut<DashboardController>(
5 () => DashboardController(),
6 );
7 Get.lazyPut<HomeController>(
8 () => HomeController(),
9 );
10 Get.lazyPut<Product1Controller>(
11 () => Product1Controller(),
12 );
13 Get.lazyPut<Product2Controller>(
14 () => Product2Controller(),
15 );
16 }
17}
这时如果你想从产品1导航其明细页面,即按以下顺序:
1home ==> product1 ==> product1_detail
你就必须要在进入明细页面前再次绑定它,如下是产品页面点击后将要点到明细页面的代码:
1return InkWell(
2onTap: () {
3 Get.lazyPut<Product1DetailController>(
4 () => Product1DetailController(),
5 );
6 Get.to(() => const Product1DetailPage());
7},
4.2 使用服务来做通用功能函数
GetX
的服务可以很方便地让你创建全局的通用功能函数,你可以在控制器里去处理 onInit()
, onReady()
, onClose()
这几个事件,同时你也可以在任何地方使用 Get.find()
获取到所要的服务。如下例子,创建一个存储器的服务以处理 SharedPreferences
相关逻辑
1
2class LocalStorageService extends GetxService {
3Future<T?> getValue<T>(
4 LocalStorageKeys key, [
5 T Function(Map<String, dynamic>)? fromJson,
6 ]) async {
7 SharedPreferences prefs = await SharedPreferences.getInstance();
8 switch (T) {
9 case int:
10 return prefs.getInt(key.name) as T?;
11 case double:
12 return prefs.getDouble(key.name) as T?;
13 case String:
14 return prefs.getString(key.name) as T?;
15 case bool:
16 return prefs.getBool(key.name) as T?;
17 default:
18 assert(fromJson != null, 'fromJson must be provided for Object values');
19 if (fromJson != null) {
20 final stringObject = prefs.getString(key.name);
21 if (stringObject == null) return null;
22 final jsonObject = jsonDecode(stringObject) as Map<String, dynamic>;
23 return fromJson(jsonObject);
24 }
25 }
26 return null;
27 }
28
29 void setValue<T>(LocalStorageKeys key, T value) async {
30 SharedPreferences prefs = await SharedPreferences.getInstance();
31 switch (T) {
32 case int:
33 prefs.setInt(key.name, value as int);
34 break;
35 case double:
36 prefs.setDouble(key.name, value as double);
37 break;
38 case String:
39 prefs.setString(key.name, value as String);
40 break;
41 case bool:
42 prefs.setBool(key.name, value as bool);
43 break;
44 default:
45 assert(
46 value is Map<String, dynamic>,
47 'value must be int, double, String, bool or Map<String, dynamic>',
48 );
49 final stringObject = jsonEncode(value);
50 prefs.setString(key.name, stringObject);
51 }
52 }
53}
然后在 main.dart
初始化服务
1Get.put(LocalStorageService());
之后你就可以在任何地方获取和使用它了
1
2var localStorage = Get.find<LocalStorageService>();
3
4...
5
6
7localStorage.setValue<String>('currentLanguage', 'en');
8
9
10var currentLanguage =
11 await localStorage.getValue<String>('currentLanguage');
4.3 使用对话框
GetX
还支持使用弹出对话框,你可以轻松地使用默认的对话框:
1Get.defaultDialog(
2 title: '标题',
3 titleStyle: const TextStyle(color: Colors.red),
4 middleText: '信息');
这只是普通的对话框,如果你想使用自定义样式,你可以使用以下方式实现
1Get.dialog(
2 Column(
3 mainAxisAlignment: MainAxisAlignment.center,
4 children: [
5 Padding(
6 padding: const EdgeInsets.symmetric(horizontal: 40),
7 child: Container(
8 decoration: const BoxDecoration(
9 color: Colors.white,
10 borderRadius: BorderRadius.all(
11 Radius.circular(20),
12 ),
13 ),
14 child: Padding(
15 padding: const EdgeInsets.all(20.0),
16 child: Material(
17 child:
18
19
20
21 ),
22 ),
23 ),
24 ),
25 ],
26 ),
27);
正如你所看到的那样,你可以放任何小部件到 Get.dialog()
里,所以其实这已不只是对话框了,你甚至可以当作一个弹出页面来使用哦 :)
当然,你也可以将自定义的对话框做成一个服务,然后就可以随时调用了:
1enum DialogType {
2 info,
3 success,
4 error,
5 warning,
6}
7
8
9class DialogService extends GetxService {
10 showAlert(
11 String title,
12 String message, {
13 DialogType dialogType = DialogType.info,
14 Function? callback,
15 }) {
16 IconData iconData = Icons.info;
17 Color iconColor = Colors.blueGrey;
18 if (dialogType == DialogType.error) {
19 iconData = Icons.error;
20 iconColor = Colors.red;
21 } else if (dialogType == DialogType.warning) {
22 iconData = Icons.warning;
23 iconColor = Colors.yellow;
24 } else if (dialogType == DialogType.success) {
25 iconData = Icons.done_rounded;
26 iconColor = Colors.green;
27 }
28
29 Get.dialog(
30 Column(
31 mainAxisAlignment: MainAxisAlignment.center,
32 children: [
33 Padding(
34 padding: const EdgeInsets.symmetric(horizontal: 40),
35 child: Container(
36 decoration: const BoxDecoration(
37 color: Colors.white,
38 borderRadius: BorderRadius.all(
39 Radius.circular(20),
40 ),
41 ),
42 child: Padding(
43 padding: const EdgeInsets.all(20.0),
44 child: Material(
45 child: Column(
46 children: [
47 const SizedBox(height: 10),
48 Icon(
49 iconData,
50 color: iconColor,
51 size: 50,
52 ),
53 const SizedBox(height: 10),
54 Text(
55 title,
56 style: const TextStyle(
57 fontSize: 24, fontWeight: FontWeight.bold),
58 textAlign: TextAlign.center,
59 ),
60 const SizedBox(height: 20),
61 Text(
62 message,
63 style: const TextStyle(fontSize: 18),
64 textAlign: TextAlign.center,
65 ),
66 const SizedBox(height: 50),
67
68 Row(
69 children: [
70 Expanded(
71 child: ElevatedButton(
72 style: ElevatedButton.styleFrom(
73 foregroundColor: const Color(0xFFFFFFFF),
74 backgroundColor: Colors.blue,
75 minimumSize: const Size(0, 45),
76 shape: RoundedRectangleBorder(
77 borderRadius: BorderRadius.circular(8),
78 ),
79 ),
80 onPressed: () {
81 callback != null ? callback() : null;
82 },
83 child: Text(
84 style: const TextStyle(fontSize: 18),
85 LabelKeys.ok.tr,
86 ),
87 ),
88 ),
89 ],
90 ),
91 ],
92 ),
93 ),
94 ),
95 ),
96 ),
97 ],
98 ),
99 );
100 }
101}
现在你可以在任何地方使用它
1final dialog = Get.find<DialogService>();
2
3
4
5dialog.showAlert(
6 '标题',
7 '这是一个普通的信息对话框'
8);
9
10
11dialog.showAlert(
12 '错误',
13 '这是一个错误警告',
14 dialogType: DialogType.error
15);
5. 高级 API 用法
GetX
里还有很多高级 API
可以使用,如下面这些
1
2Get.arguments
3
4
5Get.previousRoute
6
7
8Get.rawRoute
9
10
11Get.routing
12
13
14Get.isSnackbarOpen
15
16
17Get.isDialogOpen
18
19
20Get.isBottomSheetOpen
21
22
23GetPlatform.isAndroid
24GetPlatform.isIOS
25GetPlatform.isMacOS
26GetPlatform.isWindows
27GetPlatform.isLinux
28GetPlatform.isFuchsia
29
30
31GetPlatform.isMobile
32GetPlatform.isDesktop
33GetPlatform.isWeb
34
35
36
37
38Get.height
39Get.width
40
41
42Get.context
43
44
45Get.contextOverlay
46
47...
你可以在这里找到更详细的说明。
6. 总结
GetX
功能强大,其节省了我很多时间,而且很容易理解,它还有一个非常活跃和乐于助人的社区,如果有任何问题基本上都可以在他们的社区中找到解决方案,而且还可以安装 VSCode 扩展以帮助构建 GetX
项目。 如果你对 GetX
有其他想法的话,也欢迎给我留言哦 :)
更多精彩文章,请关注我,或者到我的博客网站: