源码地址
在 Flutter 中,Get 是来自 get 包的一个轻量级、功能强大的状态管理与路由框架,常用于:

  • 状态管理
  • 路由管理
  • 依赖注入(DI)
  • Snackbar / Dialog / BottomSheet 管理
  • 本地化(多语言)

下面是 get 的常见用法介绍:


✅ 1. 安装

pubspec.yaml 中添加依赖:

dependencies:get: ^4.6.6

✅ 2. 基础设置

main.dart 使用 GetMaterialApp 替代 MaterialApp

import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(GetMaterialApp(home: HomePage(),));
}

✅ 3. 状态管理用法

3.1 使用 GetxController 管理状态:

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

3.2 页面中使用:

class HomePage extends StatelessWidget {final CounterController c = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("GetX Example")),body: Center(child: Obx(() => Text("Count: ${c.count}")),),floatingActionButton: FloatingActionButton(onPressed: c.increment,child: Icon(Icons.add),),);}
}

明白了!你问的是 Flutter 的 GetX 框架中状态管理的几种方式,包括如何定义状态、如何更新状态、以及它们的区别与用法示例。


✅ GetX 状态管理的三种主要方式

类型特点使用方式关键字
响应式(Reactive)基于 .obs 变量 + Obx 小部件Rx / .obs + Obx
简单状态(Simple)基于 GetBuilder 刷新 widgetGetBuilder
混合状态(Worker)使用 ever/once 监听变化Workers

🧪 方式一:响应式(Reactive State)Rx + Obx

✅ 使用场景:最常用,响应式自动刷新,无需手动更新。

🧵 示例:

class CounterController extends GetxController {var count = 0.obs;void increment() {count++;}
}class CounterPage extends StatelessWidget {final CounterController c = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(body: Center(child: Obx(() => Text("Count: ${c.count}"))),floatingActionButton: FloatingActionButton(onPressed: c.increment,child: Icon(Icons.add),),);}
}

✅ 特点:

  • 自动刷新 UI(通过 Obx
  • 状态类型必须是 .obs 形式或 Rx<Type> 类型
  • 适合实时反应数据变化

🧪 方式二:简单状态(Simple State)使用 GetBuilder<T>

✅ 使用场景:性能高、不需要响应式,只在需要时手动刷新。

🧵 示例:

class CounterController extends GetxController {int count = 0;void increment() {count++;update(); // 手动通知刷新}
}class CounterPage extends StatelessWidget {Widget build(BuildContext context) {return GetBuilder<CounterController>(init: CounterController(),builder: (c) => Scaffold(body: Center(child: Text("Count: ${c!.count}")),floatingActionButton: FloatingActionButton(onPressed: c.increment,child: Icon(Icons.add),),),);}
}

✅ 特点:

  • update()GetBuilder 中的 widget 刷新
  • 性能更高,无响应式开销
  • 不需要使用 .obs

🧪 方式三:混合监听 Worker(更高级)

✅ 使用场景:在状态变化时执行副作用逻辑(如网络请求、打印等)

🧵 示例:

class CounterController extends GetxController {var count = 0.obs;void onInit() {ever(count, (value) => print("count changed: $value"));super.onInit();}void increment() => count++;
}

✅ 常用 Worker 方法:

方法说明
ever()每次变化都会触发
once()第一次变化触发一次
debounce()用户停止输入一段时间后才触发(适合搜索)
interval()一定时间内只触发一次(节流)

🔄 状态更新对比

方式自动刷新手动调用响应式变量性能
Obx中等
GetBuilder✅(update()
Worker用于副作用

🧠 总结建议:

使用目的推荐方式
快速开发、响应式 UIRx + Obx
高性能、低耦合局部刷新GetBuilder
响应状态变化做副作用Workers 系列

Obx 会监听 所有.obs 的变化吗,还是可以指定,他是与某个Controller绑定吗


🎯 Obx 的监听机制:只监听它内部使用到的 .obs 变量

✅ 它不会监听所有 .obs,只监听你在 Obx 函数体中实际使用到的 .obs


🔍 示例说明:

class MyController extends GetxController {var a = 0.obs;var b = 0.obs;
}
final c = Get.put(MyController());Obx(() => Text("${c.a}")); // ✅ 只会监听 c.a 的变化,不管 c.b

如果 c.b 改变了,Obx 不会重建,因为你没有在 Obx 里使用它。


🧠 你问的三个点详解:

1. ✅ Obx 会监听所有 .obs 吗?

❌不会。
只监听 Obx 函数内部使用到的 .obs,不会管你定义了多少个 .obs


2. ✅ 可以指定监听变量吗?

✅是的,通过写在 Obx 内部来“指定”。

你只要在 Obx 里访问了某个 .obs 变量,它就自动监听它。

例如:

Obx(() => Text("${controller.username}")); // 只监听 username

3. ❓ Obx 是与某个 Controller 绑定的吗?

❌不是绑定关系,它只是“用了”某个 Controller 里的 .obs

你甚至可以在 Obx 中混用多个 controller 的 .obs

Obx(() => Text("${c1.a} - ${c2.b}")); // 同时监听 c1 和 c2 的状态

✅ 总结一句话:

Obx 监听谁,取决于你在它里面访问了哪些 .obs 变量,与是否来自某个 Controller 无关。


如果你想实现多个 Obx 分别监听不同状态避免无关刷新,这就是 GetX 的优势之一,写法也很清晰。

这里是一个完整示例,展示如何用多个 Obx 精确监听不同的 .obs 状态变量,从而避免不必要的重建 —— 每个 Obx 只响应自己的数据变化。


✅ 示例场景:两个计数器,两个 Obx,互不干扰

import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(GetMaterialApp(home: CounterPage()));
}/// 控制器,包含两个独立的状态变量
class CounterController extends GetxController {var countA = 0.obs;var countB = 0.obs;void incrementA() => countA++;void incrementB() => countB++;
}

✅ 页面布局(两个 Obx 精准监听)

class CounterPage extends StatelessWidget {final controller = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("多个 Obx 独立监听示例")),body: Column(mainAxisAlignment: MainAxisAlignment.center,children: [// 只监听 countAObx(() => Text("计数器 A:${controller.countA}",style: TextStyle(fontSize: 20))),ElevatedButton(onPressed: controller.incrementA,child: Text("A +1"),),SizedBox(height: 40),// 只监听 countBObx(() => Text("计数器 B:${controller.countB}",style: TextStyle(fontSize: 20))),ElevatedButton(onPressed: controller.incrementB,child: Text("B +1"),),],),);}
}

✅ 运行效果

  • 点击「A +1」只会刷新第一个 Text,第二个不动。
  • 点击「B +1」只会刷新第二个 Text,第一个不动。

🧠 总结要点

做法原因/作用
多个 Obx精准监听、独立更新
.obs 拆成多个变量分离状态,避免串联刷新
不要在一个 Obx 里访问多个状态否则它会“监听多个”,导致全部重建

如果你要优化一个页面性能,建议就是:

  • 把状态拆得更小(多个 .obs);
  • 把 UI 拆得更细(多个 Obx);
  • 这样能避免不必要的 widget rebuild。

GetBuilder 会监听 所有.obs 的变化吗,还是可以指定,他是与某个Controller绑定吗

我们来精准拆解 GetBuilder<T> 的监听机制


✅ 结论先行

问题答案
GetBuilder<T> 会监听所有 .obs 的变化吗?不会。它不监听 .obs,和 .obs 完全无关。
GetBuilder 可以指定监听变量吗?可以手动控制刷新范围,但不是通过变量监听,而是 id 区分。
它是与某个 Controller 绑定的吗?是的T 就是绑定的 Controller 类型。

🧠 本质区别:GetBuilder.obs 没有关系

GetBuilder 的刷新是由 controller.update() 主动触发的,和你有没有使用 .obs 完全无关。

✅ 示例说明:

class MyController extends GetxController {int countA = 0;int countB = 0;void incrementA() {countA++;update(['a']); // 指定 id = 'a' 的 GetBuilder 会刷新}void incrementB() {countB++;update(['b']); // 指定 id = 'b' 的 GetBuilder 会刷新}
}
class MyPage extends StatelessWidget {final controller = Get.put(MyController());Widget build(BuildContext context) {return Scaffold(body: Column(children: [GetBuilder<MyController>(id: 'a',builder: (_) => Text("A: ${_.countA}"),),GetBuilder<MyController>(id: 'b',builder: (_) => Text("B: ${_.countB}"),),ElevatedButton(onPressed: controller.incrementA, child: Text("A +1")),ElevatedButton(onPressed: controller.incrementB, child: Text("B +1")),]),);}
}

update() 概要:

调用方式刷新范围
update()所有使用该 Controller 的 GetBuilder
update(['a'])只刷新 id 为 'a'GetBuilder
update(['b', 'c'])同时刷新 'b''c'

✅ 总结对比表:Obx vs GetBuilder

特性Obx(响应式)GetBuilder(手动)
是否与 .obs 有关✅ 是❌ 无关
是否自动刷新✅ 会自动监听 .obs 的变化❌ 不会,需手动调用 update()
是否能精细控制刷新区域✅ 拆多个 Obx✅ 支持 id 精细控制
性能表现中等(需响应式依赖追踪)高(只有调用 update() 才重建)
与 Controller 绑定❌ 没有绑定(只用了变量)✅ 强绑定,必须指定类型 T

如果你是性能优先、UI固定、只需要响应某些点击/事件,推荐 GetBuilder
如果你需要动态响应式 UI(比如登录状态、购物车数量),用 Obx 更适合。


🔍 Worker 监听机制简述

Worker 是 GetX 中用来监听特定 .obs 状态变化并执行副作用操作的一组工具函数,例如 everoncedebounceinterval 等。


✅ 问题逐条回答

问题回答
Worker 会监听所有 .obs 的变化吗?不会,只监听你显式传入的某个 .obs 变量。不会自动监听全部。
可以指定监听哪个 .obs 吗?必须指定监听哪个 .obs,Worker 的第一个参数就是监听目标。
Worker 是否与某个 Controller 绑定?✅ 通常放在某个 Controller 中使用,但绑定的是 .obs不是 Controller 本身

🧪 示例:监听某个 .obs 变量的变化

class MyController extends GetxController {var count = 0.obs;var username = ''.obs;void onInit() {super.onInit();// 每次 count 改变时打印ever(count, (value) => print("count changed: $value"));// 只监听 username 第一次改变once(username, (value) => print("username changed once: $value"));// 用户停止输入 800ms 后才触发(如搜索)debounce(username, (value) => print("debounced: $value"), time: Duration(milliseconds: 800));// 每 2 秒最多触发一次interval(count, (value) => print("interval: $value"), time: Duration(seconds: 2));}void increment() => count++;void setUsername(String name) => username.value = name;
}

🧠 总结:Worker 用法与绑定机制

项目说明
监听对象必须手动传入某个 .obs(如 count, username
可监听多个变量✅ 可以在一个 controller 里设置多个 ever() 等,监听多个 .obs
生命周期绑定通常在 onInit() 中设置,自动随 controller 生命周期注销
与 Controller 关系✅ 通常放在 controller 中,但不是监听整个 controller,仅监听你指定的 .obs

✅ Worker 适用场景

场景使用方法
搜索输入防抖(停止输入才查)debounce(textObs, callback)
防止按钮频繁点击interval(buttonTapObs, callback)
登录状态变化提示ever(isLoggedInObs, callback)
页面加载后只响应一次(如埋点)once(pageReadyObs, callback)

好的,这里是一个完整示例:
使用 GetX 的 Worker 机制 来监听两个 .obs

  1. 用户名输入框 → 使用 debounce 实现 防抖搜索
  2. 登录状态 → 使用 ever 实现登录提示(弹 Snackbar)

✅ 项目结构预览

lib/
├── main.dart
└── controller.dart  ← 状态 & Worker 逻辑

📄 controller.dart

import 'package:get/get.dart';
import 'package:flutter/material.dart';class AuthController extends GetxController {// 状态变量var username = ''.obs;var isLoggedIn = false.obs;void onInit() {super.onInit();// 防抖搜索:用户停止输入 800ms 后触发搜索逻辑debounce(username, (value) {print("🔍 执行搜索:$value");// 模拟调用搜索 API}, time: Duration(milliseconds: 800));// 登录状态变化提示ever(isLoggedIn, (status) {if (status == true) {Get.snackbar("登录成功", "欢迎你,${username.value} 🎉");} else {Get.snackbar("退出登录", "已成功退出 👋");}});}void login() {if (username.value.isNotEmpty) {isLoggedIn.value = true;}}void logout() {isLoggedIn.value = false;}
}

📄 main.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';void main() {runApp(GetMaterialApp(home: LoginPage()));
}class LoginPage extends StatelessWidget {final auth = Get.put(AuthController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("GetX Worker 示例")),body: Padding(padding: const EdgeInsets.all(24),child: Column(children: [TextField(decoration: InputDecoration(labelText: "用户名(用于模拟搜索)"),onChanged: (value) => auth.username.value = value,),SizedBox(height: 20),Obx(() => auth.isLoggedIn.value? Column(children: [Text("已登录为:${auth.username.value}",style: TextStyle(fontSize: 18)),SizedBox(height: 10),ElevatedButton(onPressed: auth.logout,child: Text("退出登录"),),],): ElevatedButton(onPressed: auth.login,child: Text("登录"),)),],),),);}
}

✅ 运行效果

  • 输入用户名时,不会立刻打印“搜索”,而是停止输入 800ms 后触发一次模拟搜索(防抖)。
  • 点击“登录”后,isLoggedIntrue,会自动触发 ever,显示 Snackbar 提示“登录成功”。

🧠 技术要点

功能技术手段
搜索输入防抖debounce(username, ...)
登录状态变化提示ever(isLoggedIn, ...)
Snackbar 弹窗Get.snackbar(...)
状态绑定 UIObx(() => ...)
控制器全局管理Get.put(AuthController())

扩展上一个 Worker 示例,加入以下两个功能:


✅ 目标功能扩展

1. ✅ 模拟异步搜索请求(带加载动画)

  • 当用户输入停止后,模拟调接口(2秒)
  • 显示搜索中动画
  • 请求完成后显示「已搜索:XXX」

2. ✅ 登录成功自动跳转到欢迎页面

  • 用户输入用户名,点击登录
  • 弹出 Snackbar 提示
  • 自动跳转到欢迎页面(WelcomePage

📦 新状态变量(在 Controller 中添加)

var isSearching = false.obs;
var searchResult = ''.obs;

📄 controller.dart(更新后的)

import 'package:get/get.dart';
import 'package:flutter/material.dart';class AuthController extends GetxController {var username = ''.obs;var isLoggedIn = false.obs;var isSearching = false.obs;var searchResult = ''.obs;void onInit() {super.onInit();// 防抖搜索debounce(username, (val) async {if (val.toString().isEmpty) return;isSearching.value = true;searchResult.value = '';print("开始搜索:$val");// 模拟异步接口调用await Future.delayed(Duration(seconds: 2));searchResult.value = "搜索完成:$val";isSearching.value = false;}, time: Duration(milliseconds: 800));// 登录提示 & 自动跳转ever(isLoggedIn, (status) {if (status == true) {Get.snackbar("登录成功", "欢迎你,${username.value} 🎉");Future.delayed(Duration(milliseconds: 800), () {Get.off(WelcomePage()); // 跳转欢迎页});} else {Get.snackbar("退出登录", "已成功退出 👋");}});}void login() {if (username.value.isNotEmpty) {isLoggedIn.value = true;}}void logout() {isLoggedIn.value = false;}
}

📄 main.dart

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'controller.dart';void main() {runApp(GetMaterialApp(home: LoginPage()));
}class LoginPage extends StatelessWidget {final auth = Get.put(AuthController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("GetX Worker 扩展示例")),body: Padding(padding: const EdgeInsets.all(24),child: Column(crossAxisAlignment: CrossAxisAlignment.start,children: [TextField(decoration: InputDecoration(labelText: "用户名"),onChanged: (value) => auth.username.value = value,),SizedBox(height: 16),// 搜索中或结果Obx(() {if (auth.isSearching.value) {return Row(children: [CircularProgressIndicator(strokeWidth: 2),SizedBox(width: 8),Text("搜索中..."),],);} else if (auth.searchResult.value.isNotEmpty) {return Text(auth.searchResult.value,style: TextStyle(color: Colors.green));} else {return Container();}}),SizedBox(height: 32),Obx(() => auth.isLoggedIn.value? Column(crossAxisAlignment: CrossAxisAlignment.start,children: [Text("已登录为:${auth.username.value}",style: TextStyle(fontSize: 18)),SizedBox(height: 10),ElevatedButton(onPressed: auth.logout,child: Text("退出登录"),),],): ElevatedButton(onPressed: auth.login,child: Text("登录"),)),],),),);}
}class WelcomePage extends StatelessWidget {Widget build(BuildContext context) {final username = Get.find<AuthController>().username.value;return Scaffold(appBar: AppBar(title: Text("欢迎页面")),body: Center(child: Text("欢迎回来,$username!", style: TextStyle(fontSize: 24)),),);}
}

✅ 运行效果

操作效果
输入用户名停止输入 800ms 后开始「搜索」,2秒后显示搜索结果
点击登录按钮弹出「登录成功」提示,800ms 后自动跳转欢迎页
欢迎页展示当前用户名

🧠 技术亮点

功能技术手段
防抖搜索debounce(obs, callback)
登录提示ever(isLoggedIn, callback)
页面跳转Get.off()
状态绑定显示Obx(() => ...)
异步处理加载动画.obs + Future + CircularProgressIndicator

是否还需要加上:

  • ✅ 登录失败提示(用户名为空时)
  • ✅ 登录后的 token 保存 / 本地持久化
  • ✅ 从登录页自动恢复登录状态

✅ 4. 路由管理(导航)

GetX 的路由(导航)管理功能非常强大、简洁,无需 context,还支持命名路由、无命名路由、参数传递、动画控制等。


📦 一、基础配置:使用 GetMaterialApp

main.dart 中使用:

void main() {runApp(GetMaterialApp(initialRoute: '/',getPages: AppRoutes.routes,));
}

📁 二、定义路由表(推荐结构)

创建 routes.dart 文件:

import 'package:get/get.dart';
import 'home_page.dart';
import 'detail_page.dart';class AppRoutes {static final routes = [GetPage(name: '/', page: () => HomePage()),GetPage(name: '/detail', page: () => DetailPage()),];
}

🚀 三、导航跳转方式

✅ 1. 普通跳转(非命名)

Get.to(DetailPage());

✅ 2. 命名路由跳转

Get.toNamed('/detail');

✅ 3. 返回上一页

Get.back();

✅ 4. 替换当前页(不能返回)

Get.off(DetailPage());
Get.offNamed('/detail');

✅ 5. 清空历史并跳转(常用于登录成功)

Get.offAllNamed('/home');

🎯 四、参数传递方式

✅ 方式一:通过 arguments 传递参数(推荐)

🔁 传参:
Get.toNamed('/detail', arguments: {'id': 123, 'title': '测试'});
🧾 接收:
final args = Get.arguments as Map;
print(args['id']); // 123

✅ 方式二:通过 URL 参数(路径参数)

📥 定义路由时设置参数:
GetPage(name: '/detail/:id',page: () => DetailPage(),
)
🔁 传参:
Get.toNamed('/detail/888');
🧾 接收:
final id = Get.parameters['id']; // "888"
✅ 也支持 query:
Get.toNamed('/detail/888?title=测试');
final title = Get.parameters['title']; // "测试"

✅ 五、完整例子

📄 main.dart

void main() {runApp(GetMaterialApp(initialRoute: '/',getPages: [GetPage(name: '/', page: () => HomePage()),GetPage(name: '/detail/:id', page: () => DetailPage()),],));
}

📄 home_page.dart

class HomePage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(body: Center(child: ElevatedButton(onPressed: () => Get.toNamed('/detail/123?title=你好'),child: Text("去详情页"),),),);}
}

📄 detail_page.dart

class DetailPage extends StatelessWidget {Widget build(BuildContext context) {final id = Get.parameters['id'];final title = Get.parameters['title'];return Scaffold(appBar: AppBar(title: Text("详情页")),body: Center(child: Text("ID: $id, 标题: $title"),),);}
}

🧠 常用跳转方法对比总结

方法功能
Get.to(Widget())跳转新页面
Get.toNamed('/path')命名路由跳转
Get.off(...)替换当前页面
Get.offAll(...)清空栈跳转,常用于登录成功
Get.back()返回上一页
Get.arguments获取参数(Map)
Get.parameters['id']获取 URL 参数

在 GetX 中,每个路由都可以独立配置动画,通过 GetPage 的以下属性实现:


✅ 一、使用内建动画配置

你可以通过 transitiontransitionDuration 快速配置内建动画:

GetPage(name: '/detail',page: () => DetailPage(),transition: Transition.rightToLeftWithFade, // 动画类型transitionDuration: Duration(milliseconds: 400), // 动画时间
)

🎬 内建动画类型一览(Transition 枚举):

动画类型效果描述
Transition.fade淡入淡出
Transition.rightToLeft从右向左滑动进入
Transition.leftToRight从左向右滑动进入
Transition.upToDown从上往下
Transition.downToUp从下往上
Transition.rightToLeftWithFade滑动 + 淡入淡出
Transition.zoom缩放
Transition.topLevel立体层叠感(较强)

✅ 二、自定义动画 customTransition

如果内建动画不满足需求,可以自定义动画:

🔧 1. 创建自定义动画类:

class MyCustomTransition extends CustomTransition {Widget buildTransition(BuildContext context,Curve curve,Alignment alignment,Animation<double> animation,Animation<double> secondaryAnimation,Widget child,) {return SlideTransition(position: Tween<Offset>(begin: Offset(0, 1),  // 从底部进来end: Offset.zero,).animate(animation),child: child,);}
}

📌 2. 应用在 GetPage 中:

GetPage(name: '/detail',page: () => DetailPage(),customTransition: MyCustomTransition(),transitionDuration: Duration(milliseconds: 500),
)

✅ 三、全局默认过渡动画(不推荐)

如果你希望所有页面默认使用统一动画,可以在 GetMaterialApp 中配置:

GetMaterialApp(defaultTransition: Transition.fade,transitionDuration: Duration(milliseconds: 300),
)

⚠️ 缺点:所有页面一刀切,不灵活。


✅ 四、页面跳转时单独设置动画(临时跳转)

Get.to(DetailPage(),transition: Transition.zoom,duration: Duration(milliseconds: 400),curve: Curves.easeInOut,
);

🎯 总结:路由动画配置选型

方式优点使用场景
transition + duration简洁、常规页面跳转动画大多数页面跳转
customTransition灵活自定义复杂动画自定义 slide、fade、组合动画
defaultTransition快速统一默认动画小项目统一风格
Get.to() 传入动画参数单次临时跳转自定义特定跳转需要额外动画时

✅ 5. 依赖注入

GetX 的依赖注入(Dependency Injection, 简称 DI)非常强大且简单,常用于控制器、服务类的自动注册与全局获取,避免你手动管理生命周期和传递 context


🧠 为什么使用 Get 的依赖注入?

  • 不用手动传对象
  • 生命周期自动管理
  • 支持懒加载、永久实例、局部注入
  • Provider 简单很多

✅ 1. 基本用法:Get.put()(立即注入)

📌 注册依赖

final controller = Get.put(MyController());
  • 立即创建并注册
  • 可在任何地方调用 Get.find<MyController>() 来获取

✅ 2. 懒加载:Get.lazyPut()

Get.lazyPut<MyController>(() => MyController());
  • 在第一次使用时才创建
  • 更节省内存,适合大项目中很多控制器

✅ 3. 永久依赖:Get.put(..., permanent: true)

Get.put(MyService(), permanent: true);
  • 永久存在,Get.reset() 也不会清除
  • 适合如网络层、用户配置等全局服务

✅ 4. 获取依赖:Get.find<T>()

final c = Get.find<MyController>();
  • 在任意位置获取,不需要 context
  • 如果未注册,会抛错

✅ 5. 删除依赖:Get.delete<T>()

Get.delete<MyController>();
  • 销毁注册的依赖,释放内存

✅ 6. 结合页面使用示例

👇 创建一个 Controller

class CounterController extends GetxController {var count = 0.obs;void increment() => count++;
}

👇 页面注册 & 使用(推荐在页面内 Get.put()

class CounterPage extends StatelessWidget {final controller = Get.put(CounterController());Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text("计数器")),body: Center(child: Obx(() => Text("Count: ${controller.count}")),),floatingActionButton: FloatingActionButton(onPressed: controller.increment,child: Icon(Icons.add),),);}
}

✅ 7. 自动依赖绑定:使用 Bindings

适用于路由时自动注入依赖。

创建 Bindings 类:

class CounterBinding extends Bindings {void dependencies() {Get.lazyPut<CounterController>(() => CounterController());}
}

配置到路由:

GetPage(name: '/counter',page: () => CounterPage(),binding: CounterBinding(),
)

使用时跳转:

Get.toNamed('/counter'); // 会自动执行绑定

🧠 总结依赖注入方法

方法用途
Get.put()立即注入
Get.lazyPut()懒加载注入,首次使用才创建
Get.putAsync()异步创建实例
Get.find<T>()获取已注入实例
Get.delete<T>()删除实例
permanent: true保留永久实例(不被清除)
Bindings自动依赖注入,推荐配合路由使用

以下是适合中大型项目的 GetX 推荐目录结构 + 依赖注入(DI)配置方案,可直接用于你实际项目中。


🗂 推荐项目结构

lib/
├── main.dart
├── app/
│   ├── routes/
│   │   ├── app_pages.dart        ← 所有页面路由配置(含绑定)
│   │   └── app_routes.dart       ← 所有路由名定义
│   ├── bindings/
│   │   ├── auth_binding.dart     ← 登录相关依赖
│   │   └── global_binding.dart   ← 全局一次性绑定(如网络服务)
│   ├── controllers/
│   │   ├── auth_controller.dart
│   │   └── home_controller.dart
│   ├── views/
│   │   ├── login_page.dart
│   │   └── home_page.dart
│   └── services/
│       ├── api_service.dart
│       └── storage_service.dart

🧠 1. 控制器定义(如 auth_controller.dart

class AuthController extends GetxController {var isLoggedIn = false.obs;void login(String username) {isLoggedIn.value = true;}void logout() {isLoggedIn.value = false;}
}

⚙️ 2. 单个 Binding 示例(如 auth_binding.dart

class AuthBinding extends Bindings {void dependencies() {Get.lazyPut<AuthController>(() => AuthController());}
}

⚙️ 3. 全局 Binding(如 global_binding.dart

class GlobalBinding extends Bindings {void dependencies() {// 注册全局服务Get.put<ApiService>(ApiService(), permanent: true);Get.put<StorageService>(StorageService(), permanent: true);}
}

🔁 4. 路由配置(app_pages.dart)

import 'package:get/get.dart';
import '../views/login_page.dart';
import '../views/home_page.dart';
import '../bindings/auth_binding.dart';class AppPages {static final routes = [GetPage(name: '/login',page: () => LoginPage(),binding: AuthBinding(),),GetPage(name: '/home',page: () => HomePage(),),];
}

🧭 5. 启动入口(main.dart)

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'app/routes/app_pages.dart';
import 'app/bindings/global_binding.dart';void main() {runApp(GetMaterialApp(initialRoute: '/login',getPages: AppPages.routes,initialBinding: GlobalBinding(), // 注册全局依赖debugShowCheckedModeBanner: false,));
}

✅ 跳转方式示例

Get.toNamed('/home');

控制器在跳转页面时会自动注入(如果该页面设置了 binding:


🚀 小贴士:常见依赖注入场景与方式

场景推荐写法
登录页面需要用到 AuthControllerbinding: AuthBinding()
首页需要多个 controllerbinding: BindingsBuilder(() => {...})
全局单例服务(如网络、缓存)Get.put(Service(), permanent: true)
获取控制器实例final auth = Get.find<AuthController>();

🔚 总结

GetX 的依赖注入建议这样使用:

类型使用方式说明
页面级 ControllerlazyPut + Binding路由进入时自动注入
全局服务put(..., permanent: true)启动时注入,全程共享
自定义 Service单独建 services/ 目录网络、数据库、存储类都放这里
中央注册点GlobalBinding 做一次性全局注入避免在 main.dart 重复注入依赖

✅ 6. Snackbar / Dialog / BottomSheet

GetX 提供了非常方便的 Snackbar、Dialog 和 BottomSheet 管理方法,支持无 Context 调用,语法简洁,且自带动画和丰富参数。


1. Snackbar

基础用法

Get.snackbar('标题','内容信息',snackPosition: SnackPosition.BOTTOM, // 顶部还是底部duration: Duration(seconds: 3),      // 显示时间backgroundColor: Colors.blueGrey,colorText: Colors.white,
);

常用参数

参数说明
title标题文本
message内容文本
snackPosition显示位置(TOP 或 BOTTOM)
duration显示时间
backgroundColor背景颜色
colorText文字颜色
icon左侧图标
mainButton右侧按钮(Widget)

2. Dialog(弹窗)

简单对话框

Get.defaultDialog(title: '提示',middleText: '你确定要删除吗?',textConfirm: '确认',textCancel: '取消',onConfirm: () {print('确认删除');Get.back();},onCancel: () {print('取消删除');},
);

自定义内容对话框

Get.dialog(AlertDialog(title: Text('自定义标题'),content: Text('这是自定义内容'),actions: [TextButton(onPressed: () => Get.back(),child: Text('关闭'),)],),
);

3. BottomSheet(底部弹窗)

简单底部弹窗

Get.bottomSheet(Container(color: Colors.white,padding: EdgeInsets.all(16),child: Wrap(children: [ListTile(leading: Icon(Icons.photo),title: Text('照片'),onTap: () => Get.back(),),ListTile(leading: Icon(Icons.music_note),title: Text('音乐'),onTap: () => Get.back(),),],),),backgroundColor: Colors.white,shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20)),),
);

4. 关闭弹窗或 Snackbar

Get.back(); // 关闭当前弹窗、Snackbar、BottomSheet 等

5. 结合示例

ElevatedButton(onPressed: () {Get.snackbar('Hi', '这是一个消息提示');},child: Text('显示Snackbar'),
);ElevatedButton(onPressed: () {Get.defaultDialog(title: '确认',middleText: '是否删除该条数据?',textConfirm: '是',textCancel: '否',onConfirm: () {print('删除');Get.back();},);},child: Text('显示Dialog'),
);ElevatedButton(onPressed: () {Get.bottomSheet(Container(height: 200,color: Colors.white,child: Center(child: Text('这是一个底部弹窗')),),);},child: Text('显示BottomSheet'),
);

下面是一个完整 Flutter 页面示例,集成 GetX 的 Snackbar、Dialog、BottomSheet,带按钮交互,包含关闭逻辑和动画配置,方便直接拿去用。


import 'package:flutter/material.dart';
import 'package:get/get.dart';class GetUIExamplePage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('GetX Snackbar/Dialog/BottomSheet 示例'),),body: Padding(padding: const EdgeInsets.all(20),child: Column(children: [ElevatedButton(onPressed: () {Get.snackbar('提示','这是一条消息提示',snackPosition: SnackPosition.BOTTOM,duration: Duration(seconds: 4),backgroundColor: Colors.blueGrey.shade700,colorText: Colors.white,icon: Icon(Icons.info, color: Colors.white),mainButton: TextButton(onPressed: () => Get.back(),child: Text('关闭', style: TextStyle(color: Colors.white)),),);},child: Text('显示 Snackbar'),),SizedBox(height: 20),ElevatedButton(onPressed: () {Get.defaultDialog(title: '确认操作',middleText: '确定要删除这条记录吗?',textConfirm: '确认',textCancel: '取消',barrierDismissible: false,onConfirm: () {Get.back();Get.snackbar('删除', '记录已删除', snackPosition: SnackPosition.BOTTOM);},onCancel: () => Get.back(),);},child: Text('显示 Dialog'),),SizedBox(height: 20),ElevatedButton(onPressed: () {Get.bottomSheet(Container(decoration: BoxDecoration(color: Colors.white,borderRadius: BorderRadius.vertical(top: Radius.circular(20)),),padding: EdgeInsets.all(16),child: Wrap(children: [ListTile(leading: Icon(Icons.photo),title: Text('相册'),onTap: () {Get.back();Get.snackbar('选择', '点击了相册', snackPosition: SnackPosition.BOTTOM);},),ListTile(leading: Icon(Icons.camera_alt),title: Text('拍照'),onTap: () {Get.back();Get.snackbar('选择', '点击了拍照', snackPosition: SnackPosition.BOTTOM);},),ListTile(leading: Icon(Icons.cancel),title: Text('取消'),onTap: () => Get.back(),),],),),isDismissible: true,enableDrag: true,shape: RoundedRectangleBorder(borderRadius: BorderRadius.vertical(top: Radius.circular(20)),),);},child: Text('显示 BottomSheet'),),],),),);}
}

说明

  • Snackbar 带关闭按钮和图标,位置在底部,自动消失
  • Dialog 有确认和取消按钮,且点击遮罩不可关闭(barrierDismissible: false
  • BottomSheet 带圆角,上滑可拖拽关闭,包含几个选项点击时关闭并提示

你只需

Get.to(() => GetUIExamplePage());

✅ 7. 国际化(多语言)

GetX 内置了强大的本地化(国际化)支持,让你快速实现多语言切换,且使用简单灵活。


📦 基础使用步骤

1. 创建语言翻译类,继承 Translations

import 'package:get/get.dart';class Messages extends Translations {Map<String, Map<String, String>> get keys => {'en_US': {'hello': 'Hello World','login': 'Login',},'zh_CN': {'hello': '你好,世界','login': '登录',},};
}

2. 在 GetMaterialApp 中配置

GetMaterialApp(translations: Messages(),          // 绑定翻译类locale: Locale('en', 'US'),        // 默认语言fallbackLocale: Locale('en', 'US'), // 找不到翻译时的兜底语言home: MyHomePage(),
);

3. 页面中使用 .tr 获取对应语言文本

Text('hello'.tr),    // 自动根据当前语言显示文本
ElevatedButton(onPressed: () => print('login'.tr),child: Text('login'.tr),
),

4. 动态切换语言

// 切换为中文
Get.updateLocale(Locale('zh', 'CN'));// 切换为英语
Get.updateLocale(Locale('en', 'US'));

✅ 完整示例

import 'package:flutter/material.dart';
import 'package:get/get.dart';void main() {runApp(GetMaterialApp(translations: Messages(),locale: Locale('en', 'US'),fallbackLocale: Locale('en', 'US'),home: HomePage(),));
}class Messages extends Translations {Map<String, Map<String, String>> get keys => {'en_US': {'hello': 'Hello World', 'login': 'Login'},'zh_CN': {'hello': '你好,世界', 'login': '登录'},};
}class HomePage extends StatelessWidget {Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('hello'.tr)),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('login'.tr),SizedBox(height: 20),ElevatedButton(onPressed: () => Get.updateLocale(Locale('zh', 'CN')),child: Text('切换到中文'),),ElevatedButton(onPressed: () => Get.updateLocale(Locale('en', 'US')),child: Text('Switch to English'),),],),),);}
}

🧠 注意事项

  • keys 里每个 key 是 语言_国家 格式,必须严格写对
  • .trString 的扩展方法,直接调用即可
  • 语言切换后,Get 会自动通知所有使用 .tr 的 Widget 更新
  • 可以结合持久化保存用户选择的语言,下次启动时自动加载

完整的 GetX 本地化示例

  • 多语言 JSON 文件分离管理
  • 从 JSON 读取语言包
  • 语言切换后自动刷新 UI
  • 语言偏好保存到本地,下次启动自动加载

1. 准备工作:在 assets/lang/ 目录放语言 JSON 文件

assets/lang/en_US.json
assets/lang/zh_CN.json

示例 en_US.json

{"hello": "Hello World","login": "Login","logout": "Logout"
}

示例 zh_CN.json

{"hello": "你好,世界","login": "登录","logout": "退出登录"
}

2. 修改 pubspec.yaml,声明资源文件

flutter:assets:- assets/lang/en_US.json- assets/lang/zh_CN.json

3. 创建 translation_service.dart,实现从 JSON 加载翻译

import 'dart:convert';
import 'package:flutter/services.dart';
import 'package:get/get.dart';
import 'package:flutter/material.dart';class TranslationService extends Translations {static Locale? locale;static Locale fallbackLocale = Locale('en', 'US');// 用于缓存翻译Mapfinal Map<String, Map<String, String>> _translations = {};TranslationService() {_loadTranslations();}// 从 assets 加载所有语言 JSON 文件Future<void> _loadTranslations() async {final locales = ['en_US', 'zh_CN'];for (var loc in locales) {final jsonString =await rootBundle.loadString('assets/lang/$loc.json');final Map<String, dynamic> jsonMap = json.decode(jsonString);_translations[loc] = jsonMap.map((key, value) => MapEntry(key, value.toString()));}}Map<String, Map<String, String>> get keys => _translations;// 切换语言static void changeLocale(Locale newLocale) {locale = newLocale;Get.updateLocale(newLocale);}
}

注意:这里用 async 读取,建议在 main() 里先初始化好翻译资源。


4. 修改 main.dart,初始化语言资源并读取用户偏好

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'translation_service.dart';
import 'package:shared_preferences/shared_preferences.dart';void main() async {WidgetsFlutterBinding.ensureInitialized();final prefs = await SharedPreferences.getInstance();String? languageCode = prefs.getString('language_code') ?? 'en';String? countryCode = prefs.getString('country_code') ?? 'US';TranslationService.locale = Locale(languageCode, countryCode);runApp(MyApp());
}class MyApp extends StatelessWidget {final TranslationService _translationService = TranslationService();Widget build(BuildContext context) {return GetMaterialApp(translations: _translationService,locale: TranslationService.locale,fallbackLocale: TranslationService.fallbackLocale,home: HomePage(),);}
}

5. 创建一个示例 HomePage,支持切换语言并保存偏好

import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'translation_service.dart';class HomePage extends StatelessWidget {Future<void> _changeLanguage(Locale locale) async {TranslationService.changeLocale(locale);final prefs = await SharedPreferences.getInstance();await prefs.setString('language_code', locale.languageCode);await prefs.setString('country_code', locale.countryCode ?? '');}Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('hello'.tr),),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Text('login'.tr, style: TextStyle(fontSize: 24)),SizedBox(height: 20),ElevatedButton(onPressed: () => _changeLanguage(Locale('zh', 'CN')),child: Text('切换到中文'),),ElevatedButton(onPressed: () => _changeLanguage(Locale('en', 'US')),child: Text('Switch to English'),),],),),);}
}

6. 依赖添加

pubspec.yaml 加入:

dependencies:get: ^4.6.5shared_preferences: ^2.0.15

小结

  • 语言文本放 JSON 文件,方便维护
  • TranslationService 负责加载 JSON 并提供翻译
  • SharedPreferences 保存用户语言偏好,应用启动时读取并设置
  • 页面使用 .tr 获取文本,自动根据当前语言刷新
  • 语言切换时调用 TranslationService.changeLocale() 并保存

GetX 中 Controller 回收机制

在使用 GetX 的 Controller(例如 GetxControllerBindings 绑定的 Controller) 时,生命周期管理(即“回收”)非常关键,不当使用可能会造成内存泄漏或状态异常。

下面是关于 Controller 回收机制的说明,以及你在项目中应注意的关键问题。


✅ GetX 中 Controller 回收机制

1. 默认行为:Get.put() 注册的是单例

Get.put(MyController());
  • 控制器将常驻内存,不会自动回收
  • 调用 Get.delete<MyController>()Get.reset() 才会释放
  • permanent: true 时,即使手动调用 Get.reset(),也不会删除

2. 按需释放:Get.lazyPut()

Get.lazyPut(() => MyController());
  • 懒加载,首次使用时才创建实例
  • 默认会绑定页面,当页面被销毁时自动回收

3. 自动回收:使用 GetBuilderGet.put 并指定 tag/fenix: false

Get.lazyPut(() => MyController(), fenix: false); // 页面销毁即销毁 controller

🔁 Controller 生命周期方法

你可以在 Controller 中覆盖这些方法:

class MyController extends GetxController {void onInit() {super.onInit();print('Controller 初始化');}void onReady() {super.onReady();print('页面渲染完成');}void onClose() {print('Controller 被销毁');super.onClose();}
}

🧠 实战注意事项

场景推荐写法注意事项
页面间跳转带状态Get.lazyPut(() => Controller())会随页面销毁
多页面共享状态Get.put(Controller(), permanent: true)手动释放 Get.delete()
临时弹窗/小组件使用 Get.create(() => Controller())每次调用都创建新实例
获取已存在实例Get.find<Controller>()没有注册会报错

🧹 手动释放 Controller

Get.delete<MyController>();
  • 如果你用的是 Get.put(),Controller 不会自动销毁,必须手动释放
  • 可加在 onClose()dispose()WillPopScope

🐞 常见坑 & 解决

❌ 控制器被重复创建

// 错误示例:每次都创建新 controller
Get.put(MyController()); // 多次执行

✅ 解决:

// 用 Get.putIfAbsent,避免重复创建
Get.put<MyController>(MyController(), permanent: false);

❌ Controller 没有销毁,状态不一致

✅ 检查是否用了 permanent: true,如果不再需要,应手动:

Get.delete<MyController>();

✅ 建议的写法总结

目的推荐写法
页面内独立 controllerGet.lazyPut(() => Controller())
共享全局状态Get.put(() => Controller(), permanent: true)
确保生命周期自动管理使用 Bindings 注册 controller
页面关闭主动释放Get.delete<Controller>()

如果你在使用 GetX 做状态管理/DI/本地化,但不使用 Get.to() / Get.off() 等 GetX 路由 API,而是继续用 Flutter 原生的 Navigator.push(),也是完全可以的——但你需要注意以下几点,否则会失去 GetX 的一些功能或引起生命周期问题。


✅ 总体策略

如果你使用 Navigator.push(),但控制器是通过 Get.put() / Get.lazyPut() 提供的,你必须 手动管理 Controller 生命周期手动注入绑定逻辑,否则可能出现重复注入或无法回收。


✅ 正确使用方式说明

✅ 场景 1:页面不需要独立 Controller(或使用全局 Controller)

你可以正常使用原生跳转:

Navigator.push(context,MaterialPageRoute(builder: (_) => HomePage()),
);

这时候只要你页面中用的是:

final authController = Get.find<AuthController>();

就可以继续享受 GetX 的状态管理、.obs 等。


✅ 场景 2:页面需要自己的 Controller + 生命周期管理

⚠️ 由于你没有用 GetPage() 路由定义,GetX 无法帮你自动绑定/释放 Controller,你就需要手动注册 + 销毁。

✅ 推荐写法:
class DetailPage extends StatelessWidget {final controller = Get.put(DetailController()); // 手动注册Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('详情')),body: Center(child: Obx(() => Text('value: ${controller.count}')),),);}
}

🔁 页面关闭后,在合适的地方释放:


void dispose() {Get.delete<DetailController>();super.dispose();
}
  • 如果你用的是 StatelessWidget,可以用 WidgetsBindingObserver + Get.delete() 或者改为 StatefulWidget

✅ 场景 3:自己实现一个 NavigatorWrapper 做中间处理

你也可以封装一层 Navigator,用于兼容 Flutter 原生跳转 + GetX 注入生命周期

void navigateWithInjection<T>(BuildContext context,Widget page, {required Bindings binding,
}) {binding.dependencies(); // 手动注入绑定Navigator.push(context,MaterialPageRoute(builder: (_) => page,),).then((_) {// 页面返回后手动释放Get.delete<T>();});
}

使用:

navigateWithInjection<DetailController>(context,DetailPage(),binding: BindingsBuilder(() {Get.put(DetailController());}),
);

🚫 不推荐的混用写法(示例)

// 页面 A 使用 Flutter Navigator.push
Navigator.push(context, MaterialPageRoute(builder: (_) => PageB()));// 页面 B 用了 GetBuilder 绑定了 Controller,但没有 GetPage 注入,也没手动注入
GetBuilder<MyController>(builder: (_) => ...)

⚠️ 这会导致:

  • MyController 根本没被注册(或重复注册)
  • 页面退出后不会释放
  • 控制器是全局状态?还是每次新建?逻辑混乱

✅ 最推荐方案总结

目标推荐方案
使用 Get 的所有功能(路由、DI、生命周期)使用 Get.to() + GetPage + binding
继续使用原生路由 + 使用 Controller手动 Get.put() + Get.delete()
页面中不涉及独立 Controller可直接用 Navigator.push() + Get.find() 获取全局依赖
自定义兼容方案封装 push + binding/deletion 工具方法

👉 总结一句话:

如果你选择使用 Flutter 原生导航,你必须自己负责控制器的注册与释放,GetX 不会自动帮你做这些了。


在使用 GetX 的 GetxController 时,理解并正确使用其生命周期方法对资源管理、网络请求、控制器重用等场景非常关键。


🧭 GetxController 生命周期方法一览(按执行顺序)

方法名触发时机作用
onInit()Controller 被创建后第一次调用初始化数据、订阅、加载缓存等
onReady()Widget 渲染完毕(可获取 context)适合启动动画、发送请求、打开 dialog 等
onClose()Controller 被销毁前调用清理资源(如取消订阅、定时器、监听器等)
dispose()仅用于 GetxService,一般不用与 Flutter 原生 Widget 的 dispose 相同

✅ 推荐用法说明

onInit()

  • 用于初始化变量、Rx监听器、计时器、数据拉取等
  • 不要访问 context,它还没准备好

void onInit() {super.onInit();debounce(searchTerm, (_) => fetchResults(), time: Duration(milliseconds: 500));
}

onReady()

  • 适合访问 UI 或调用依赖 context 的逻辑(比如打开 dialog、动画、focus)
  • 会在 widget tree build 完成后执行一次

void onReady() {super.onReady();Future.delayed(Duration(milliseconds: 300), () {Get.snackbar('提示', '页面加载完成');});
}

onClose()

  • 回收资源、取消监听、关闭 stream、关闭计时器、断开 WebSocket 等都放这里
  • 页面关闭、Controller 被销毁时自动调用
late Timer timer;
void onInit() {timer = Timer.periodic(Duration(seconds: 1), (_) => print('tick'));super.onInit();
}
void onClose() {timer.cancel();super.onClose();
}

⚠️ 常见误区

错误情况原因解决方案
onClose() 不调用Controller 没有被释放(如 permanent: true改用非 permanent,或手动 Get.delete<>()
onInit() 里访问 contextcontext 尚未可用使用 onReady()
订阅 .obs 没有清理内存泄漏ever()debounce() 时在 onClose() 里调用 dispose()
Controller 只用了一次却没销毁忘记用 lazyPut()/绑定页面,或 Get.put() 没 delete用绑定系统或在页面返回时手动 Get.delete()

🌟 典型 Controller 生命周期例子

class LoginController extends GetxController {final RxString username = ''.obs;late Worker _worker;void onInit() {super.onInit();// 输入防抖_worker = debounce(username, (_) {print("用户名变化: $username");}, time: Duration(milliseconds: 500));}void onReady() {super.onReady();print('登录页就绪,可以显示动画或提示');}void onClose() {_worker.dispose();print('LoginController 已销毁');super.onClose();}
}

✅ 最佳实践总结

你要做什么放在哪个生命周期中
初始化变量、绑定 Rx 监听onInit()
动画/弹窗/访问 contextonReady()
清理 Rx Worker、Timer、StreamonClose()
控制器不释放,生命周期不触发检查是否用了 permanent 或未 delete

我们来继续扩展你的文档内容,添加一节关于 “多个页面共用同一个 Controller” 的处理方式与注意事项,将如下内容追加到你现有文档末尾:


🔄 多个页面共用同一个 Controller:处理方式与注意事项

在大型应用中,为了保持状态一致、避免重复逻辑,我们常常希望多个页面共用一个 Controller,例如登录状态、购物车、用户信息等。

✅ 实现方式

最常用的方式是使用 Get.put()Get.lazyPut() 注册为全局 Controller:

// 在 GlobalBinding 或 main 中注入
Get.put<UserController>(UserController(), permanent: true);

在多个页面中使用:

final userController = Get.find<UserController>();

✅ 可选方式:使用 Tag 区分多个实例(不推荐用于共享场景)

除非你需要多个实例互不干扰,否则不要用 tag:

Get.put(UserController(), tag: 'A');
Get.put(UserController(), tag: 'B');

🧠 注意事项

⚠️ 问题说明与建议
Controller 被重复创建避免在每个页面中都写 Get.put(),应通过 Get.find() 获取已存在实例
生命周期混乱,onClose() 不调用若用了 permanent: true 或未被销毁,onClose() 不会触发
控制器数据被提前销毁避免将共享 Controller 用 lazyPut 且未设置 fenix: true
同步多个页面 UI 更新使用 .obsObx()GetBuilder() 保证响应式更新
修改数据后部分页面未更新检查是否遗漏 .obs 或未正确包裹 UI

✅ 推荐 Controller 注册方式

场景推荐注入方式
多页面共享状态(如用户信息)Get.put(UserController(), permanent: true)
页面独立状态管理Get.lazyPut(() => PageController())
动态创建多个实例Get.put(..., tag: 'xxx')

💡 示例:用户控制器在多个页面共享

// controller
class UserController extends GetxController {var username = ''.obs;
}// 页面 A
Obx(() => Text('用户名:${Get.find<UserController>().username.value}'))// 页面 B
Get.find<UserController>().username.value = '新名字';

此时 A、B 页面都会自动同步更新。


好的,我来为你的文档添加一节完整示例,展示:

✅ 多个页面共享一个 Controller
✅ 页面间通过 GetX 路由跳转
✅ 动画过渡
✅ 控制器数据联动更新


🧪 示例:多个页面共享同一个 Controller + 路由动画 + 数据联动

🎯 场景说明

  • 有两个页面:ProfilePageEditNamePage
  • 它们共用 UserController,编辑名字后自动同步到另一个页面
  • 路由跳转带有动画

🧬 Step 1:UserController

class UserController extends GetxController {var username = '张三'.obs;
}

🖼️ Step 2:ProfilePage(显示用户名)

class ProfilePage extends StatelessWidget {final userController = Get.find<UserController>();Widget build(BuildContext context) {return Scaffold(appBar: AppBar(title: Text('用户信息')),body: Center(child: Column(mainAxisAlignment: MainAxisAlignment.center,children: [Obx(() => Text('用户名:${userController.username.value}',style: TextStyle(fontSize: 24))),SizedBox(height: 20),ElevatedButton(onPressed: () {Get.toNamed('/edit');},child: Text('编辑用户名'),),],),),);}
}

✏️ Step 3:EditNamePage(修改用户名)

class EditNamePage extends StatelessWidget {final userController = Get.find<UserController>();final TextEditingController textController = TextEditingController();Widget build(BuildContext context) {textController.text = userController.username.value;return Scaffold(appBar: AppBar(title: Text('编辑用户名')),body: Padding(padding: const EdgeInsets.all(16.0),child: Column(children: [TextField(controller: textController),SizedBox(height: 20),ElevatedButton(onPressed: () {userController.username.value = textController.text;Get.back(); // 返回上一页},child: Text('保存'),)],),),);}
}

🧭 Step 4:路由配置(含动画)

GetMaterialApp(initialRoute: '/profile',getPages: [GetPage(name: '/profile',page: () => ProfilePage(),),GetPage(name: '/edit',page: () => EditNamePage(),transition: Transition.downToUp,transitionDuration: Duration(milliseconds: 400),),],initialBinding: BindingsBuilder(() {Get.put(UserController(), permanent: true); // 全局共享}),
);

✅ 效果预期

  1. 打开 ProfilePage 显示用户名
  2. 点击“编辑用户名”进入 EditNamePage(带动画)
  3. 修改并保存后返回 ProfilePage,页面立即更新

如何使用多个 Controller 实例,并包括创建、管理、销毁、避免冲突等实践。


🧬 多个 Controller 实例的管理方式

在某些场景中(如多个 tab、多个子组件、多个动态生成的 item),你可能需要为同一个 Controller 创建多个独立实例。GetX 提供两种方式来实现:


✅ 方式一:使用 tag 区分多个实例

GetX 的 tag 就像“命名空间”,可以让你为同一个 Controller 类型创建多个独立的实例。

🔧 注册多个实例
Get.put(OrderController(), tag: 'order1');
Get.put(OrderController(), tag: 'order2');
📦 获取实例
final order1 = Get.find<OrderController>(tag: 'order1');
final order2 = Get.find<OrderController>(tag: 'order2');

✅ 方式二:使用 Get.create()(每次都新建)

Get.create() 不会缓存 Controller,每次调用都创建一个新实例,适合临时组件或弹窗用。

Get.create(() => TempController()); // 每次调用都是新的

使用时:

final tempController = Get.put(TempController());

⚠️ 注意事项与最佳实践

问题说明与建议
❌ 用 Get.put() 多次注册无 tag会抛异常或覆盖原实例,建议加 tag 区分
✅ 控制器按需使用后释放用完后手动 Get.delete<OrderController>(tag: 'xxx')
✅ tag 命名唯一且可追踪建议统一格式,如 "chat_user_$id""product_$index"
✅ 使用 BindingsBuilder 创建支持多 tag 注入,保持结构清晰

🌟 示例:多个订单页面使用不同的控制器实例

📦 Controller:

class OrderController extends GetxController {final String orderId;OrderController(this.orderId);var status = ''.obs;void onInit() {super.onInit();status.value = '正在加载订单 $orderId';}
}

🖼️ 页面中使用:

class OrderPage extends StatelessWidget {final String orderId;OrderPage(this.orderId);Widget build(BuildContext context) {final controller = Get.put(OrderController(orderId), tag: orderId);return Scaffold(appBar: AppBar(title: Text('订单 $orderId')),body: Obx(() => Text(controller.status.value)),);}
}

🧭 路由跳转:

Get.to(() => OrderPage('order_001'));
Get.to(() => OrderPage('order_002'));

每个页面拥有各自的 Controller 实例,互不干扰。


✅ 总结:使用多个 Controller 实例的建议

场景推荐方法
同一类型 Controller 多实例使用 tag
临时或短生命周期 Controller使用 Get.create()
页面绑定多个 Controller 实例BindingsBuilder() + tag
实例用完释放手动 Get.delete<T>(tag: ...)

下面是「带多个 Tab,每个 Tab 使用独立 Controller 实例」的完整示例。


🧪 示例:Tab 页面中每个 Tab 使用独立的 Controller 实例

🎯 场景说明

  • 页面有多个 Tab,每个 Tab 显示独立数据(如:推荐、热门、最新)
  • 每个 Tab 用一个独立的 TabController
  • 所有 Tab 使用相同类型的 NewsController,通过 tag 区分
  • 数据互不干扰,生命周期由页面控制

🧬 Step 1:创建 Controller

class NewsController extends GetxController {final String category;NewsController(this.category);var articles = <String>[].obs;void onInit() {super.onInit();fetchArticles();}void fetchArticles() {// 模拟网络加载articles.value = List.generate(5, (index) => '$category 新闻 $index');}
}

🖼️ Step 2:主 Tab 页面

class NewsTabPage extends StatelessWidget {final tabs = ['推荐', '热门', '最新'];Widget build(BuildContext context) {return DefaultTabController(length: tabs.length,child: Scaffold(appBar: AppBar(title: Text('新闻中心'),bottom: TabBar(tabs: tabs.map((t) => Tab(text: t)).toList(),),),body: TabBarView(children: tabs.map((category) {final tag = 'news_$category';// 每个 tab 注入自己的 controller(只注入一次)if (!Get.isRegistered<NewsController>(tag: tag)) {Get.put(NewsController(category), tag: tag);}return NewsTabContent(tag: tag);}).toList(),),),);}
}

🧩 Step 3:Tab 内容组件

class NewsTabContent extends StatelessWidget {final String tag;const NewsTabContent({required this.tag});Widget build(BuildContext context) {final controller = Get.find<NewsController>(tag: tag);return Obx(() => ListView.builder(itemCount: controller.articles.length,itemBuilder: (_, i) => ListTile(title: Text(controller.articles[i]),),));}
}

🧹 Step 4:页面销毁时释放 Controller(可选)

你可以在页面 pop 时手动释放所有 tab 的 controller:


void dispose() {for (final tab in ['推荐', '热门', '最新']) {Get.delete<NewsController>(tag: 'news_$tab');}super.dispose();
}

或者设置 fenix: false 时自动释放(只要不用 permanent)。


✅ 小结

优势实现方式
每个 Tab 独立逻辑与状态Get.put(..., tag: category)
相同 Controller 类型复用结构通过 tag 实现实例区分
生命周期清晰,资源可回收页面退出手动 Get.delete(tag: ...)

源码地址

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/pingmian/85545.shtml
繁体地址,请注明出处:http://hk.pswp.cn/pingmian/85545.shtml
英文地址,请注明出处:http://en.pswp.cn/pingmian/85545.shtml

如若内容造成侵权/违法违规/事实不符,请联系英文站点网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

深度学习:人工神经网络基础概念

本文目录&#xff1a; 一、什么是神经网络二、如何构建神经网络三、神经网络内部状态值和激活值 一、什么是神经网络 人工神经网络&#xff08;Artificial Neural Network&#xff0c; 简写为ANN&#xff09;也简称为神经网络&#xff08;NN&#xff09;&#xff0c;是一种模仿…

Unity2D 街机风太空射击游戏 学习记录 #12环射道具的引入

概述 这是一款基于Unity引擎开发的2D街机风太空射击游戏&#xff0c;笔者并不是游戏开发人&#xff0c;作者是siki学院的凉鞋老师。 笔者只是学习项目&#xff0c;记录学习&#xff0c;同时也想帮助他人更好的学习这个项目 作者会记录学习这一期用到的知识&#xff0c;和一些…

网站如何启用HTTPS访问?本地内网部署的https网站怎么在外网打开?

在互联网的世界里&#xff0c;数据安全已经成为了每个网站和用户都不得不面对的问题。近期&#xff0c;网络信息泄露事件频发&#xff0c;让越来越多的网站开始重视起用户数据的安全性&#xff0c;因此启用HTTPS访问成为了一个热门话题。作为一名网络安全专家&#xff0c;我希望…

计算机网络-----详解网络原理TCP/IP(上)

文章目录 &#x1f4d5;1. UDP协议✏️1.1 UDP的特点✏️1.2 基于UDP的应用层协议 &#x1f4d5;2. TCP协议✏️2.1 TCP协议段格式✏️2.2 TCP协议特点之确认应答✏️2.3 TCP协议特点之超时重传✏️2.4 TCP协议特点之连接管理✏️2.5 TCP协议特点之滑动窗口✏️2.6 TCP协议特点…

Lora训练

一种大模型高效训练方式&#xff08;PEFT&#xff09; 目标&#xff1a; 训练有限的ΔW&#xff08;权重更新矩阵&#xff09; ΔW为低秩矩阵→ΔWAB&#xff08;其中A的大小为dr, B的大小为rk&#xff0c;且r<<min(d,k)&#xff09;→ 原本要更新的dk参数量大幅度缩减…

蓝牙 5.0 新特性全解析:传输距离与速度提升的底层逻辑(面试宝典版)

蓝牙技术自 1994 年诞生以来,已经经历了多次重大升级。作为当前主流的无线通信标准之一,蓝牙 5.0 在 2016 年发布后,凭借其显著的性能提升成为了物联网(IoT)、智能家居、可穿戴设备等领域的核心技术。本文将深入解析蓝牙 5.0 在传输距离和速度上的底层技术逻辑,并结合面试…

Minio使用https自签证书

自签证书参考&#xff1a;window和ubuntu自签证书_windows 自签证书-CSDN博客 // certFilePath: 直接放在 resources 目录下 或者可以自定实现读取逻辑 // 读取的是 .crt 证书文件public static OkHttpClient createTrustingOkHttpClient(String certFilePath) throws Excep…

汽车前纵梁焊接总成与冲压件的高效自动化三维检测方案

汽车主体结构件上存在很多安装位&#xff0c;为保证汽车装配时的准确性&#xff0c;主体结构件需要进行全方位的尺寸和孔位置精度检测&#xff0c;以确保装配线的主体结构件质量合格。 前纵梁焊接总成是车身框架的核心承载部件&#xff0c;焊接总成由多片钣金冲压件焊接组成&a…

F接口基础.go

前言&#xff1a;接口是一组方法的集合&#xff0c;它定义了一个类型应该具备哪些行为&#xff0c;但不关心具体怎么实现这些行为。一个类型只要实现了接口中定义的所有方法&#xff0c;那么它就实现了这个接口。这种实现是隐式的&#xff0c;不需要显式声明。 目录 接口的定…

cartographer官方指导文件说明---第3章 cartographer前端算法流程介绍

cartographer官方指导文件说明 第3章 cartographer前端算法流程介绍 3.1 Scan Match扫描匹配 扫描匹配&#xff08;Scan Matching&#xff09;是 Cartographer 中实现局部SLAM的核心技术&#xff0c;它通过优化算法将当前激光扫描数据对齐到子图地图中。下面从计算过程、数学…

汽车整车厂如何用数字孪生系统打造“透明车间”

随着工业4.0时代的发展&#xff0c;数字孪生技术已成为现代制造业的重要利器。特别是在汽车整车厂&#xff0c;通过数字孪生系统的应用&#xff0c;能够有效打造一个“透明车间”&#xff0c;实现生产过程的全面可视化与实时监控&#xff0c;提高生产效率&#xff0c;降低成本&…

openKylin适配RISC-V高性能服务器芯片,携手睿思芯科共拓智算新蓝海

3月31日&#xff0c;睿思芯科&#xff08;深圳&#xff09;技术有限公司&#xff08;简称“睿思芯科”&#xff09;2025春季新品发布会在深圳前海国际会议中心盛大举行&#xff0c;作为RISC-V领域的年度盛事&#xff0c;此次发布会吸引了众多业内目光。此次发布会上&#xff0c…

【已解决】lxml.etree.ParserError: Document is empty

本专栏解决日常生活工作中非快速找到解决方案的问题。 问题背景 在爬取某网站时&#xff0c;使用开源框架报错&#xff1a;lxml.etree.ParserError: Document is empty 解决方案 1、多个搜索引擎中查找&#xff0c;建议都是对lxml的python源码进行修改&#xff0c;不好用。…

mac电脑调试iphone真机safari网页

mac电脑调试iphone真机safari网页 start 本文主要是记录一下如何调试苹果手机上的safari的网页 方法 1.苹果手机打开 web检查器 操作步骤&#xff1a; 打开设置搜索safari最底部“高级”开启“网页检查器” 2.mac电脑打开safari 操作步骤&#xff1a; 先用数据线连接手机和…

opencv依据图像类型读取图像像素点

Mat数据类型和通道对应的type()&#xff1a; 库类型C1C2C3C4CV_8U081624CV_8S191725CV_16U2101826CV_16S3111927CV_32S4122028CV_32F5132129CV_64F6142230 通过c程序查看类型并读取图像像素点&#xff1a; switch (im->type()){case 0:std::cout << "at (&quo…

软件架构的发展历程——从早期的单体架构到如今的云原生与智能架构

软件架构的发展历程是技术演进与业务需求相互驱动的结果&#xff0c;从早期的单体架构到如今的云原生与智能架构&#xff0c;每一步都在突破系统的可扩展性、灵活性和效率边界。以下是其核心发展脉络及未来趋势的全景解析&#xff1a; 一、发展历程&#xff1a;从单体到智能的…

Oracle 基础语句大全:从数据定义到复杂查询

一、DDL&#xff08;数据定义语言&#xff09;&#xff1a;定义数据库结构 1. 创建表&#xff08;CREATE TABLE&#xff09; -- 语法格式 CREATE TABLE [schema.]table_name (column1 datatype [CONSTRAINT constraint1],column2 datatype [DEFAULT default_value],-- 表级约…

【学习笔记】锁+死锁+gdb调试死锁

【学习笔记】锁死锁gdb调试死锁 一、互斥锁&#xff08;std::mutex&#xff09; 最基本的锁类型&#xff0c;提供排他性访问&#xff0c;同一时间仅允许一个线程持有锁。 #include <iostream> #include <mutex> #include <thread>std::mutex mtx; // 全局…

Flutter中将bytes转换成XFile对象上传

在Flutter中将字节数据(bytes)转换为XFile对象并上传可以通过以下步骤实现&#xff1a; 1.字节数据转临时文件 首先需要将字节数据写入临时文件&#xff0c;可以使用dart的File类实现&#xff1a; final tempDir await getTemporaryDirectory(); final file File(${tempDi…

饼图:数据可视化的“切蛋糕”艺术

饼图&#xff0c;作为数据可视化家族中最经典、最易识别的成员之一&#xff0c;其核心功能如同其名——像切分蛋糕一样&#xff0c;直观展示一个整体&#xff08;100%&#xff09;被划分为若干组成部分的比例关系。 往期文章推荐: 20.用Mermaid代码画ER图&#xff1a;AI时代的…