- 作者:老汪软件技巧
- 发表时间:2024-08-21 10:01
- 浏览量:
(整理翻译自: /@yurinovico…)
GetX5
这篇文章主要讨论GetX5导航、深度链接,以及前进和后退按钮的使用。
虽然状态管理和服务定位器(整个 Flutter 社区错误地称之为依赖注入)基本保持不变,但 GetX 5 中的导航发生了显著变化。
GetX 5 在底层使用 Navigator 2.0 API。
是的,即使我们使用 Get.to() 或 Get.back(),routerDelegate 对象依然在内部被调用。
Flutter Navigator 2.0 API 非常复杂。 GetX 的作者做了大量工作,以便将其复杂性隐藏在开发者面前。
GetX 4 的导航文件夹包含 22 个类,而 GetX 5 包含 36 个,几乎是前者的两倍。由于 Navigator 2.0 API 本身就很复杂,这些新的 GetX 代码也变得非常复杂。而且,和以往一样,代码中没有任何注释。我在试图理解这些代码,但老实说,这感觉很无望。(不过,我相对来说是 Dart 的初学者。)
虽然了解框架的底层工作原理在某些复杂情况下可能有帮助,但 GetX 5 的导航使用起来非常简单。(嵌套导航除外,这将在下面介绍。)
我们可以像以前一样使用匿名路由。
请看这个例子:
void main() {
runApp(const MainApp());
}
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return const GetMaterialApp(
home: FirstScreen(),
);
}
}
class FirstScreen extends StatelessWidget {
const FirstScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("FirstScreen"),
),
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
ElevatedButton(
child: const Text("Go to secondScreen"),
onPressed: () {
Get.to(() => const SecondScreen());
},
),
],
),
),
);
}
}
class SecondScreen extends StatelessWidget {
const SecondScreen({super.key});
@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text("SecondScreen"),
),
);
}
}
如上图所示,我们让浏览器按钮工作,而无需任何额外的工作。代码与我们使用 Navigator 1.0 时编写的代码完全相同。现在,它与 Navigator 2.0 一起工作。
但深度链接尚未实现。
让我们将匿名路由转换为命名路由。
我们应该在上面的例子中添加以下代码:
class Routes {
static const FIRST_SCREEN = '/';
static const SECOND_SCREEN = '/SecondScreen';
static final routes = [
GetPage(
name: FIRST_SCREEN,
page: () => const FirstScreen(),
),
GetPage(
name: SECOND_SCREEN,
page: () => const SecondScreen(),
),
];
}
并稍微更改成我们的 GetMaterialApp:
class MainApp extends StatelessWidget {
const MainApp({super.key});
@override
Widget build(BuildContext context) {
return GetMaterialApp(
initialRoute: Routes.FIRST_SCREEN, //instead of home:
getPages: Routes.routes, //added
);
}
}
现在深度链接也可以工作了。
所以,如果你想将项目升级到 GetX 5,但由于缺乏文档而有些害怕——不用怕,你不需要任何文档。
在大多数情况下,你的 GetX 4 代码将继续工作而无需任何更改。
如在前一篇文章中所述,匿名和命名路由的混合使用可能是个问题。但它本来就不是个好主意。命名路由既简单又有趣。
让我们继续。
子路由
让我们做一个简单的应用程序,让人们可以浏览产品。 类似这样的:
转存失败,建议直接上传图片文件
正如我们所见,所有网页功能:深度链接和浏览器按钮都正常工作。
class Routes {
static const HOME = '/';
static const PRODUCT_LIST = '/products';
static final routes = [
GetPage(
name: HOME,
page: () => const Home(),
transition: Transition.circularReveal,
),
GetPage(
name: PRODUCT_LIST,
page: () => const ProductList(),
transition: Transition.circularReveal,
children: [
GetPage(
name: '/:id',
page: () => ProductScreen(),
transition: Transition.circularReveal,
preventDuplicates: false,
),
]),
];
}
ProductScreen 被定义为 ProductList 的子路由。
以下是我们如何从 ProductList 导航到 ProductScreen:
Get.toNamed('${Routes.PRODUCT_LIST}/${products[index].id}');
我几乎可以肯定这个例子中的代码在 GetX 4 中也能工作。
(本文末尾有包含此和其他示例的完整源代码的存储库链接。)
抽屉示例
我做了一个非常简单的应用程序,使用抽屉进行导航。升级到 GetX 5 不需要任何更改。
转存失败,建议直接上传图片文件
正如我们所见,浏览器按钮和深度链接都在工作。
嵌套路由(BottomNavBar)
让我们设计一个简单的语言学习应用程序。每个课程屏幕有三个标签:阅读、听力和口语。类似这样的:
转存失败,建议直接上传图片文件
看起来像奇迹,对吧? 浏览器按钮、地址栏和深度链接在嵌套路由中都能正常工作。
我不确定 GetX 4 是否能做到这样的事情。
那么,我们如何在 GetX 5 中实现它呢?
首先,定义路由:
class Routes {
static const HOME = '/';
static const LESSON_LIST = '/lessons';
static const LESSON = '/:id';
static const READING = '/reading';
static const LISTENING = '/listening';
static const SPEAKING = '/speaking';
static final routes = [
GetPage(
name: HOME,
page: () => const HomeScreen(),
participatesInRootNavigator: true,
),
GetPage(
name: LESSON_LIST,
page: () => const LessonListScreen(),
participatesInRootNavigator: true,
children: [
GetPage(
name: LESSON,
page: () => LessonScreen(),
preventDuplicates: false,
participatesInRootNavigator: true,
children: [
GetPage(
name: READING,
page: () => const ReadingPage(),
transition: Transition.downToUp
),
GetPage(
name: LISTENING,
page: () => const ListeningPage(),
transition: Transition.fade
),
GetPage(
name: SPEAKING,
page: () => const SpeakingPage(),
transition: Transition.zoom
),
]),
]),
];
}
简单结构:Lesson 是 LessonList 的子路由,而 Reading、Speaking 和 Listening 是 Lesson 的子路由。
这是最重要的部分:LessonScreen 类:
GetRouterOutlet 是 GetX 5 中的一个新类。在 GetX 4 中有类似的东西,但我从未使用过。Router Outlet 的概念来自 Angular,意味着特定页面是单独路由的一部分。
在我们的例子中,这些是:Reading、Speaking 和 Listening。
class LessonScreen extends StatelessWidget {
String? id;
LessonScreen({super.key});
@override
Widget build(BuildContext context) {
id = Get.parameters['id'];
return Scaffold(
appBar: AppBar(
...
),
body: GetRouterOutlet(
initialRoute: '${Routes.LESSON_LIST}/$id${Routes.READING}',
anchorRoute: '${Routes.LESSON_LIST}/$id',
),
bottomNavigationBar: IndexedRouteBuilder(
routes: [
'${Routes.LESSON_LIST}/$id${Routes.READING}',
'${Routes.LESSON_LIST}/$id${Routes.LISTENING}',
'${Routes.LESSON_LIST}/$id${Routes.SPEAKING}'
],
builder: (context, routes, index) {
final delegate = context.delegate;
return BottomNavigationBar(
currentIndex: index,
onTap: (value) => delegate.toNamed(routes[value]),
items: const [
BottomNavigationBarItem(
icon: Icon(Icons.home),
label: 'Reading',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box_rounded),
label: 'Listening',
),
BottomNavigationBarItem(
icon: Icon(Icons.account_box_rounded),
label: 'Speaking',
),
],
);
}),
);
}
}
下面是我对其工作原理的解释,可能不是 100% 准确或充分。
GetX 5 在应用程序启动时自动创建根 routerDelegate。这个 routerDelegate 构建了一个根导航器(黑色)。当我们将 GetRouterOutlet 小部件放在 LessonScreen 中时,我们创建了另一个 routerDelegate(绿色),每次我们点击 bottomNavBar 中的标签时,它都会构建另一个导航器。
非常重要的是为包含出口的路由设置 participatesInRootNavigator: true。在我们的例子中,它是 LESSON。没有它,Reading、Listening 和 Speaking 会被复制并在 WidgetInspector 中显示两次:一次在根导航器中,一次在单独的导航器中。 为 Lesson 设置 participatesInRootNavigator: true 会使其子页面的 participatesInRootNavigator: false。
Routes、initialRoute 和 anchorRoute 字段可能很难正确设置。
这是到 Reading 标签的完整路径:
*initialRoute: */lessons/3/reading
*anchorRoute: *lessons/3
*Routes: */lessons/3/reading, /lessons/3/listening, /lessons/3/speaking
上述所有示例的源代码。
GetX 5.0.0-release-candidate-6 包含一个 example_nav2 文件夹,其中有一个更完整的示例,包含导航、嵌套路由、受保护路由、状态管理和依赖注入。仅此示例就足以让熟悉 GetX 4 的人开始使用 GetX 5。
感谢阅读。祝编码愉快!