需求背景: 使用getx进行路由管理,可以设置根路由,以及返回根路由,返回指定页面路由,并且中间页面可以自动销毁,多次重复跳转同一个页面,可以实现页面数据刷新的功能.
1. 配置路由管理器
在 main.dart
中配置 GetMaterialApp 和路由表: 可以单独抽取两个类实现
dart
less
void main() => runApp(MyApp());
class MyApp extends StatelessWidget {
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialRoute: '/',
getPages: [
GetPage(name: '/', page: () => HomePage()),
GetPage(name: '/detail', page: () => DetailPage()),
GetPage(name: '/settings', page: () => SettingsPage()),
],
);
}
}
GetPage属性介绍
- 1.name (String): 页面的名称,用于在路由表或导航时引用此页面。
- 2.page (WidgetBuilder): 用于构建页面的WidgetBuilder函数。这个函数应该返回一个Widget实例,通常是GetWidget的实例。
- 3.transition (Transition): 页面转换动画。例如,Transition.fadeIn会使页面以渐入的方式出现。
- 4.transitionDuration (Duration): 页面转换动画的持续时间。
- 5.binding (Bindings): 页面绑定的类,用于初始化页面所需的控制器或服务。
- 6.middlewares (List): 中间件列表,用于在页面跳转前后执行一些逻辑,例如身份验证检查。
- 7.middlewaresBuilder (BindingsBuilder?): 与middlewares类似,但以函数形式提供,允许更灵活的中间件管理。
- 8 unknownRoute (bool): 如果设置为true,当尝试导航到不存在的路由时,将使用此页面作为回退。
- 9 preventDuplicate (bool): 如果设置为true,当尝试重复导航到同一个页面时,将不会再次创建页面实例。
- 10 fullscreenDialog (bool): 如果设置为true,此页面将作为全屏对话框显示。
- 11 customTransition (CustomTransition?): 自定义页面转换动画,允许更复杂的动画效果。
- 12 curve (Curve): 页面转换动画的曲线。
- 13 popGesture (bool): 如果设置为true,启用从边缘滑动返回的返回手势。
- 14 popGesturePathMatchPattern (String?): 用于匹配哪些路由可以使用返回手势。
- 15 showCupertinoParallax 属性用于控制Cupertino导航栏的透视效果
2. 路由跳转与返回逻辑
实现核心路由操作:
dart
less
//1、导航到新的页面
Get.to(NextScreen());
Get.toNamed("/NextScreen");
//2、关闭SnackBars、Dialogs、BottomSheets或任何你通常会用Navigator.pop(context)关闭的东西
Get.back();
//3、进入下一个页面,但没有返回上一个页面的选项(用于SplashScreens,登录页面等)
Get.off(NextScreen());
Get.offNamed("/NextScreen");
//4、进入下一个界面并取消之前的所有路由(在购物车、投票和测试中很有用)
Get.offAll(NextScreen());
Get.offAllNamed("/NextScreen");
//5、发送数据到其它页面只要发送你想要的参数即可。Get在这里接受任何东西,无论是一个字符串,一个Map,一个List,甚至一个类的实例。
Get.to(NextScreen(), arguments: 'Get is the best');
Get.toNamed("/NextScreen", arguments: 'Get is the best');
在你的类或控制器上:
print(Get.arguments);
//print out: Get is the best
//6. 要导航到下一条路由,并在返回后立即接收或更新数据
var data = await Get.to(Payment());
var data = await Get.toNamed("/payment");
// 在另一个页面上,发送前一个路由的数据
Get.back(result: 'success');
// 并使用它,例:
if(data == 'success') madeAnything();
//7. 跳转到详情页(支持重复跳转刷新)
Get.toNamed(
'/detail',
arguments: {'id': 123}, // 传递参数
preventDuplicates: false, // 允许重复页面
);
//8. 返回到栈中第一个路由,即首页。
Get.until((route) => route.isFirst);
//9. 返回根路由并销毁所有中间页面
Get.offAllNamed('/');
//10. 返回到指定页面(如设置页)并销毁中间页面
Get.offNamedUntil(
'/settings',
(route) => route.settings.name == '/', // 保留根路由
);
3. 页面刷新机制
在目标页面控制器中实现数据刷新:
dart
ini
class DetailController extends GetxController {
var itemId = 0.obs;
var data = ''.obs;
@override
void onInit() {
super.onInit();
loadData();
}
// 从参数获取ID并刷新数据
void loadData() {
final args = Get.arguments;
itemId.value = args['id'] ?? 0;
// 模拟数据加载
data.value = '正在加载...';
Future.delayed(Duration(seconds: 1), () {
data.value = '数据内容 ${itemId.value}';
});
}
// 手动刷新按钮
void refreshData() {
loadData();
}
}
4. 页面自动销毁配置
在路由跳转时使用 Get.offXXX
系列方法自动销毁页面:
dart
less
// 跳转后销毁当前页
Get.offNamed('/detail');
// 跳转后销毁所有历史页
Get.offAllNamed('/detail');
// 跳转到新页面并销毁直到指定路由
Get.offNamedUntil('/detail', ModalRoute.withName('/'));
5. 完整页面示例
详情页 (detail_page.dart)
dart
less
class DetailPage extends StatelessWidget {
final DetailController controller = Get.put(DetailController());
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('详情页')),
body: Center(
child: Obx(() => Column(
children: [
Text(controller.data.value),
ElevatedButton(
onPressed: controller.refreshData,
child: Text('刷新数据'),
),
ElevatedButton(
onPressed: () => Get.offAllNamed('/'),
child: Text('返回根路由'),
),
],
)),
),
);
}
}
根路由页 (home_page.dart)
dart
less
class HomePage extends StatelessWidget {
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(title: Text('首页')),
body: Center(
child: Column(
children: [
ElevatedButton(
onPressed: () => Get.toNamed('/detail', arguments: {'id': DateTime.now().millisecond}),
child: Text('跳转详情页(带随机参数)'),
),
ElevatedButton(
onPressed: () => Get.offNamedUntil('/settings', (route) => route.isFirst),
child: Text('跳设置页并销毁中间页'),
)
],
),
),
);
}
}
关键特性说明
-
路由管理:
Get.toNamed()
:普通跳转Get.offNamed()
:替换当前路由Get.offAllNamed()
:清空历史栈Get.offNamedUntil()
:定向跳转并销毁中间页
-
页面刷新:
preventDuplicates: false
允许相同路由重复打开- 通过每次传递新参数触发控制器更新
- 手动刷新按钮调用控制器方法
-
自动销毁:
- 使用
off
系列方法自动释放不再使用的页面 - 结合
until
条件精确控制保留的页面栈
- 使用
-
控制器生命周期:
- 使用
Get.put()
绑定控制器 - 页面销毁时自动回收资源
onInit()
处理初始化逻辑
- 使用
注意事项
- 在
GetPage
配置中不要使用binding
如果不需要强制绑定控制器 - 使用
Get.arguments
获取路由参数而非传统构造方法 - 对于需要保持状态的页面可使用
Get.lazyPut
配合fenix: true
- 复杂场景可使用
Get.parameters
处理 URL 式参数
这种实现方式提供了灵活的路由控制,同时确保内存高效管理,特别适合需要频繁导航和状态更新的应用场景。
对在 GetPage 配置中不要使用 binding 如果不需要强制绑定控制器的详细解释。
为什么在不需要强制绑定控制器时不建议使用 binding?
-
资源浪费问题:
- 当使用
binding
时,每次路由跳转都会创建新的控制器实例 - 对于不需要独立状态的页面,这会创建不必要的对象
- 增加内存开销和垃圾回收负担
- 当使用
-
生命周期冲突:
dart
scssGetPage( name: '/profile', page: () => ProfilePage(), binding: BindingsBuilder(() { Get.lazyPut<ProfileController>(() => ProfileController()); }), )
即使页面还在栈中,返回时控制器也会被销毁(GetX 默认行为)
-
状态管理冲突:
- 绑定会强制创建新控制器,覆盖可能已存在的实例
- 破坏可能存在的跨页面状态共享
如果强制绑定了会怎样?
-
重复创建问题:
dart
arduino// 多次跳转相同路由 Get.toNamed('/detail'); // 创建 ControllerA Get.toNamed('/detail'); // 创建 ControllerB 覆盖 ControllerA
前一个控制器实例会被丢弃,可能导致内存泄漏
-
状态丢失问题:
dart
scss// 页面A Get.toNamed('/detail'); // 跳转时携带状态 // 绑定中的控制器 Get.lazyPut<DetailController>(() => DetailController()); // 忽略传入状态
-
性能影响:
- 每次跳转都执行初始化逻辑
- 对于复杂控制器,会降低路由切换速度
不绑定时如何访问控制器数据?
方法1:手动获取控制器(推荐)
dart
scala
// 在页面中
class DetailPage extends StatelessWidget {
final DetailController controller = Get.find<DetailController>();
@override
Widget build(BuildContext context) {
return Obx(() => Text(controller.data.value));
}
}
方法2:按需初始化
dart
scss
// 在跳转前初始化
void navigateToDetail() {
// 检查是否已存在控制器
if (!Get.isRegistered<DetailController>()) {
Get.put(DetailController());
}
Get.toNamed('/detail');
}
方法3:使用参数传递数据
dart
php
// 跳转时
Get.toNamed('/detail', arguments: {'item': item});
// 页面中
final item = Get.arguments['item'];
何时应该使用 binding?
-
需要严格隔离状态的场景:
dart
lessGetPage( name: '/checkout', page: () => CheckoutPage(), binding: BindingsBuilder(() { Get.put(CheckoutController()); // 每次都是全新状态 }), )
-
需要自动依赖注入的复杂页面:
dart
vbnetbinding: BindingsBuilder(() { Get.put<ApiService>(ApiServiceImpl()); Get.put(CheckoutController(Get.find<ApiService>())); })
-
需要强制刷新的页面:
dart
arduinoGet.toNamed('/product?refresh=true'); // 每次打开都强制刷新产品数据
最佳实践建议
-
状态保持策略:
dart
csharp// 在main.dart中初始化全局控制器 void main() { Get.put<AuthController>(AuthController(), permanent: true); runApp(MyApp()); }
-
混合使用模式:
dart
scssGetPage( name: '/detail', page: () => DetailPage(), // 可选绑定 - 仅当不存在控制器时创建 binding: BindingsBuilder(() { if (!Get.isRegistered<DetailController>()) { Get.lazyPut(() => DetailController()); } }), )
-
智能刷新方案:
dart
javascript// 在控制器中添加刷新方法 void refreshData({bool force = false}) { if (force || data.isEmpty) { // 获取数据 } } // 跳转时 Get.toNamed('/detail?refresh=${DateTime.now().milliseconds}'); // 页面初始化 onInit() { refreshData(force: Get.parameters['refresh'] != null); }
-
路由观察模式:
dart
scss// 监听路由变化 ever(Get.routing, (route) { if (route.current == '/detail') { // 执行特定逻辑 } });
总结对比表
场景 | 使用 Binding | 不使用 Binding |
---|---|---|
需要独立页面状态 | ✅ 推荐 | ⚠️ 可能状态污染 |
需要共享状态 | ❌ 不推荐 | ✅ 推荐 |
简单数据展示页 | ❌ 过度设计 | ✅ 简洁高效 |
需要自动依赖注入 | ✅ 必要 | ❌ 需手动处理 |
频繁打开的页面 | ⚠️ 有性能损耗 | ✅ 更高效 |
需要严格生命周期控制 | ✅ 自动管理 | ❌ 需手动管理 |
根据你的需求描述,推荐:
- 根路由使用永久控制器 (
permanent: true
) - 详情页不使用 binding,采用参数驱动刷新
- 使用
Get.offAllNamed('/')
返回根路由时自动销毁中间控制器 - 对需要强制刷新的页面使用 URL 参数触发刷新
这样既能满足路由管理需求,又能优化内存使用和性能表现。