• Github 中文镜像
Sign inSign up
Watch966
Star102.4k
Fork61.8k
Tag: flutter
Switch branches/tags
K / Flutter 路由 2.0(一).md
移动浏览 Clone
加载中...
到移动设备上浏览
263 lines 17.27 KB
First commit on 2 Nov 2020

    翻自:学习 Flutter 的新导航和路由系统

    原文太长,拆成两篇,这一篇是说明,下一篇是练习。

    本文介绍了 Flutter 的新 NavigatorRouter API 的工作原理。如果您遵循 Flutter 的开放设计文档,则可能已经看到了称为 Navigator 2.0 和 Router 的这些新功能。我们将探讨这些 API 如何对您的应用中的屏幕进行更精细的控制,以及如何使用它来解析路由。

    这些新的 API 并没有改变,它们只是添加了一个新的声明性 API。在 Navigator 2.0 之前,很难推送或弹出多个页面,或删除当前页面下方的页面。但是,如果您对 Navigator 今天的工作方式感到满意,则可以继续以相同的方式(必需)使用它。

    Router 提供了处理来自底层平台的路由并显示适当页面的能力。在本文中,将 Router 配置为解析浏览器 URL 以显示适当的页面。

    本文可帮助您选择哪种 Navigator 模式最适合您的应用,并说明如何使用 Navigator 2.0 解析浏览器 URL 并完全控制活动页面的堆栈。本文中的练习显示了如何构建一个应用程序,该应用程序处理从平台传入的路由并管理应用程序的页面。以下 GIF 展示了实际的示例应用程序:

    看不见图的话请翻和谐墙

    Navigator 1.0

    如果您使用的是 Flutter,则可能使用的是 Navigator,并且熟悉以下概念:

    • Navigator — 一个 widget,用于管理 Route 对象的堆栈。
    • Route — 由维护 screen 的 Navigator 管理的对象,通常由 MaterialPageRoute 之类的类实现。

    在 Navigator 2.0 之前,Routes 已通过命名路由或匿名路由 pushed and popped 到 Navigator 的堆栈中。下一节是对这两种方法的简要概述。

    匿名路由

    大多数移动应用程序将屏幕彼此叠放在一起,例如堆叠在一起。在 Flutter 中,使用 Navigator 很容易实现。

    MaterialAppCupertinoApp 在 hool 下使用 Navigator。您可以使用 Navigator.of() 访问导航,或者使用 Navigator.push() 显示新屏,然后使用 Navigator.pop() 返回上一个屏:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(Nav2App());
    }
    
    class Nav2App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          home: HomeScreen(),
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: FlatButton(
              child: Text('View Details'),
              onPressed: () {
                Navigator.push(
                  context,
                  MaterialPageRoute(builder: (context) {
                    return DetailScreen();
                  }),
                );
              },
            ),
          ),
        );
      }
    }
    
    class DetailScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: FlatButton(
              child: Text('Pop!'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
          ),
        );
      }
    }
    

    调用 push() 时,DetailScreen 小部件将放置在 HomeScreen 小部件的顶部,如下所示:

    看不见图的话请翻和谐墙

    上一屏(HomeScreen)仍是小部件树的一部分,因此与它关联的任何 State 对象都将停留在 DetailScreen 可见时。

    命名路由

    Flutter 还支持命名路由,该路由在 MaterialAppCupertinoApproutes 参数中定义:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(Nav2App());
    }
    
    class Nav2App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          routes: {
            '/': (context) => HomeScreen(),
            '/details': (context) => DetailScreen(),
          },
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: FlatButton(
              child: Text('View Details'),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/details',
                );
              },
            ),
          ),
        );
      }
    }
    
    class DetailScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: FlatButton(
              child: Text('Pop!'),
              onPressed: () {
                Navigator.pop(context);
              },
            ),
          ),
        );
      }
    }
    

    这些路由必须是预定义的。尽管您可以将参数传递给命名路由,但是您无法从路由本身解析参数。例如,如果该应用程序在网络上运行,则无法从 /details/:id 之类的路由中解析 ID。

    使用 onGenerateRoute 的高级命名路由

    处理命名路由的一种更灵活的方法是使用 onGenerateRoute。该 API 使您能够处理所有路径:

    onGenerateRoute: (settings) {
      // Handle '/'
      if (settings.name == '/') {
        return MaterialPageRoute(builder: (context) => HomeScreen());
      }
      
      // Handle '/details/:id'
      var uri = Uri.parse(settings.name);
      if (uri.pathSegments.length == 2 &&
          uri.pathSegments.first == 'details') {
        var id = uri.pathSegments[1];
        return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
      }
      
      return MaterialPageRoute(builder: (context) => UnknownScreen());
    },
    

    这是完整的例子:

    import 'package:flutter/material.dart';
    
    void main() {
      runApp(Nav2App());
    }
    
    class Nav2App extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          onGenerateRoute: (settings) {
            // Handle '/'
            if (settings.name == '/') {
              return MaterialPageRoute(builder: (context) => HomeScreen());
            }
    
            // Handle '/details/:id'
            var uri = Uri.parse(settings.name);
            if (uri.pathSegments.length == 2 &&
                uri.pathSegments.first == 'details') {
              var id = uri.pathSegments[1];
              return MaterialPageRoute(builder: (context) => DetailScreen(id: id));
            }
    
            return MaterialPageRoute(builder: (context) => UnknownScreen());
          },
        );
      }
    }
    
    class HomeScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: FlatButton(
              child: Text('View Details'),
              onPressed: () {
                Navigator.pushNamed(
                  context,
                  '/details/1',
                );
              },
            ),
          ),
        );
      }
    }
    
    class DetailScreen extends StatelessWidget {
      String id;
    
      DetailScreen({
        this.id,
      });
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: Column(
              mainAxisAlignment: MainAxisAlignment.center,
              children: [
                Text('Viewing details for item $id'),
                FlatButton(
                  child: Text('Pop!'),
                  onPressed: () {
                    Navigator.pop(context);
                  },
                ),
              ],
            ),
          ),
        );
      }
    }
    
    class UnknownScreen extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(),
          body: Center(
            child: Text('404!'),
          ),
        );
      }
    }
    

    在这里,settingsRouteSettings 的一个实例。名称和参数字段是在调用 Navigator.pushNamed 或设置为 initialRoute 时提供的值。

    Navigator 2.0

    Navigator 2.0 API 向框架中添加了新类,以使应用程序的屏幕具有应用程序状态的功能,并能够解析来自底层平台(如 Web URL)的路由。以下是新功能概述:

    • Page — 一个不可变的(immutable)对象,用于设置 Navigator 的历史记录堆栈。
    • Router — 配置要由 Navigator 显示的页面列表。通常,此页面列表根据基础平台或应用程序更改状态而更改。
    • RouteInformationParser,它从 RouteInformationProvider 获取 RouteInformation 并将其解析为用户定义的数据类型。
    • RouterDelegate — 定义特定于应用程序的行为,例如路由器如何了解应用程序状态的更改以及如何响应更改。它的工作是监听 RouteInformationParser 和应用程序状态,并使用当前 Pages 列表构建 Navigator
    • BackButtonDispatcher — 向 Router 报告后退按钮按下情况。

    下图显示了 RouterDelegate 如何与 RouterRouteInformationParser 和应用程序状态进行交互:

    看不见图的话请翻和谐墙

    这是这些实例如何相互作用的一个例子:

    • 当平台发出新路由(例如“books/2”)时,RouteInformationParser 会将其转换为您在应用程序中定义的抽象数据类型 T(例如,名为 BooksRoutePath 的类)。
    • 使用此数据类型调用 RouterDelegatesetNewRoutePath 方法,并且必须更新应用程序状态以反映更改(例如,通过设置 selectedBookId)并调用 notifyListeners
    • 调用 notifyListeners 时,它告诉路由器重建 RouterDelegate(使用其 build() 方法)
    • RouterDelegate.build() 返回一个新的 Navigator,该 Navigator 的页面现在反映了对应用程序状态的更改(例如,selectedBookId)。

    下一篇:Navigator 2.0 练习