Skip to content

GetX 状态与路由 ​

一、安装 ​

bash
flutter pub add get

二、初始化 ​

将默认的 MaterialApp 替换为 GetMaterialApp

三、状态管理 ​

1. 定义响应式变量 ​

只需在变量的末尾加上一个 .obs 就可将变量定义为响应式变量

dart
var count = 0.obs;

响应式变量可以用在任何类型上

dart
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;

2. 更新响应式变量 ​

更新基础数据类型

dart
count.value = 2

更新 List 数据类型

dart
class Controller extends GetxController {
  List list1 = [1];
  var count = [].obs;
  increment() {
    count.addAll(list1);
  }
}

更新 Map 数据类型某个值

dart
user.update((value) {
  value?.name = "123";
});

更新 Map 数据类型

dart
user({'name': 'zhangsan', 'age': 20});

注意:如果没有使用 obs,则需手动调用 update() 进行更新,并且在 view 中使用 GetBuilder

dart
class Controller extends GetxController {
  int count = 0;
  increment() {
    count++;
    update();
  }
}
dart
//view
GetBuilder<Controller>(
  builder: (_) => Text("Clicks: ${c.count}"),
)

3. 状态管理器 ​

GetX 提供了使用 Controller 来管理状态,实现一个自定义 Controller 类继承自 GetxController

dart
class Controller extends GetxController {
  var count = 0.obs;
  increment() => count++; // increment() => count.value = 2
}

4. 注册依赖 Get.put ​

将状态管理器注入到 GetX 中,方便自身或子路由调用

dart
//可通过c访问存储的状态
final CounterController controller = Get.put(CounterController());

Get.put<CounterController>(CounterController(), permanent: true);
Get.put<CounterController>(CounterController, tag: "counter");

permanent:是否永久,默认 false 当实例不再使用时会进行销毁,true 则会一直保留

tag:标签,用于区分同一个类不同实例

5. 异步注册依赖 Get.putAsync ​

dart
Get.putAsync(() async {
  final prefs = await SharedPreferences.getInstance();
  await prefs.setInt('counter', 12345);
  return prefs;
});

跟 put 一样,同样拥有 permanent 和 tag 参数,且作用一样。

6. 懒注入 Get.lazyPut ​

延迟初始化,在需要用到的时候才会初始化实例对象,即第一次 find 某一个类的时候才会进行初始化

dart
///只有当第一次使用Get.find<CounterController>时,CounterController才会被调用。
Get.lazyPut<CounterController>(() => CounterController());

Get.lazyPut<CounterController>(
  () {
    return CounterController();
  },
  tag: Math.random().toString(),
  fenix: true
)

7. 界面更新 ​

在界面上使用响应式变量只需在使用变量的控件上包裹 Obx 即可实现响应式更新

dart
//先注册
final Controller c = Get.put(Controller());
//后更新
Obx(() => Text("Clicks: ${c.count}"))

除了使用 Obx 实现界面数据自动刷新外,GetX 提供了多种手动方式对响应式变量进行数据变化监听

dart
///每次`count`变化时调用。
ever(count, (newValue) => print("$newValue has been changed"));

///只有在变量count在第一次被改变时才会被调用。
once(count, (newValue) => print("$newValue was changed once"));

///防DDos - 每当用户停止输入1秒时调用,例如。
debounce(count, (newValue) => print("debouce$newValue"), time: Duration(seconds: 1));

///忽略1秒内的所有变化,只有最后一次会触发回调。
interval(count, (newValue) => print("interval $newValue"), time: Duration(seconds: 1));

8. 获取实例 Get.find ​

在子路由中,可以使用 Get.find 找到对应的控制器

dart
final Controller c = Get.find();

//通过 tag 获取
final Controller c = Get.find("counter");

9. 生命周期 ​

通知 GetX 依赖注入系统:

onInit()、onReady()、onClose()

dart
import 'package:get/get.dart';

class SplashController extends GetxController {
  var opacityLevel = 0.0.obs;

  @override
  void onInit() {
    super.onInit();
    Future.delayed(const Duration(milliseconds: 200), () {
      opacityLevel.value = 1.0;
    });
  }

  @override
  void onReady() {}

  @override
  void onClose() {}
}

10. 计时器示例 ​

dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
      title: 'Welcome to Flutter',
      home: Home(),
    );
  }
}

class Home extends StatelessWidget {
  @override
  Widget build(context) {
    // 使用Get.put()实例化Controller类,对所有子路由有效
    final Controller c = Get.put(Controller());

    return Scaffold(
        // Use Obx(()=> to update Text() whenever count is changed.
        appBar: AppBar(title: Obx(() => Text("Clicks: ${c.count}"))),
        body: Center(
            child: ElevatedButton(
                child: Text("Go to Other"), onPressed: () => Get.to(Other()))),
        floatingActionButton: FloatingActionButton(
            child: Icon(Icons.add), onPressed: c.increment));
  }
}

class Other extends StatelessWidget {
  // 查找另一个页面正在使用的控制器并重定向到该控制器
  final Controller c = Get.find();

  @override
  Widget build(context) {
    // 访问更新后的count变量
    return Scaffold(appBar: AppBar(), body: Center(child: Text("${c.count}")));
  }
}

class Controller extends GetxController {
  var count = 0.obs;
  increment() {
    count.value++;
  }
}

四、路由管理 ​

1. 普通路由 ​

dart
// 路由跳转 to
Get.to(CounterPage());

// 路由传参 arguments
Get.to(CounterPage(), arguments: count);

// 获取参数 Get.arguments
dynamic args = Get.arguments;

// 没有返回的路由跳转 off
Get.off(CounterPage());

// 进入下一个界面并取消之前的所有路由 offAll
Get.offAll(CounterPage());

// 路由返回 back
Get.back();

// 返回传参
Get.back(result: 'success');

// 获取返回参数
var data = await Get.to(CounterPage());

2. 别名路由 ​

首先创建一个自定义的类,用于统一配置路由映射关系:

dart
class RouteGet {
  /// page name
  static const String counter = "/counter";
  static const String other = "/other";

  ///pages map
  static final List<GetPage> getPages = [
    GetPage(
      name: counter,
      page: () => CounterPage(),
    ),
    GetPage(
      name: other,
      page: () => Other(),
    ),
  ];
}

然后在 GetMaterialApp 进行 initialRoute 和 getPages 的配置,即初始页面和路由映射集合

dart
class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return GetMaterialApp(
        title: 'Flutter Demo',
        initialRoute: RouteGet.counter, //初始页面
        getPages: RouteGet.getPages, //路由集合
        theme: ThemeData(
          primarySwatch: Colors.blue,
        ));
  }
}

路由跳转:使用方法与普通路由基本相同,只是方法上多了 Named

dart
// 路由跳转
Get.toNamed(RouteGet.other)

// 传参
Get.toNamed(RouteGet.other, arguments: {"name": "aaaa"})

// 接参
final dynamic args = Get.arguments;

3. Bindings ​

Bindings 主要是配合路由进行使用,当通过 GetX 路由进入页面时,会自动调用 dependencies 方法,可以在这里进行依赖关系的注册等

dart
import 'package:get/get.dart';

import 'splash_controller.dart';

class SplashBinding extends Bindings {
  @override
  void dependencies() {
    Get.lazyPut(() => SplashController());
  }
}

在配置路由的时候使用

dart
GetPage(
    name: splashPage,
    page: () => const SplashPage(),
    binding: SplashBinding()),

4. routingCallback ​

在跳转前做些事情,比如判断是否登录

dart
GetMaterialApp(
  routingCallback: (routing) {
    if (routing.current == '/second') {
      // 如果登录。。。
    }
  }
)

5. 路由懒加载 ​

  1. 定义一个 Future 组件

    dart
    //定义一个包裹组件
    class ContainerAsyncRouterPage extends StatelessWidget {
      final Future libraryFuture;
    
      final WidgetBuilder child;
    
      ContainerAsyncRouterPage(this.libraryFuture, this.child);
    
      @override
      Widget build(BuildContext context) {
        return FutureBuilder(
            future: libraryFuture,
            builder: (c, s) {
              if (s.connectionState == ConnectionState.done) {
                if (s.hasError) {
                  return Scaffold(
                    appBar: AppBar(),
                    body: Container(
                      alignment: Alignment.center,
                      child: Text(
                        'Error: ${s.error}',
                        style: TextStyle(color: Colors.red),
                      ),
                    ),
                  );
                }
                return child.call(context);
              }
              return Scaffold(
                appBar: AppBar(),
                body: Container(
                  alignment: Alignment.center,
                  child: CircularProgressIndicator(),
                ),
              );
            });
      }
    }
  2. 修改引入方式

    dart
    import 'package:flutter_application_1/pages/counterPage.dart'
        deferred as counter_page;
    import 'package:flutter_application_1/pages/other.dart' deferred as other_page;
    
    class RouteGet {
      /// page name
      static const String counter = "/counter";
      static const String other = "/other";
    
      ///pages map
      static final List<GetPage> getPages = [
        GetPage(
          name: counter,
          page: () {
            return ContainerAsyncRouterPage(counter_page.loadLibrary(), (context) {
              return counter_page.CounterPage();
            });
          },
        ),
        GetPage(
            name: other,
            page: () {
              return ContainerAsyncRouterPage(other_page.loadLibrary(), (context) {
                return other_page.Other();
              });
            }),
      ];
    }

运行 flutter build web 查看打包效果

五、小部件 ​

1. SnackBars ​

dart
Get.snackbar('Hi', 'i am a modern snackbar');

2. Dialogs ​

打开一个默认的 Dialog:

dart
Get.defaultDialog(
  onConfirm: () => print("Ok"),
  middleText: "Dialog made in 3 lines of code"
);

打开自定义的 Dialog:

dart
Get.dialog(YourDialogWidget());

3. BottomSheets ​

Get.bottomSheet 类似于 showModalBottomSheet,但不需要 context:

dart
Get.bottomSheet(
  Container(
    child: Wrap(
      children: <Widget>[
        ListTile(
          leading: Icon(Icons.music_note),
          title: Text('Music'),
          onTap: () => {}
        ),
        ListTile(
          leading: Icon(Icons.videocam),
          title: Text('Video'),
          onTap: () => {},
        ),
      ],
    ),
  )
);

六、GetUtils ​

GetX 还提供了很多工具方法,可以使用 GetUtils 调用,比如判断是否是邮箱,判断文件格式类型等

除此之外 GetX 还提供了一些扩展方法

dart
//检查应用程序在哪个平台上运行。
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>()

七、国际化 ​

1. 定义一个专门存放字符串 key 的类 ​

方便做 key 值提示

dart
// 新建i18/str_res_keys.dart
class SR{
  static const hello = 'hello';
}

2. 新建语言资源文件 ​

新建 i18/str_res_en.dart 与 i18/str_res_zh.dart 用于存放两种语言

dart
//str_res_en.dart
import './str_res_keys.dart';

const en_US_res = {
  SR.hello: 'Hello World',
};
dart
//str_res_zh.dart
import './str_res_keys.dart';

const zh_CN_res = {
  SR.hello: '你好 世界',
};

3. 创建一个语言资源类 ​

创建一个语言资源类继承自 GetX 的 Translations,实现 get keys

dart
//main.dart

import 'i18/str_res_en.dart';
import 'i18/str_res_zh.dart';

class StringRes extends Translations {
  @override
  Map<String, Map<String, String>> get keys =>
      {'zh_CN': zh_CN_res, 'en_US': en_US_res};
}

4. 在 GetMaterialApp 中进行配置 ​

dart
GetMaterialApp(
  translations: StringRes(),
  locale: const Locale('zh', 'CN'), //默认使用的语言
  fallbackLocale: Locale('en', 'US') //没有默认语言时使用的资源
  ...
);

5. 使用 ​

dart
import '../i18/str_res_keys.dart';

Text(SR.hello.tr),

6. 更改语言 ​

dart
Get.updateLocale(const Locale('en', 'US'))