首页/文章/flutter

GetX 的介绍和使用

2024-05-30
22058 分钟
...

1. 介绍

Flutter 是一个强大的跨平台移动应用开发框架,它易于学习并构建漂亮的用户界面。 但是,如果你是一个经验丰富的 C#/Java 程序员(就是我~哈哈),也许你会有点不适应,因为 Flutter 是一个响应式编程框架,响应式编程是一种基于异步事件处理和数据流的声明式编程方式。 简单地说就是你要改变一个界面上的元素,并不是像传统语言写法那样,按着顺序写,如:

txtLabel.value = "";

void onClick() {
    txtLabel.value = "你点击了按钮";
}

以上示例是传统语言的常见用法,点击一下就改变了 label 的值,但在 Flutter 中可不是直接这样写就能改变界面元素的,而是通过改变变量的值,然后使用状态管理重建界面元素,如上面的例子中,要改变界面 AppBar 的标题内容:

String _label = "欢迎"; 

Scaffold(
    appBar: AppBar(
    title: Text(_label),
),

按传统语言逻辑,会直接改变 AppBartitle 属性, 但这个在 Flutter 是做不到的,因为你根本获取不了这个对象,就算你获取到也更改了,但界面也不会被刷新的,这时就要用到状态管理,要在点击事件中直接改变 _label 变量的值,然后再反过来刷新界面,而要做到刷新界面就要使用 setState 方法,如下:

void _onClick() {
    setState(() {
      _label = "你好,代码部落!";
    });
}

这就是响应式编程语言的特点,但它的刷新只是局部的,就是你哪里改变就只刷新哪里,这也能提高整体的效率,不用每次都重载整个页面。

所以,如果你想在 Flutter 中更新 UI,就需要更新状态来刷新布局,编程逻辑与 C# 或 Java 会有所不同,但幸运的是有很多状态管理框架可以帮助做到这一点,如 GetX、MobX、BLoC、Redux、Provider 等,不过这些框架中除了 GetX 之外,其他都不那么容易理解和使用。

2. 为什么是 GetX

GetX 专注于性能和最小资源消耗。 GetX 的关键性能优势之一在于其开销非常小。 通过最大限度地减少不必要的重新渲染和重建小部件,GetX 显著地减轻了应用程序的计算负担,从而加快了渲染速度并提高了整体性能。

此外,GetX 利用了高效依赖注入的力量。 其轻量级依赖注入机制可以在不影响性能的情况下创建和管理依赖项。 通过有效管理依赖关系,GetX 有助于消除不必要的对象实例化并确保高效的内存利用。

同时 GetX 简单易用,很容易理解,使用过程中比较接近于传统语言的逻辑。接下来就让我们看看如何使用它吧~~

3. 基本运用

3.1 安装

添加以下依赖到 pubspec.yaml 文件:

dependencies:
    get: 4.6.6

然后在你的 dart 文件里导入相关的包

import 'package:get/get.dart';

3.2 基本架构

GetX 使用的是 MVC 的架构,即可以明确区分了模型、视图和控制器,这也是我喜欢的架构之一。这样做好处是可以很好地将界面布局与后台逻辑分开,因此你可以在控制器里写主要的逻辑代码,然后在视图中调用。如果你学习过 ASP.NET MVC ,对此就绝对不会陌生了。

如以下的简单示例(我之后会另有文章详细说明)


class HomeController extends GetxController {
    var title = '';
}


class HomePage extends GetView<HomeController> {
 @override
  Widget build(BuildContext context) {
    return MainLayout(
      appBar: AppBar(
        title: Text(controller.title),
        centerTitle: true,
      ),
    )
  }
}

可以看到,要在视图调用控制器变量,只需直接使用 controller.变量 即可

3.3 状态管理

GetX 的使用很简单,例如,你可直接定义一个变量,然后在其默认值后而添加 .obs,此时这就变成了一个 GetX 变量, 如:

var isEnabled = false.obs;

...


isEnabled.value = true;

之后在界面布局中使用 Obx(() => ) 以监测其改动

return Scaffold(
      appBar: AppBar(
        title: const Text('Test'),
        centerTitle: true,
      ),
      body: Stack(
        children: [
          Obx(
            () => controller.isEnabled.value 
                ? ListView(
                    children: <Widget>[
                     ...
                    ],
                  )
                : const SizedBox.shrink(),
          ),
        ],
      ), 
    );
  }

以上的例子中,只要 isEnabled 的值被更新为 true 后(不需要使用 setState ),那么在界面布局中就显示出 ListView 来,否则就什么也不显示,这是不是很简单? :)

3.4 路由管理

导航在一个应用中是非常重要的, GetX 可以帮助你轻松地管理它,以下是其一些用法:


Get.to(NextPage());


Get.tonamed('/next');


Get.back();


Get.off(NextPage());


Get.offAll(NextPage());

Ok, 这些只是一部分,你可以到官方网站查看更多详细的用法,而接下来我将继续告诉你其他的使用技巧 :)

4. 其他的用法

对于 GetX 的基本使用,其实官方已有很好的说明,在这里我就不再一一述说了,而我想介绍的是在实际项目中的一些使用技巧和问题

4.1 在 bottomNavigationBar 中使用

在大部分时间里,我们只需在应用启动时,在 main.dart 里绑定相关的依赖,但如果你在使用 bottomNavigationBar 的话就要注意了,你同时还要在导航的主页面绑定相关的依赖,如下面的例子

如下导航文件结构, dashboard 是整个框架主页面, home 是第一个 tab,然后里面有2个产品页面,每个产品页面又再有一个明细页面,而 settings 就是第二个 tab:

dashboard (bottomNavigationBar 的主页面)
    
        
            
        
            
    

因此,我们也要在 dashboard 里绑定相关依赖

class DashboardBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut<DashboardController>(
      () => DashboardController(),
    );
    Get.lazyPut<HomeController>(
      () => HomeController(),
    );
    Get.lazyPut<Product1Controller>(
      () => Product1Controller(),
    );
    Get.lazyPut<Product2Controller>(
      () => Product2Controller(),
    );
  }
}

这时如果你想从产品1导航其明细页面,即按以下顺序:

home ==> product1  ==>  product1_detail

你就必须要在进入明细页面前再次绑定它,如下是产品页面点击后将要点到明细页面的代码:

return InkWell(
onTap: () {
  Get.lazyPut<Product1DetailController>(
    () => Product1DetailController(),
  );
  Get.to(() => const Product1DetailPage());
},

4.2 使用服务来做通用功能函数

GetX 的服务可以很方便地让你创建全局的通用功能函数,你可以在控制器里去处理 onInit(), onReady(), onClose() 这几个事件,同时你也可以在任何地方使用 Get.find() 获取到所要的服务。如下例子,创建一个存储器的服务以处理 SharedPreferences 相关逻辑


class LocalStorageService extends GetxService {
Future<T?> getValue<T>(
    LocalStorageKeys key, [
    T Function(Map<String, dynamic>)? fromJson,
  ]) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    switch (T) {
      case int:
        return prefs.getInt(key.name) as T?;
      case double:
        return prefs.getDouble(key.name) as T?;
      case String:
        return prefs.getString(key.name) as T?;
      case bool:
        return prefs.getBool(key.name) as T?;
      default:
        assert(fromJson != null, 'fromJson must be provided for Object values');
        if (fromJson != null) {
          final stringObject = prefs.getString(key.name);
          if (stringObject == null) return null;
          final jsonObject = jsonDecode(stringObject) as Map<String, dynamic>;
          return fromJson(jsonObject);
        }
    }
    return null;
  }

  void setValue<T>(LocalStorageKeys key, T value) async {
    SharedPreferences prefs = await SharedPreferences.getInstance();
    switch (T) {
      case int:
        prefs.setInt(key.name, value as int);
        break;
      case double:
        prefs.setDouble(key.name, value as double);
        break;
      case String:
        prefs.setString(key.name, value as String);
        break;
      case bool:
        prefs.setBool(key.name, value as bool);
        break;
      default:
        assert(
          value is Map<String, dynamic>,
          'value must be int, double, String, bool or Map<String, dynamic>',
        );
        final stringObject = jsonEncode(value);
        prefs.setString(key.name, stringObject);
    }
  }
}

然后在 main.dart 初始化服务

Get.put(LocalStorageService());

之后你就可以在任何地方获取和使用它了


var localStorage = Get.find<LocalStorageService>();

...


localStorage.setValue<String>('currentLanguage', 'en');


var currentLanguage =
      await localStorage.getValue<String>('currentLanguage');

4.3 使用对话框

GetX 还支持使用弹出对话框,你可以轻松地使用默认的对话框:

Get.defaultDialog(
    title: '标题',
    titleStyle: const TextStyle(color: Colors.red),
    middleText: '信息');

这只是普通的对话框,如果你想使用自定义样式,你可以使用以下方式实现

Get.dialog(
  Column(
    mainAxisAlignment: MainAxisAlignment.center,
    children: [
      Padding(
        padding: const EdgeInsets.symmetric(horizontal: 40),
        child: Container(
          decoration: const BoxDecoration(
            color: Colors.white,
            borderRadius: BorderRadius.all(
              Radius.circular(20),
            ),
          ),
          child: Padding(
            padding: const EdgeInsets.all(20.0),
            child: Material(
              child: 
              
              
              
            ),
          ),
        ),
      ),
    ],
  ),
);

正如你所看到的那样,你可以放任何小部件到 Get.dialog() 里,所以其实这已不只是对话框了,你甚至可以当作一个弹出页面来使用哦 :)

当然,你也可以将自定义的对话框做成一个服务,然后就可以随时调用了:

enum DialogType {
  info,
  success,
  error,
  warning,
}


class DialogService extends GetxService {
  showAlert(
    String title,
    String message, {
    DialogType dialogType = DialogType.info,
    Function? callback,
  }) {
    IconData iconData = Icons.info;
    Color iconColor = Colors.blueGrey;
    if (dialogType == DialogType.error) {
      iconData = Icons.error;
      iconColor = Colors.red;
    } else if (dialogType == DialogType.warning) {
      iconData = Icons.warning;
      iconColor = Colors.yellow;
    } else if (dialogType == DialogType.success) {
      iconData = Icons.done_rounded;
      iconColor = Colors.green;
    }

    Get.dialog(
      Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: [
          Padding(
            padding: const EdgeInsets.symmetric(horizontal: 40),
            child: Container(
              decoration: const BoxDecoration(
                color: Colors.white,
                borderRadius: BorderRadius.all(
                  Radius.circular(20),
                ),
              ),
              child: Padding(
                padding: const EdgeInsets.all(20.0),
                child: Material(
                  child: Column(
                    children: [
                      const SizedBox(height: 10),
                      Icon(
                        iconData,
                        color: iconColor,
                        size: 50,
                      ),
                      const SizedBox(height: 10),
                      Text(
                        title,
                        style: const TextStyle(
                            fontSize: 24, fontWeight: FontWeight.bold),
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 20),
                      Text(
                        message,
                        style: const TextStyle(fontSize: 18),
                        textAlign: TextAlign.center,
                      ),
                      const SizedBox(height: 50),
                      
                      Row(
                        children: [
                          Expanded(
                            child: ElevatedButton(
                              style: ElevatedButton.styleFrom(
                                foregroundColor: const Color(0xFFFFFFFF),
                                backgroundColor: Colors.blue,
                                minimumSize: const Size(0, 45),
                                shape: RoundedRectangleBorder(
                                  borderRadius: BorderRadius.circular(8),
                                ),
                              ),
                              onPressed: () {
                                callback != null ? callback() : null;
                              },
                              child: Text(
                                style: const TextStyle(fontSize: 18),
                                LabelKeys.ok.tr,
                              ),
                            ),
                          ),
                        ],
                      ),
                    ],
                  ),
                ),
              ),
            ),
          ),
        ],
      ),
    );
  }
}

现在你可以在任何地方使用它

final dialog = Get.find<DialogService>();



dialog.showAlert(
  '标题',
  '这是一个普通的信息对话框'
);


dialog.showAlert(
  '错误',
  '这是一个错误警告',
  dialogType: DialogType.error
);

5. 高级 API 用法

GetX 里还有很多高级 API 可以使用,如下面这些


Get.arguments


Get.previousRoute


Get.rawRoute


Get.routing


Get.isSnackbarOpen


Get.isDialogOpen


Get.isBottomSheetOpen


GetPlatform.isAndroid
GetPlatform.isIOS
GetPlatform.isMacOS
GetPlatform.isWindows
GetPlatform.isLinux
GetPlatform.isFuchsia


GetPlatform.isMobile
GetPlatform.isDesktop
GetPlatform.isWeb




Get.height
Get.width


Get.context


Get.contextOverlay

...

你可以在这里找到更详细的说明。

6. 总结

GetX 功能强大,其节省了我很多时间,而且很容易理解,它还有一个非常活跃和乐于助人的社区,如果有任何问题基本上都可以在他们的社区中找到解决方案,而且还可以安装 VSCode 扩展以帮助构建 GetX 项目。 如果你对 GetX 有其他想法的话,也欢迎给我留言哦 :)

更多精彩文章,请关注我,或者到我的博客网站:

代码部落中文站

代码部落英文站

如果您觉得这篇文章有帮助,请点个赞吧~

分享文章

相关文章

更多文章 →
flutter2026-07-01
Dart 入门回顾
1\. Dart 是什么 Dart 是一门类型安全、支持面向对象、支持异步编程的语言。它的语法和 JavaScript、TypeScript、Java、C# 都有一些相似之处。 Dart 常见使用场景: Flutter 跨平台应用开发 命令行工具 Web 应用 服务端接口或脚本 作为学习现代类型语言的入口 Dart 的几个核心特点: 静态类型:变量、函数返回值、参数都有类型。 类型推断:很多时候可以用 ,让 Dart 自动推断类型。 空...
学习
flutter2026-07-01
Flutter 常用 Widget 入门
Flutter 的核心思想是:一切皆 Widget。文字、图片、按钮、页面结构、布局、间距、滚动、动画、手势,几乎都由 Widget 组合出来。 1\. Widget 先分成几类理解 | 类别 | 常用 Widget | 解决什么问题 | | | | | | 应用入口 | 、 | 搭建应用外壳、主题、路由 | | 页面骨架 | 、 、 | 标准页面结构 | | 基础显示 | 、 、 | 显示文字、图标、图片 | | 布局容器 | 、 、...
学习
flutter2024-11-21
在 Flutter 中使用 webview_flutter 4.0
1. 2. ,已入选 3. 4. 本文是第 4 篇,定位是新手入门,介绍和演示 webview 的基础用法。最后还介绍了事件处理的技巧。因为是补写的,所以本来应该首发的却发在最后了。 环境准备已经在 说过了,不再赘述。既然是新手入门,就会本着详尽的原则,而且会多配示例。 获取页面信息 获取页面 title 核心方法:controller.getTitle 完整示例,放在 main.dart 就能运行。运行示例,点击获取 title 的按...
学习
flutter2024-11-06
flutter android端运行报错Namespace not specified. Specify a namespace in the module‘s build file
在使用flutterSDK 3.24之后发现新建项目,项目默认都是基于AGP8了,由于 AGP 开始强制要求 namespace 配置,不仅仅是主工程,也包括第三方库都强制要求 namespace 。但是我们在引入第三方库的时候会发现部分库没能及时的去做相关配置,这也就导致了我们会出现标题错误: 出现这个问题,我们可能会通过去找到本地缓存的第三方库代码去修改它的build.gradle和AndroidManifest.xml来解决缺失...
学习
flutter2024-11-03
Flutter中如何实现APP升级功能背景
背景 每个App应用中一定会包含的功能 ,App的运行平台不同实现升级功能的方式也就不同,基本分类如下所示。本文主要聚焦在使用Flutter框架开发的应用在安卓系统内如何更新,以及所涉及要点梳理。 IOS系统 Andriod系统 官网下载 各类应用市场 (本文聚焦点) 功能清单(按步骤) UpgradeCard组件(展示升级内容) 进行新旧版本对比判断是否需要更新 下载更新然后安装应用 UpgradeCard组件 组件需要包含基本标题、...
学习
flutter2024-11-03
Flutter应用内更新App版本使用flutter_downloader而非ota_update
做当前App应用内更新的前提:用户可以在App更新的时候还可以使用软件,不能让他一直等待下载,关闭下载组件后,打开组件还能继续看到他的进度,并且用户锁屏或者短暂退出APP后需要保证任务不能总是挂掉,这里没有用到保活,而是需要用到生命周期去让他暂停和恢复,所以当前app还要有一个全局管理类保存任务id。UI图在最后 (插件文档说明可以通知栏显示,但是我用的这个版本在100%下完后总是failed,而不是complete,就不会很友好,后面...
学习

评论

请登录后发表评论

去登录
加载评论中...

目录