Flutter导航管理插件navi的使用

发布于 1周前 作者 caililin 来自 Flutter

Flutter导航管理插件navi的使用

Navi

一个为Flutter设计的简单易学的声明式导航框架,基于Navigator 2.0(路由)。


如果你喜欢Flutter,你也会喜欢声明式UI声明式导航

Navigator 2.0 提供了一个声明式导航API。不幸的是,它过于复杂且难以使用,并且包含大量的样板代码。不仅如此,它还需要维护一个单一状态来管理整个应用程序的导航系统。这不是一个好的架构,并且不适合大型应用

Navi 帮助你保留所有Navigator 2.0的强大功能,但具有简单易学的API。它帮助你在分割和隔离的域中管理你的导航系统。

请注意,命令式导航API 也作为声明式API之外的一层被支持。

快速示例

要使用库,控制NaviStack小部件是你需要学习的一切!

下面是一个具有两个页面的应用程序:

  • / 显示书籍列表
  • /:id 显示一本书。
dependencies:
  navi: any
import 'package:flutter/material.dart';
import 'package:navi/navi.dart';

void main() {
  runApp(App());
}

class App extends StatelessWidget {
  final _informationParser = NaviInformationParser();
  final _routerDelegate = NaviRouterDelegate.material(child: BooksStack());

  @override
  Widget build(BuildContext context) {
    return MaterialApp.router(
      routeInformationParser: _informationParser,
      routerDelegate: _routerDelegate,
    );
  }
}
// 你的BooksStack小部件状态应该使用NaviRouteMixin以便在路由更改时接收通知
class _BooksStackState extends State<BooksStack> with NaviRouteMixin<BooksStack> {
  Book? _selectedBook;

  @override
  void onNewRoute(NaviRoute unprocessedRoute) {
    // 如果路由更改(例如浏览器地址栏或深层链接),将路由转换为你的状态并重建小部件(堆栈)
    final bookId = int.tryParse(unprocessedRoute.pathSegmentAt(0) ?? '');
    _selectedBook = getBookById(bookId); // 例如从数据库获取
    setState(() {});
  }

  @override
  Widget build(BuildContext context) {
    // 你可以像使用其他小部件一样在另一个NaviStack下嵌套NaviStack,以创建嵌套路由
    // 请参阅“嵌套堆栈”部分
    return NaviStack(
      pages: (context) => [
        NaviPage.material(
          key: const ValueKey('Books'),
          // 没有路由属性,url默认为'/'
          // BooksPagelet是你的小部件,显示书籍列表
          child: BooksPagelet(
            // 你可以更新BooksStack小部件的状态以进行导航
            // 或者你可以在BooksPagelet内部使用context.navi进行导航(请参阅“跳转到新路由”部分)
            onSelectBook: (book) => setState(() {
              _selectedBook = book;
            }),
          ),
        ),
        if (_selectedBook != null)
          NaviPage.material(
            key: ValueKey(_selectedBook),
            route: NaviRoute(path: ['${_selectedBook!.id}']), // url为'/:id'
            // BookPagelet是你的小部件,显示一本书
            child: BookPagelet(book: _selectedBook!),
          ),
      ],
      onPopPage: (context, route, dynamic result) {
        // 弹出时更新状态
        if (_selectedBook != null) {
          setState(() {
            _selectedBook = null;
          });
        }
      },
    );
  }
}

更多示例

所有示例

架构层

包名 层次 计划 解释
Navi 代码生成器 发布1.0后 生成样板代码
Navi 配置器 在可能的情况下发布1.0之前,否则在发布1.0之后 类似于Angular或Vue的URL映射方法
Navi 命令式API 发布1.0之前 当声明式不需要时有用
Navi 高级声明式API 进行中 简单易用,同时保留Navigator 2.0的强大功能
Flutter SDK Navigator 2.0 低级声明式API N/A 太复杂且难以使用

声明式导航

声明式导航类似于声明式UI,因为它允许你通过当前UI状态描述你的导航系统。更新当前UI状态以告诉Navi决定导航到哪里。

只有在以下情况下,声明式导航才是强大的:

  • 提供的API足够简单
  • 你的应用程序合理地分割成可管理的域(或在此库中的堆栈)

一个例子,声明式导航的优势在于管理一系列页面以完成单个任务(例如多页的注册表单)。在这种情况下,命令式方法通常更困难。

链式页面场景(也称为页面流)只是你可以使用Navi的一种情况。这个库绝对不仅仅是这些。

跳转到新路由

要进行导航,你有两个选择:

  • 声明式:更新你的小部件状态(或多个小部件状态)以重建小部件。它将重建所需的堆栈并相应地更新URL。
  • 命令式:调用以下方法:
    • context.navi.to(['products', '1'])context.navi.to(['products/1']):导航到绝对URL /products/1
    • context.navi.relativeTo(['details']):导航到相对URL。如果当前URL (context.navi.currentRoute) 是 /products/1,则目标URL将是 /products/1/details。你可以使用 ../ 来向上移动一个层级。例如,context.navi.relativeTo(['../2/details']) 将在此示例中导航到 /products/2/details
    • context.navi.pop():快捷方式 Navigator.of(context).pop()
    • context.navi.maybePop():快捷方式 Navigator.of(context).maybePop()
    • context.navi.canPop():快捷方式 Navigator.of(context).canPop()
    • TODOs:
      • context.navi.stack(ProductsStackMarker()).to(['2', 'overview']):从给定堆栈的当前URL开始导航到相对URL。如果当前URL是 my/long/path/to/products/1/details 并且 ProductsStack 的URL是 my/long/path/to/products,则目标URL将是 my/path/to/products/2/overview
      • context.navi.back():返回到历史记录中的上一个页面。

嵌套堆栈

因为NaviStack只是一个普通的小部件,所以你需要像使用其他小部件一样使用这个小部件来构建嵌套堆栈。

例如,你有一个书店,有两个页面:书列表页面和书页面。它们的URL分别是 /books/books/:id

在书页面中,你将内容拆分为两个标签:概述和详细信息。它们的URL分别是 /books/:id/overview/books/:id/details

在这种情况下,你可以创建两个堆栈:

// 这个堆栈可以是你的RootStack,直接在你的MaterialApp之下。
NaviStack(
  pages: (context) => [
    NaviPage.material(
      route: NaviRoute(path: ['books']),
      child: BooksPagelet(),
    ),
    NaviPage.material(
      route: NaviRoute(path: ['books', book.id]),
      child: BookStack(),
    ),
  ],
);

// 在BookStack小部件中,你构建另一个堆栈
NaviStack(
  pages: (context) => [
    if (pageId == 'overview') NaviPage.material(
      route: NaviRoute(path: ['overview']),
      child: BookOverviewPagelet(),
    ),
    if (pageId == 'details') NaviPage.material(
      route: NaviRoute(path: ['details']),
      child: BookDetailsPagelet(),
    ),
  ],
);

主要思想是,在嵌套堆栈 BookStack 中,你不需要知道父堆栈的URL(在这个例子中是 RootStack)。

Navi会帮你合并父堆栈中的当前URL(例如 /books/1)和嵌套堆栈中的URL(例如 /overview)以为你生成最终的URL(例如 /books/1/overview)。

你可以根据需要创建无限深度的嵌套堆栈,每个堆栈只管理它应该知道的URL部分。

它通常与 <a href="https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html"><code>BottomNavigationBar</code></a><a href="https://api.flutter.dev/flutter/material/TabBar-class.html"><code>TabBar</code></a> 一起使用,但它肯定适用于其他组件和设计。

如果你想在 <a href="https://api.flutter.dev/flutter/material/BottomNavigationBar-class.html"><code>BottomNavigationBar</code></a> 中保持堆栈的状态,你可以使用 <a href="https://api.flutter.dev/flutter/widgets/IndexedStack-class.html"><code>IndexedStack</code></a>

如果你想在 <a href="https://api.flutter.dev/flutter/material/TabBar-class.html"><code>TabBar</code></a> 中保持堆栈的状态,你可以使用 <a href="https://api.flutter.dev/flutter/widgets/AutomaticKeepAliveClientMixin-mixin.html"><code>AutomaticKeepAliveClientMixin</code></a>

当与选项卡一起使用并保持选项卡状态时,你会在小部件树中保持多个堆栈分支。在这种情况下,请确保将非活动堆栈中的 active: false 设置为非活动状态。只有当前选项卡中的堆栈设置 active: true。活跃堆栈负责向浏览器地址栏报告正确的最终URL,而非活跃堆栈不报告。

请在 示例 中查看更多。

自定义页面

要使用默认的material和cupertino页面,你可以使用快捷方式:

  • NaviRouterDelegate.material()
  • NaviRouterDelegate.cupertino()
  • NaviPage.material()
  • NaviPage.cupertino()

要使用自定义页面,使用默认构造函数:

  • NaviRouterDelegate(rootPage: () => YourCustomPage())
  • NaviPage(pageBuilder: (key, child) => YourCustomPage())

TODO: 扁平化一堆栈到单个堆栈

FlatNaviStack(
  children: [
    NaviStack(),
    NaviStack(),
    // ...
  ]
)

更多关于Flutter导航管理插件navi的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter导航管理插件navi的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


在Flutter中,navi 并不是一个官方或广泛认可的导航管理插件。通常,Flutter开发者会使用 flutter/material 包中的 Navigator 类来进行导航管理。不过,如果你提到的 navi 是一个第三方库,并且希望了解如何使用类似的导航管理插件,我可以给你一个基于官方 Navigator 的示例,并假设 navi 的使用方式可能与之类似。

使用 Flutter 官方的 Navigator 进行导航管理

以下是一个使用 Navigator 进行页面导航管理的简单示例:

  1. 创建主页面(Main Screen)
import 'package:flutter/material.dart';
import 'second_screen.dart'; // 导入第二个页面

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Navigation Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MyHomePage(),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Main Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go to Second Screen'),
          onPressed: () {
            Navigator.push(
              context,
              MaterialPageRoute(builder: (context) => SecondScreen()),
            );
          },
        ),
      ),
    );
  }
}
  1. 创建第二个页面(Second Screen)
import 'package:flutter/material.dart';

class SecondScreen extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go back to Main Screen'),
          onPressed: () {
            Navigator.pop(context);
          },
        ),
      ),
    );
  }
}

假设 navi 插件的使用方式

虽然 navi 不是官方的插件,但假设它提供了类似的 API,代码结构可能会是这样的:

// 假设 navi 插件提供了 Navi 类来管理导航
import 'package:navi/navi.dart'; // 假设的包导入路径
import 'second_screen_navi.dart'; // 假设的第二个页面

void main() {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Navi Demo',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: NaviProvider( // 假设使用 NaviProvider 包装主页面
        child: MyHomePage(),
        navigatorKey: GlobalKey<NaviState>(), // 假设需要提供一个 GlobalKey
      ),
    );
  }
}

class MyHomePage extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final Navi navi = Navi.of(context); // 假设使用 Navi.of 获取 Navi 实例
    return Scaffold(
      appBar: AppBar(
        title: Text('Main Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go to Second Screen'),
          onPressed: () {
            navi.push( // 假设使用 navi.push 进行导航
              MaterialPageRoute(builder: (context) => SecondScreenNavi()),
            );
          },
        ),
      ),
    );
  }
}

// 假设的第二个页面,使用 navi 插件的特定方式
class SecondScreenNavi extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    final Navi navi = Navi.of(context);
    return Scaffold(
      appBar: AppBar(
        title: Text('Second Screen'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('Go back to Main Screen'),
          onPressed: () {
            navi.pop(); // 假设使用 navi.pop 返回上一页面
          },
        ),
      ),
    );
  }
}

注意:上述 navi 插件相关的代码是假设性的,并非真实存在的插件 API。在实际开发中,你需要查阅 navi 插件的官方文档来了解其具体的 API 和使用方法。如果 navi 插件确实存在,并且提供了与 Navigator 类似的 API,那么上述假设性的代码结构可能会与真实使用方式相近。

回到顶部