Flutter路由导航(1):基本使用

 


一、路由管理

1.1 认识Flutter路由

路由的概念由来已久,包括网络路由后端路由,到现在广为流行的前端路由

  • 无论路由的概念如何应用,它的核心是一个路由映射表
  • 比如:名字 detail 映射到 DetailPage 页面等
  • 有了这个映射表之后,我们就可以方便的根据名字来完成路由的转发(在前端表现出来的就是页面跳转)

在 Flutter 中,路由管理主要有两个类:Route 和 Navigator


1.2. Route

Route:一个页面要想被路由统一管理,必须包装为一个 Route

  • 官方的说法很清晰:An abstraction for an entry managed by a Navigator.

但是 Route 是一个抽象类,所以它是不能实例化的

  • 在上面有一段注释,让我们查看 MaterialPageRoute 来使用
/// See [MaterialPageRoute] for a route that replaces the
/// entire screen with a platform-adaptive transition.
abstractclass Route<T> {
}

事实上 MaterialPageRoute 并不是 Route 的直接子类:

  • MaterialPageRoute 在不同的平台有不同的表现
  • 对 Android 平台,打开一个页面会从屏幕底部滑动到屏幕的顶部,关闭页面时从顶部滑动到底部消失
  • 对 iOS 平台,打开一个页面会从屏幕右侧滑动到屏幕的左侧,关闭页面时从左侧滑动到右侧消失
  • 当然,iOS 平台我们也可以使用 CupertinoPageRoute
MaterialPageRoute -> PageRoute -> ModalRoute -> TransitionRoute -> OverlayRoute -> Route

1.3 Navigator

在 Flutter 中,Navigator 是一个管理应用视图(页面)的组件,它使用栈(Stack)的方式来控制页面的切换。每当你跳转到一个新页面时,Navigator 会将新页面的 Route 压栈(push),当你返回到之前的页面时,它会将当前页面的 Route 出栈(pop)。

为了使用 Navigator 进行页面跳转,我们需要使用 BuildContext,它表示当前 widget 在 widget 树中的位置。BuildContext 是用于与 Navigator 进行交互的必要参数。

Navigator 有几个最常见的方法:

// 路由跳转:传入一个路由对象
Future<T> push<T extendsObject>(Route<T> route)

// 路由跳转:传入一个名称(命名路由)
Future<T> pushNamed<T extendsObject>(
  String routeName, {
    Object arguments,
  })

// 路由返回:可以传入一个参数
bool pop<T extendsObject>([ T result ])

二、路由基本使用

2.1 基本跳转

我们来实现一个最基本跳转:

  • 创建首页页面,中间添加一个按钮,点击按钮跳转到详情页面
  • 创建详情页面,中间添加一个按钮,点击按钮返回到首页页面

Flutter_page_A.gif


核心的跳转代码如下(首页中代码):

ElevatedButton(
  child: Text("打开详情页"),
  onPressed: () => _onPushTap(context),
),

// 按钮点击执行的代码
_onPushTap(BuildContext context) {
  Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
    return DetailPage();
  }));
}

核心的返回代码如下(详情页中代码):

ElevatedButton(
  child: Text("返回首页"),
  onPressed: () => _onBackTap(context),
),

// 按钮点击执行的代码
_onBackTap(BuildContext context) {
  Navigator.of(context).pop();
}        

完整代码如下:

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MyHomeBody(),
      ),
    );
  }
}

// 首页
class MyHomeBody extends StatelessWidget {
  const MyHomeBody({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('首页'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text("打开详情页"),
          onPressed: () => _onPushTap(context),
        ),
      ),
    );
  }
}

// 按钮点击执行的代码
_onPushTap(BuildContext context) {
  Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
    return DetailPage();
  }));
}

// 详情页
class DetailPage extends StatelessWidget {
  const DetailPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('详情页'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text("返回首页"),
          onPressed: () => _onBackTap(context),
        ),
      ),
    );
  }
}

// 按钮点击执行的代码
_onBackTap(BuildContext context) {
  Navigator.of(context).pop();
}

2.2 参数传递

在跳转过程中,我们通常可能会携带一些参数,比如

  • 首页跳到详情页,携带一条信息:a home message
  • 详情页返回首页,携带一条信息:a detail message

Flutter_page_B.gif


首页跳转核心代码:

  • 在页面跳转时,会返回一个 Future
  • 该 Future 会在详情页面调用 pop 时,回调对应的 then 函数,并且会携带结果
// 按钮点击执行的代码
_onPushTap(BuildContext context) {
  // 1.跳转代码
  final future = Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
    return DetailPage(data: "a home message");
  }));

  // 2.获取结果
  /*future.then((res) {
    setState(() {
      _message = res;
    });
  });*/
}

详情页返回核心代码:

// 按钮点击执行的代码
_onBackTap(BuildContext context) {
  Navigator.of(context).pop("a detail message");
}

完整代码如下:

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';

main(List<String> args) {
  runApp(MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: Scaffold(
        body: MyHomeBody(),
      ),
    );
  }
}

// 首页
class MyHomeBody extends StatelessWidget {
  const MyHomeBody({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('首页'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text("打开详情页"),
          onPressed: () => _onPushTap(context),
        ),
      ),
    );
  }
}

// 按钮点击执行的代码
_onPushTap(BuildContext context) {
  // 1.跳转代码
  final future = Navigator.of(context).push(MaterialPageRoute(builder: (ctx) {
    return DetailPage(data: "a home message");
  }));

  // 2.获取结果
  /*future.then((res) {
    setState(() {
      _message = res;
    });
  });*/
}

// 详情页
class DetailPage extends StatelessWidget {
  final String data;

  DetailPage({required this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('详情页'),
      ),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center, // 主轴居中排列
          crossAxisAlignment: CrossAxisAlignment.center,
          mainAxisSize: MainAxisSize.max,
          children: <Widget>[
            Text(data),
            SizedBox(height: 20.0), // 这里是间隔
            ElevatedButton(
              child: Text("返回首页"),
              onPressed: () => _onBackTap(context),
            ),
          ],
        ),
      ),
    );
  }
}

// 按钮点击执行的代码
_onBackTap(BuildContext context) {
  Navigator.of(context).pop("a detail message");
}

2.3 返回细节

但是这里有一个问题,如果用户是点击右上角的返回按钮,如何监听呢?

方法一:自定义返回的按钮(在详情页中修改 Scaffold 的 appBar)

appBar: AppBar(
  title: Text("详情页"),
  leading: IconButton(
    icon: Icon(Icons.arrow_back),
    onPressed: () {
      Navigator.of(context).pop("a back detail message");
    },
  ),
),

方法二:监听返回按钮的点击(给 Scaffold 包裹一个 WillPopScope)

  • WillPopScope 有一个 onWillPop 的回调函数,当我们点击返回按钮时会执行

  • 这个函数要求有一个 Future 的返回值:

    • true:那么系统会自动帮我们执行 pop 操作
    • false:系统不再执行 pop 操作,需要我们自己来执行
return WillPopScope(
  onWillPop: () {
    Navigator.of(context).pop("a back detail message");
    return Future.value(false);
  },
  child: Scaffold(
    appBar: AppBar(
      title: Text("详情页"),
    ),
    body: Center(
      child: Column(
        mainAxisAlignment: MainAxisAlignment.center,
        children: <Widget>[
          RaisedButton(
            child: Text("返回首页"),
            onPressed: () => _onBackTap(context),
          ),
          Text(_message, style: TextStyle(fontSize: 20, color: Colors.red),)
        ],
      ),
    ),
  ),
);

三、命名路由使用

我们可以通过创建一个新的 Route,使用 Navigator 来导航到一个新的页面,但是如果在应用中很多地方都需要导航到同一个页面(比如在开发中,首页、推荐、分类页都可能会跳到详情页),那么就会存在很多重复的代码。在这种情况下,我们可以使用命名路由(named route)

命名路由是一种用于管理页面导航的技术,它允许你为每个页面分配一个唯一的名称,并通过这些名称在应用程序中进行页面之间的导航。命名路由,由一对字符串(路由名称)和对应的屏幕(或称为页面/视图)组成。

命名路由的好处:

  • 提高代码可维护性:命名路由使得路由和它们对应的屏幕解耦,这让查找和修改特定路由相关的代码变得更加容易。
  • 简化路由管理:当应用的结构变得更为复杂时,使用命名路由可以帮助集中管理路由,而不是在代码中散布大量的 Navigator.push 和 MaterialPageRoute。

配置命名路由

我们可以在 MaterialApproutes 属性中定义所有的命名路由。routes 是一个 Map,它的键是字符串(路由的名称),而值是对应的构造器函数,返回相应的页面 Widget。

MaterialApp(
    title: 'Navigation with Named Routes',
    // 初始路由,应用启动时加载的路由
    initialRoute: '/',
    // 定义命名路由
    routes: {
      '/': (context) => HomePage(),
      '/newPage': (context) => NewPage(),
      '/thirdPage': (context) => ThirdPage(),
    },
)

Navigator.pushNamed 方法

要使用命名路由进行页面跳转,可以调用 Navigator.pushNamed 方法,并传入对应的路由名称。

Navigator.pushNamed(context, '/newPage');

Navigator.pop 方法

Navigator.pop 方法用于将栈顶的 Route 弹出,返回到前一个页面。

Navigator.pop(context);

Navigator.popAndPushNamed 方法
Navigator.popAndPushNamed 方法用于从当前页面返回到上一个页面,并立即导航到指定的命名路由。

该方法的作用是先执行 Navigator.pop 方法返回到上一个页面,然后立即执行 Navigator.pushNamed 方法导航到新的命名路由

Navigator.popAndPushNamed(context, '/thirdPage');

配置和使用命名路由示例代码

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';

void main() {
  runApp(MaterialApp(
    title: 'Navigation with Named Routes',
    // 初始路由,应用启动时加载的路由
    initialRoute: '/',
    // 定义命名路由
    routes: {
      '/': (context) => HomePage(),
      '/newPage': (context) => NewPage(),
      '/thirdPage': (context) => ThirdPage(),
    },
  ));
}

// 首页
class HomePage extends StatelessWidget {
  const HomePage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('首页'),
      ),
      body: Center(
        child: ElevatedButton(
          child: Text('打开新页面'),
          // 使用命名路由进行页面跳转
          onPressed: () {
            Navigator.pushNamed(context, '/newPage');
          },
        ),
      ),
    );
  }
}

// 新页面
class NewPage extends StatelessWidget {
  const NewPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('新页面'),
      ),
      body: Column(
        children: [
          Text('欢迎来到新页面'),
          TextButton(
              onPressed: () {
                Navigator.popAndPushNamed(context, '/thirdPage');
              },
              child: Text("popAndPushNamed"))
        ],
      ),
    );
  }
}

// 第三页面
class ThirdPage extends StatelessWidget {
  const ThirdPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('第三页面'),
      ),
      body: Column(
        children: [
          Text('欢迎来到第三页面'),
          TextButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: Text("pop"))
        ],
      ),
    );
  }
}

效果图如下所示:

Flutter_page_C.gif


四、路由生成钩子

在 Flutter 中,onGenerateRoute 是一个非常强大的钩子,允许开发者对路由进行自定义操作。它在 MaterialApp 或 CupertinoApp 中定义,并在导航到命名路由时被调用,特别是当使用 Navigator.pushNamed 时。它可以用于动态生成路由,传递参数到新页面,甚至处理未知的路由。

下面是一个使用 onGenerateRoute 的示例,其中包括了处理动态路由和传递参数到未知页面的代码:

// ignore_for_file: prefer_const_constructors
import 'package:flutter/material.dart';

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

class MyApp extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      // 应用初始路由
      initialRoute: '/',
      // onGenerateRoute 用于处理动态路由
      onGenerateRoute: (RouteSettings settings) {
        // 获取传递过来的参数,如果参数为null,则提供一个空的Map
        final arguments = settings.arguments as Map<String, dynamic>? ?? {};

        // 根据 settings.name 处理不同路由
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(builder: (context) => HomePage());
          case '/details':
            // 假设 DetailsPage 接受一个 'data' 参数
            final String data = arguments['data'] as String? ?? '默认值';
            return MaterialPageRoute(
                builder: (context) => DetailsPage(data: data));
          default:
            // 如果没有匹配的路由,返回到一个未知页面路由
            return MaterialPageRoute(builder: (context) => UnknownPage());
        }
      },
    );
  }
}

class HomePage extends StatefulWidget {
  const HomePage({super.key});

  @override
  State<HomePage> createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> {
  String _data = "缺省值";

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('首页'),
      ),
      body: Column(
        children: [
          Text(_data), // 显示传递到本页面的数据
          ElevatedButton(
            onPressed: () async {
              // 导航到详情页,并传递数据。同时,使用await等待详情页返回的结果
              final result = await Navigator.pushNamed(
                context,
                '/details',
                arguments: {'data': '这是一个秘密信息!'},
              );
              final arguments = result as Map<String, dynamic>? ?? {};
              setState(() {
                if (mounted) {
                  _data = arguments["data"] as String? ?? "";
                }
              });
            },
            child: Text('前往详情页'),
          ),
        ],
      ),
    );
  }
}

class DetailsPage extends StatelessWidget {
  final String data;

  DetailsPage({required this.data});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('详情页'),
      ),
      body: Column(
        children: [
          Text(data), // 显示传递到本页面的数据
          TextButton(
              onPressed: () {
                // 回传数据数据
                Navigator.pop(context, {'data': '详情返回数据'});
              },
              child: Text("pop"))
        ],
      ),
    );
  }
}

class UnknownPage extends StatelessWidget {
  const UnknownPage({Key? key}) : super(key: key);
  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('未知页面'),
      ),
      body: Center(
        child: Text('该路由名称不存在。'),
      ),
    );
  }
}

效果图如下所示:

Flutter_page_D.gif


参考:

Flutter基础(一)页面跳转_flutter 页面跳转-CSDN博客


posted @   fengMisaka  阅读(403)  评论(0编辑  收藏  举报
相关博文:
阅读排行:
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· 实操Deepseek接入个人知识库
· CSnakes vs Python.NET:高效嵌入与灵活互通的跨语言方案对比
· 【.NET】调用本地 Deepseek 模型
· Plotly.NET 一个为 .NET 打造的强大开源交互式图表库
点击右上角即可分享
微信分享提示