• 作者:老汪软件技巧
  • 发表时间: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 {
  Stringid;
  LessonScreen({super.key});
  @override
  Widget build(BuildContext context) {
    id = Get.parameters['id'];
          return Scaffold(
            appBarAppBar(
              ...
            ),
            bodyGetRouterOutlet(
              initialRoute'${Routes.LESSON_LIST}/$id${Routes.READING}',
              anchorRoute'${Routes.LESSON_LIST}/$id',
            ),
            bottomNavigationBarIndexedRouteBuilder(
                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(
                        iconIcon(Icons.home),
                        label'Reading',
                      ),
                      BottomNavigationBarItem(
                        iconIcon(Icons.account_box_rounded),
                        label'Listening',
                      ),
                      BottomNavigationBarItem(
                        iconIcon(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。

感谢阅读。祝编码愉快!