在 Flutter 中,Get
是来自 get
包的一个轻量级、功能强大的状态管理与路由框架,常用于:
- 状态管理
- 路由管理
- 依赖注入(DI)
- Snackbar / Dialog / BottomSheet 管理
- 本地化(多语言)
下面是 get
的常见用法介绍:
✅ 1. 安装
在 pubspec.yaml
中添加依赖:
yaml
dependencies:
get: ^4.6.6
✅ 2. 基础设置
在 main.dart
使用 GetMaterialApp
替代 MaterialApp
:
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
void main() {
runApp(GetMaterialApp(
home: HomePage(),
));
}
✅ 3. 状态管理用法
3.1 使用 GetxController
管理状态:
dart
class CounterController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
3.2 页面中使用:
dart
class HomePage extends StatelessWidget {
final CounterController c = Get.put(CounterController());
@override
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 刷新 widget |
GetBuilder |
混合状态(Worker) | 使用 ever /once 监听变化 |
Workers |
🧪 方式一:响应式(Reactive State)Rx
+ Obx
✅ 使用场景:最常用,响应式自动刷新,无需手动更新。
🧵 示例:
dart
class CounterController extends GetxController {
var count = 0.obs;
void increment() {
count++;
}
}
class CounterPage extends StatelessWidget {
final CounterController c = Get.put(CounterController());
@override
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>
✅ 使用场景:性能高、不需要响应式,只在需要时手动刷新。
🧵 示例:
dart
class CounterController extends GetxController {
int count = 0;
void increment() {
count++;
update(); // 手动通知刷新
}
}
class CounterPage extends StatelessWidget {
@override
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(更高级)
✅ 使用场景:在状态变化时执行副作用逻辑(如网络请求、打印等)
🧵 示例:
dart
class CounterController extends GetxController {
var count = 0.obs;
@override
void onInit() {
ever(count, (value) => print("count changed: $value"));
super.onInit();
}
void increment() => count++;
}
✅ 常用 Worker 方法:
方法 | 说明 |
---|---|
ever() |
每次变化都会触发 |
once() |
第一次变化触发一次 |
debounce() |
用户停止输入一段时间后才触发(适合搜索) |
interval() |
一定时间内只触发一次(节流) |
🔄 状态更新对比
方式 | 自动刷新 | 手动调用 | 响应式变量 | 性能 |
---|---|---|---|---|
Obx |
✅ | ❌ | ✅ | 中等 |
GetBuilder |
❌ | ✅(update() ) |
❌ | 高 |
Worker |
✅ | ❌ | ✅ | 用于副作用 |
🧠 总结建议:
使用目的 | 推荐方式 |
---|---|
快速开发、响应式 UI | Rx + Obx |
高性能、低耦合局部刷新 | GetBuilder |
响应状态变化做副作用 | Workers 系列 |
Obx 会监听 所有.obs 的变化吗,还是可以指定,他是与某个Controller绑定吗
🎯 Obx
的监听机制:只监听它内部使用到的 .obs
变量
✅ 它不会监听所有 .obs
,只监听你在 Obx
函数体中实际使用到的 .obs
。
🔍 示例说明:
dart
class MyController extends GetxController {
var a = 0.obs;
var b = 0.obs;
}
dart
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
变量,它就自动监听它。
例如:
dart
Obx(() => Text("${controller.username}")); // 只监听 username
3. ❓ Obx 是与某个 Controller 绑定的吗?
❌不是绑定关系 ,它只是"用了"某个 Controller 里的 .obs
。
你甚至可以在 Obx
中混用多个 controller 的 .obs
:
dart
Obx(() => Text("${c1.a} - ${c2.b}")); // 同时监听 c1 和 c2 的状态
✅ 总结一句话:
Obx
监听谁,取决于你在它里面访问了哪些.obs
变量,与是否来自某个 Controller 无关。
如果你想实现多个 Obx
分别监听不同状态 ,避免无关刷新,这就是 GetX 的优势之一,写法也很清晰。
这里是一个完整示例,展示如何用多个 Obx
精确监听不同的 .obs
状态变量,从而避免不必要的重建 ------ 每个 Obx
只响应自己的数据变化。
✅ 示例场景:两个计数器,两个 Obx
,互不干扰
dart
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
精准监听)
dart
class CounterPage extends StatelessWidget {
final controller = Get.put(CounterController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text("多个 Obx 独立监听示例")),
body: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
// 只监听 countA
Obx(() => Text("计数器 A:${controller.countA}",
style: TextStyle(fontSize: 20))),
ElevatedButton(
onPressed: controller.incrementA,
child: Text("A +1"),
),
SizedBox(height: 40),
// 只监听 countB
Obx(() => 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
完全无关。
✅ 示例说明:
dart
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 会刷新
}
}
dart
class MyPage extends StatelessWidget {
final controller = Get.put(MyController());
@override
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
状态变化并执行副作用操作 的一组工具函数,例如ever
、once
、debounce
、interval
等。
✅ 问题逐条回答
问题 | 回答 |
---|---|
Worker 会监听所有 .obs 的变化吗? |
❌ 不会 ,只监听你显式传入的某个 .obs 变量。不会自动监听全部。 |
可以指定监听哪个 .obs 吗? |
✅ 必须 指定监听哪个 .obs ,Worker 的第一个参数就是监听目标。 |
Worker 是否与某个 Controller 绑定? | ✅ 通常放在某个 Controller 中使用,但绑定的是 .obs ,不是 Controller 本身。 |
🧪 示例:监听某个 .obs
变量的变化
dart
class MyController extends GetxController {
var count = 0.obs;
var username = ''.obs;
@override
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
:
- 用户名输入框 → 使用
debounce
实现 防抖搜索 - 登录状态 → 使用
ever
实现登录提示(弹 Snackbar)
✅ 项目结构预览
lib/
├── main.dart
└── controller.dart ← 状态 & Worker 逻辑
📄 controller.dart
dart
import 'package:get/get.dart';
import 'package:flutter/material.dart';
class AuthController extends GetxController {
// 状态变量
var username = ''.obs;
var isLoggedIn = false.obs;
@override
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
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());
@override
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 后触发一次模拟搜索(防抖)。
- 点击"登录"后,
isLoggedIn
为true
,会自动触发ever
,显示Snackbar
提示"登录成功"。
🧠 技术要点
功能 | 技术手段 |
---|---|
搜索输入防抖 | debounce(username, ...) |
登录状态变化提示 | ever(isLoggedIn, ...) |
Snackbar 弹窗 | Get.snackbar(...) |
状态绑定 UI | Obx(() => ...) |
控制器全局管理 | Get.put(AuthController()) |
扩展上一个 Worker 示例,加入以下两个功能:
✅ 目标功能扩展
1. ✅ 模拟异步搜索请求(带加载动画)
- 当用户输入停止后,模拟调接口(2秒)
- 显示搜索中动画
- 请求完成后显示「已搜索:XXX」
2. ✅ 登录成功自动跳转到欢迎页面
- 用户输入用户名,点击登录
- 弹出 Snackbar 提示
- 自动跳转到欢迎页面(
WelcomePage
)
📦 新状态变量(在 Controller 中添加)
dart
var isSearching = false.obs;
var searchResult = ''.obs;
📄 controller.dart(更新后的)
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;
@override
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
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());
@override
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 {
@override
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
中使用:
dart
void main() {
runApp(GetMaterialApp(
initialRoute: '/',
getPages: AppRoutes.routes,
));
}
📁 二、定义路由表(推荐结构)
创建 routes.dart
文件:
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. 普通跳转(非命名)
dart
Get.to(DetailPage());
✅ 2. 命名路由跳转
dart
Get.toNamed('/detail');
✅ 3. 返回上一页
dart
Get.back();
✅ 4. 替换当前页(不能返回)
dart
Get.off(DetailPage());
Get.offNamed('/detail');
✅ 5. 清空历史并跳转(常用于登录成功)
dart
Get.offAllNamed('/home');
🎯 四、参数传递方式
✅ 方式一:通过 arguments
传递参数(推荐)
🔁 传参:
dart
Get.toNamed('/detail', arguments: {'id': 123, 'title': '测试'});
🧾 接收:
dart
final args = Get.arguments as Map;
print(args['id']); // 123
✅ 方式二:通过 URL 参数(路径参数)
📥 定义路由时设置参数:
dart
GetPage(
name: '/detail/:id',
page: () => DetailPage(),
)
🔁 传参:
dart
Get.toNamed('/detail/888');
🧾 接收:
dart
final id = Get.parameters['id']; // "888"
✅ 也支持 query:
dart
Get.toNamed('/detail/888?title=测试');
final title = Get.parameters['title']; // "测试"
✅ 五、完整例子
📄 main.dart
dart
void main() {
runApp(GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => HomePage()),
GetPage(name: '/detail/:id', page: () => DetailPage()),
],
));
}
📄 home_page.dart
dart
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: ElevatedButton(
onPressed: () => Get.toNamed('/detail/123?title=你好'),
child: Text("去详情页"),
),
),
);
}
}
📄 detail_page.dart
dart
class DetailPage extends StatelessWidget {
@override
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
的以下属性实现:
✅ 一、使用内建动画配置
你可以通过 transition
和 transitionDuration
快速配置内建动画:
dart
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. 创建自定义动画类:
dart
class MyCustomTransition extends CustomTransition {
@override
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
中:
dart
GetPage(
name: '/detail',
page: () => DetailPage(),
customTransition: MyCustomTransition(),
transitionDuration: Duration(milliseconds: 500),
)
✅ 三、全局默认过渡动画(不推荐)
如果你希望所有页面默认使用统一动画,可以在 GetMaterialApp
中配置:
dart
GetMaterialApp(
defaultTransition: Transition.fade,
transitionDuration: Duration(milliseconds: 300),
)
⚠️ 缺点:所有页面一刀切,不灵活。
✅ 四、页面跳转时单独设置动画(临时跳转)
dart
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()
(立即注入)
📌 注册依赖
dart
final controller = Get.put(MyController());
- 立即创建并注册
- 可在任何地方调用
Get.find<MyController>()
来获取
✅ 2. 懒加载:Get.lazyPut()
dart
Get.lazyPut<MyController>(() => MyController());
- 在第一次使用时才创建
- 更节省内存,适合大项目中很多控制器
✅ 3. 永久依赖:Get.put(..., permanent: true)
dart
Get.put(MyService(), permanent: true);
- 永久存在,
Get.reset()
也不会清除 - 适合如网络层、用户配置等全局服务
✅ 4. 获取依赖:Get.find<T>()
dart
final c = Get.find<MyController>();
- 在任意位置获取,不需要 context
- 如果未注册,会抛错
✅ 5. 删除依赖:Get.delete<T>()
dart
Get.delete<MyController>();
- 销毁注册的依赖,释放内存
✅ 6. 结合页面使用示例
👇 创建一个 Controller
dart
class CounterController extends GetxController {
var count = 0.obs;
void increment() => count++;
}
👇 页面注册 & 使用(推荐在页面内 Get.put()
)
dart
class CounterPage extends StatelessWidget {
final controller = Get.put(CounterController());
@override
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 类:
dart
class CounterBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<CounterController>(() => CounterController());
}
}
配置到路由:
dart
GetPage(
name: '/counter',
page: () => CounterPage(),
binding: CounterBinding(),
)
使用时跳转:
dart
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
)
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
)
dart
class AuthBinding extends Bindings {
@override
void dependencies() {
Get.lazyPut<AuthController>(() => AuthController());
}
}
⚙️ 3. 全局 Binding(如 global_binding.dart
)
dart
class GlobalBinding extends Bindings {
@override
void dependencies() {
// 注册全局服务
Get.put<ApiService>(ApiService(), permanent: true);
Get.put<StorageService>(StorageService(), permanent: true);
}
}
🔁 4. 路由配置(app_pages.dart)
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)
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,
));
}
✅ 跳转方式示例
dart
Get.toNamed('/home');
控制器在跳转页面时会自动注入(如果该页面设置了
binding:
)
🚀 小贴士:常见依赖注入场景与方式
场景 | 推荐写法 |
---|---|
登录页面需要用到 AuthController | binding: AuthBinding() |
首页需要多个 controller | binding: BindingsBuilder(() => {...}) |
全局单例服务(如网络、缓存) | Get.put(Service(), permanent: true) |
获取控制器实例 | final auth = Get.find<AuthController>(); |
🔚 总结
GetX 的依赖注入建议这样使用:
类型 | 使用方式 | 说明 |
---|---|---|
页面级 Controller | lazyPut + Binding |
路由进入时自动注入 |
全局服务 | put(..., permanent: true) |
启动时注入,全程共享 |
自定义 Service | 单独建 services/ 目录 |
网络、数据库、存储类都放这里 |
中央注册点 | 用 GlobalBinding 做一次性全局注入 |
避免在 main.dart 重复注入依赖 |
✅ 6. Snackbar / Dialog / BottomSheet
GetX 提供了非常方便的 Snackbar、Dialog 和 BottomSheet 管理方法,支持无 Context 调用,语法简洁,且自带动画和丰富参数。
1. Snackbar
基础用法
dart
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(弹窗)
简单对话框
dart
Get.defaultDialog(
title: '提示',
middleText: '你确定要删除吗?',
textConfirm: '确认',
textCancel: '取消',
onConfirm: () {
print('确认删除');
Get.back();
},
onCancel: () {
print('取消删除');
},
);
自定义内容对话框
dart
Get.dialog(
AlertDialog(
title: Text('自定义标题'),
content: Text('这是自定义内容'),
actions: [
TextButton(
onPressed: () => Get.back(),
child: Text('关闭'),
)
],
),
);
3. BottomSheet(底部弹窗)
简单底部弹窗
dart
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
dart
Get.back(); // 关闭当前弹窗、Snackbar、BottomSheet 等
5. 结合示例
dart
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,带按钮交互,包含关闭逻辑和动画配置,方便直接拿去用。
dart
import 'package:flutter/material.dart';
import 'package:get/get.dart';
class GetUIExamplePage extends StatelessWidget {
@override
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 带圆角,上滑可拖拽关闭,包含几个选项点击时关闭并提示
你只需
dart
Get.to(() => GetUIExamplePage());
✅ 7. 国际化(多语言)
GetX 内置了强大的本地化(国际化)支持,让你快速实现多语言切换,且使用简单灵活。
📦 基础使用步骤
1. 创建语言翻译类,继承 Translations
dart
import 'package:get/get.dart';
class Messages extends Translations {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {
'hello': 'Hello World',
'login': 'Login',
},
'zh_CN': {
'hello': '你好,世界',
'login': '登录',
},
};
}
2. 在 GetMaterialApp
中配置
dart
GetMaterialApp(
translations: Messages(), // 绑定翻译类
locale: Locale('en', 'US'), // 默认语言
fallbackLocale: Locale('en', 'US'), // 找不到翻译时的兜底语言
home: MyHomePage(),
);
3. 页面中使用 .tr
获取对应语言文本
dart
Text('hello'.tr), // 自动根据当前语言显示文本
ElevatedButton(
onPressed: () => print('login'.tr),
child: Text('login'.tr),
),
4. 动态切换语言
dart
// 切换为中文
Get.updateLocale(Locale('zh', 'CN'));
// 切换为英语
Get.updateLocale(Locale('en', 'US'));
✅ 完整示例
dart
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 {
@override
Map<String, Map<String, String>> get keys => {
'en_US': {'hello': 'Hello World', 'login': 'Login'},
'zh_CN': {'hello': '你好,世界', 'login': '登录'},
};
}
class HomePage extends StatelessWidget {
@override
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 是语言_国家
格式,必须严格写对.tr
是String
的扩展方法,直接调用即可- 语言切换后,Get 会自动通知所有使用
.tr
的 Widget 更新 - 可以结合持久化保存用户选择的语言,下次启动时自动加载
完整的 GetX 本地化示例:
- 多语言 JSON 文件分离管理
- 从 JSON 读取语言包
- 语言切换后自动刷新 UI
- 语言偏好保存到本地,下次启动自动加载
1. 准备工作:在 assets/lang/
目录放语言 JSON 文件
assets/lang/en_US.json
assets/lang/zh_CN.json
示例 en_US.json
json
{
"hello": "Hello World",
"login": "Login",
"logout": "Logout"
}
示例 zh_CN.json
json
{
"hello": "你好,世界",
"login": "登录",
"logout": "退出登录"
}
2. 修改 pubspec.yaml
,声明资源文件
yaml
flutter:
assets:
- assets/lang/en_US.json
- assets/lang/zh_CN.json
3. 创建 translation_service.dart
,实现从 JSON 加载翻译
dart
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');
// 用于缓存翻译Map
final 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()));
}
}
@override
Map<String, Map<String, String>> get keys => _translations;
// 切换语言
static void changeLocale(Locale newLocale) {
locale = newLocale;
Get.updateLocale(newLocale);
}
}
注意 :这里用
async
读取,建议在main()
里先初始化好翻译资源。
4. 修改 main.dart
,初始化语言资源并读取用户偏好
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();
@override
Widget build(BuildContext context) {
return GetMaterialApp(
translations: _translationService,
locale: TranslationService.locale,
fallbackLocale: TranslationService.fallbackLocale,
home: HomePage(),
);
}
}
5. 创建一个示例 HomePage
,支持切换语言并保存偏好
dart
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 ?? '');
}
@override
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
加入:
yaml
dependencies:
get: ^4.6.5
shared_preferences: ^2.0.15
小结
- 语言文本放 JSON 文件,方便维护
TranslationService
负责加载 JSON 并提供翻译SharedPreferences
保存用户语言偏好,应用启动时读取并设置- 页面使用
.tr
获取文本,自动根据当前语言刷新 - 语言切换时调用
TranslationService.changeLocale()
并保存
GetX 中 Controller 回收机制
在使用 GetX 的 Controller(例如 GetxController
、Bindings
绑定的 Controller) 时,生命周期管理(即"回收")非常关键,不当使用可能会造成内存泄漏或状态异常。
下面是关于 Controller 回收机制的说明,以及你在项目中应注意的关键问题。
✅ GetX 中 Controller 回收机制
1. 默认行为:Get.put() 注册的是单例
dart
Get.put(MyController());
- 控制器将常驻内存,不会自动回收
- 调用
Get.delete<MyController>()
或Get.reset()
才会释放 permanent: true
时,即使手动调用Get.reset()
,也不会删除
2. 按需释放:Get.lazyPut()
dart
Get.lazyPut(() => MyController());
- 懒加载,首次使用时才创建实例
- 默认会绑定页面,当页面被销毁时自动回收
3. 自动回收:使用 GetBuilder
、Get.put
并指定 tag
/fenix: false
dart
Get.lazyPut(() => MyController(), fenix: false); // 页面销毁即销毁 controller
🔁 Controller 生命周期方法
你可以在 Controller 中覆盖这些方法:
dart
class MyController extends GetxController {
@override
void onInit() {
super.onInit();
print('Controller 初始化');
}
@override
void onReady() {
super.onReady();
print('页面渲染完成');
}
@override
void onClose() {
print('Controller 被销毁');
super.onClose();
}
}
🧠 实战注意事项
场景 | 推荐写法 | 注意事项 |
---|---|---|
页面间跳转带状态 | Get.lazyPut(() => Controller()) |
会随页面销毁 |
多页面共享状态 | Get.put(Controller(), permanent: true) |
手动释放 Get.delete() |
临时弹窗/小组件 | 使用 Get.create(() => Controller()) |
每次调用都创建新实例 |
获取已存在实例 | Get.find<Controller>() |
没有注册会报错 |
🧹 手动释放 Controller
dart
Get.delete<MyController>();
- 如果你用的是
Get.put()
,Controller 不会自动销毁,必须手动释放 - 可加在
onClose()
、dispose()
、WillPopScope
中
🐞 常见坑 & 解决
❌ 控制器被重复创建
dart
// 错误示例:每次都创建新 controller
Get.put(MyController()); // 多次执行
✅ 解决:
dart
// 用 Get.putIfAbsent,避免重复创建
Get.put<MyController>(MyController(), permanent: false);
❌ Controller 没有销毁,状态不一致
✅ 检查是否用了 permanent: true
,如果不再需要,应手动:
dart
Get.delete<MyController>();
✅ 建议的写法总结
目的 | 推荐写法 |
---|---|
页面内独立 controller | Get.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)
你可以正常使用原生跳转:
dart
Navigator.push(
context,
MaterialPageRoute(builder: (_) => HomePage()),
);
这时候只要你页面中用的是:
dart
final authController = Get.find<AuthController>();
就可以继续享受 GetX 的状态管理、.obs
等。
✅ 场景 2:页面需要自己的 Controller + 生命周期管理
⚠️ 由于你没有用
GetPage()
路由定义,GetX 无法帮你自动绑定/释放 Controller,你就需要手动注册 + 销毁。
✅ 推荐写法:
dart
class DetailPage extends StatelessWidget {
final controller = Get.put(DetailController()); // 手动注册
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情')),
body: Center(
child: Obx(() => Text('value: ${controller.count}')),
),
);
}
}
🔁 页面关闭后,在合适的地方释放:
dart
@override
void dispose() {
Get.delete<DetailController>();
super.dispose();
}
- 如果你用的是
StatelessWidget
,可以用WidgetsBindingObserver
+Get.delete()
或者改为StatefulWidget
✅ 场景 3:自己实现一个 NavigatorWrapper
做中间处理
你也可以封装一层 Navigator,用于兼容 Flutter 原生跳转 + GetX 注入生命周期:
dart
void navigateWithInjection<T>(
BuildContext context,
Widget page, {
required Bindings binding,
}) {
binding.dependencies(); // 手动注入绑定
Navigator.push(
context,
MaterialPageRoute(
builder: (_) => page,
),
).then((_) {
// 页面返回后手动释放
Get.delete<T>();
});
}
使用:
dart
navigateWithInjection<DetailController>(
context,
DetailPage(),
binding: BindingsBuilder(() {
Get.put(DetailController());
}),
);
🚫 不推荐的混用写法(示例)
dart
// 页面 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,它还没准备好
dart
@override
void onInit() {
super.onInit();
debounce(searchTerm, (_) => fetchResults(), time: Duration(milliseconds: 500));
}
onReady()
- 适合访问 UI 或调用依赖 context 的逻辑(比如打开 dialog、动画、focus)
- 会在
widget tree
build 完成后执行一次
dart
@override
void onReady() {
super.onReady();
Future.delayed(Duration(milliseconds: 300), () {
Get.snackbar('提示', '页面加载完成');
});
}
onClose()
- 回收资源、取消监听、关闭 stream、关闭计时器、断开 WebSocket 等都放这里
- 页面关闭、Controller 被销毁时自动调用
dart
late Timer timer;
@override
void onInit() {
timer = Timer.periodic(Duration(seconds: 1), (_) => print('tick'));
super.onInit();
}
@override
void onClose() {
timer.cancel();
super.onClose();
}
⚠️ 常见误区
错误情况 | 原因 | 解决方案 |
---|---|---|
onClose() 不调用 |
Controller 没有被释放(如 permanent: true ) |
改用非 permanent,或手动 Get.delete<>() |
在 onInit() 里访问 context |
context 尚未可用 | 使用 onReady() |
订阅 .obs 没有清理 |
内存泄漏 | 用 ever() 、debounce() 时在 onClose() 里调用 dispose() |
Controller 只用了一次却没销毁 | 忘记用 lazyPut() /绑定页面,或 Get.put() 没 delete |
用绑定系统或在页面返回时手动 Get.delete() |
🌟 典型 Controller 生命周期例子
dart
class LoginController extends GetxController {
final RxString username = ''.obs;
late Worker _worker;
@override
void onInit() {
super.onInit();
// 输入防抖
_worker = debounce(username, (_) {
print("用户名变化: $username");
}, time: Duration(milliseconds: 500));
}
@override
void onReady() {
super.onReady();
print('登录页就绪,可以显示动画或提示');
}
@override
void onClose() {
_worker.dispose();
print('LoginController 已销毁');
super.onClose();
}
}
✅ 最佳实践总结
你要做什么 | 放在哪个生命周期中 |
---|---|
初始化变量、绑定 Rx 监听 | onInit() |
动画/弹窗/访问 context | onReady() |
清理 Rx Worker、Timer、Stream | onClose() |
控制器不释放,生命周期不触发 | 检查是否用了 permanent 或未 delete |
我们来继续扩展你的文档内容,添加一节关于 "多个页面共用同一个 Controller" 的处理方式与注意事项,将如下内容追加到你现有文档末尾:
🔄 多个页面共用同一个 Controller:处理方式与注意事项
在大型应用中,为了保持状态一致、避免重复逻辑,我们常常希望多个页面共用一个 Controller,例如登录状态、购物车、用户信息等。
✅ 实现方式
最常用的方式是使用 Get.put()
或 Get.lazyPut()
注册为全局 Controller:
dart
// 在 GlobalBinding 或 main 中注入
Get.put<UserController>(UserController(), permanent: true);
在多个页面中使用:
dart
final userController = Get.find<UserController>();
✅ 可选方式:使用 Tag 区分多个实例(不推荐用于共享场景)
除非你需要多个实例互不干扰,否则不要用 tag:
dart
Get.put(UserController(), tag: 'A');
Get.put(UserController(), tag: 'B');
🧠 注意事项
⚠️ 问题 | 说明与建议 |
---|---|
Controller 被重复创建 | 避免在每个页面中都写 Get.put() ,应通过 Get.find() 获取已存在实例 |
生命周期混乱,onClose() 不调用 |
若用了 permanent: true 或未被销毁,onClose() 不会触发 |
控制器数据被提前销毁 | 避免将共享 Controller 用 lazyPut 且未设置 fenix: true |
同步多个页面 UI 更新 | 使用 .obs 、Obx() 、GetBuilder() 保证响应式更新 |
修改数据后部分页面未更新 | 检查是否遗漏 .obs 或未正确包裹 UI |
✅ 推荐 Controller 注册方式
场景 | 推荐注入方式 |
---|---|
多页面共享状态(如用户信息) | Get.put(UserController(), permanent: true) |
页面独立状态管理 | Get.lazyPut(() => PageController()) |
动态创建多个实例 | Get.put(..., tag: 'xxx') |
💡 示例:用户控制器在多个页面共享
dart
// 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 + 路由动画 + 数据联动
🎯 场景说明
- 有两个页面:
ProfilePage
和EditNamePage
- 它们共用
UserController
,编辑名字后自动同步到另一个页面 - 路由跳转带有动画
🧬 Step 1:UserController
dart
class UserController extends GetxController {
var username = '张三'.obs;
}
🖼️ Step 2:ProfilePage(显示用户名)
dart
class ProfilePage extends StatelessWidget {
final userController = Get.find<UserController>();
@override
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(修改用户名)
dart
class EditNamePage extends StatelessWidget {
final userController = Get.find<UserController>();
final TextEditingController textController = TextEditingController();
@override
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:路由配置(含动画)
dart
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); // 全局共享
}),
);
✅ 效果预期
- 打开
ProfilePage
显示用户名 - 点击"编辑用户名"进入
EditNamePage
(带动画) - 修改并保存后返回
ProfilePage
,页面立即更新
如何使用多个 Controller 实例,并包括创建、管理、销毁、避免冲突等实践。
🧬 多个 Controller 实例的管理方式
在某些场景中(如多个 tab、多个子组件、多个动态生成的 item),你可能需要为同一个 Controller 创建多个独立实例。GetX 提供两种方式来实现:
✅ 方式一:使用 tag
区分多个实例
GetX 的
tag
就像"命名空间",可以让你为同一个 Controller 类型创建多个独立的实例。
🔧 注册多个实例
dart
Get.put(OrderController(), tag: 'order1');
Get.put(OrderController(), tag: 'order2');
📦 获取实例
dart
final order1 = Get.find<OrderController>(tag: 'order1');
final order2 = Get.find<OrderController>(tag: 'order2');
✅ 方式二:使用 Get.create()
(每次都新建)
Get.create()
不会缓存 Controller,每次调用都创建一个新实例,适合临时组件或弹窗用。
dart
Get.create(() => TempController()); // 每次调用都是新的
使用时:
dart
final tempController = Get.put(TempController());
⚠️ 注意事项与最佳实践
问题 | 说明与建议 |
---|---|
❌ 用 Get.put() 多次注册无 tag |
会抛异常或覆盖原实例,建议加 tag 区分 |
✅ 控制器按需使用后释放 | 用完后手动 Get.delete<OrderController>(tag: 'xxx') |
✅ tag 命名唯一且可追踪 | 建议统一格式,如 "chat_user_$id" 或 "product_$index" |
✅ 使用 BindingsBuilder 创建 |
支持多 tag 注入,保持结构清晰 |
🌟 示例:多个订单页面使用不同的控制器实例
📦 Controller:
dart
class OrderController extends GetxController {
final String orderId;
OrderController(this.orderId);
var status = ''.obs;
@override
void onInit() {
super.onInit();
status.value = '正在加载订单 $orderId';
}
}
🖼️ 页面中使用:
dart
class OrderPage extends StatelessWidget {
final String orderId;
OrderPage(this.orderId);
@override
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)),
);
}
}
🧭 路由跳转:
dart
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
dart
class NewsController extends GetxController {
final String category;
NewsController(this.category);
var articles = <String>[].obs;
@override
void onInit() {
super.onInit();
fetchArticles();
}
void fetchArticles() {
// 模拟网络加载
articles.value = List.generate(5, (index) => '$category 新闻 $index');
}
}
🖼️ Step 2:主 Tab 页面
dart
class NewsTabPage extends StatelessWidget {
final tabs = ['推荐', '热门', '最新'];
@override
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 内容组件
dart
class NewsTabContent extends StatelessWidget {
final String tag;
const NewsTabContent({required this.tag});
@override
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:
dart
@override
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: ...) |