Flutter——页面跳转(路由、导航)

14 阅读7分钟

Flutter 开发中实现页面跳转的核心知识点。Flutter 提供了两种导航方式:基础导航(适用于简单场景、少量页面)命名路由(适用于复杂场景、多页面应用)

一、核心概念

在 Flutter 中,所有页面都是「Widget」,导航的本质是「管理页面栈(Stack)」:

  • 「跳转页面」:将新页面「压入」栈顶(push)。
  • 「返回页面」:将当前页面「弹出」栈顶(pop)。
  • Flutter 提供了 Navigator 组件来管理页面栈,日常开发中常用 Navigator.of(context) 来获取导航实例。

方案 1:基础导航(直接跳转,简单高效)

适用于 页面数量少(3-5 个)、无需传参 / 少量传参 的场景(如简单工具类 App),核心是 Navigator.push()Navigator.pop()

1.1 基本页面跳转(无参)

步骤 1:定义两个页面 Widget(首页 + 详情页)

import 'package:flutter/material.dart';

void main() => runApp(const MyApp());

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 导航示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const HomePage(), // 首页
    );
  }
}

// 首页 Widget
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 核心:跳转至详情页(push 压入栈顶)
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailPage(), // 目标页面
              ),
            );
          },
          child: const Text('跳转到详情页'),
        ),
      ),
    );
  }
}

// 详情页 Widget
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('详情页'),
        // 导航栏默认自带返回按钮,点击等价于 Navigator.pop()
      ),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 核心:返回上一页(pop 弹出栈顶)
            Navigator.pop(context);
          },
          child: const Text('返回首页'),
        ),
      ),
    );
  }
}

关键说明:

  • MaterialPageRoute:Material 风格的路由过渡动画(Android 上下滑动、iOS 左右滑动),无需手动配置。
  • Navigator.push(context, route):将目标页面压入栈顶,实现跳转。
  • Navigator.pop(context):将当前页面弹出栈顶,返回上一页(无返回值)。
  • 详情页的 AppBar 会默认添加返回按钮,点击后自动调用 Navigator.pop(),无需手动实现。

1.2 页面传参(带参跳转 + 返回值传递)

实际开发中常需要给下一页传参(如传递商品 ID),或接收下一页的返回值(如选择图片后返回图片路径)。

步骤 1:带参跳转(首页 → 详情页,传递字符串 / 数字)

修改 DetailPage 增加构造函数接收参数,修改首页的跳转逻辑:

// 详情页(修改:添加构造函数接收参数)
class DetailPage extends StatelessWidget {
  final String title; // 接收的标题参数
  final int id; // 接收的ID参数

  // 构造函数(必填参数,也可设置可选参数)
  const DetailPage({super.key, required this.title, required this.id});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(title)),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('接收的ID:$id'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                // 返回上一页,并携带返回值("从详情页返回的内容")
                Navigator.pop(context, "从详情页返回的内容");
              },
              child: const Text('返回首页并传值'),
            ),
          ],
        ),
      ),
    );
  }
}

// 首页(修改:带参跳转 + 接收返回值)
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 异步接收返回值(await 等待 Navigator.push 执行完成)
            final result = await Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const DetailPage(
                  title: '详情页(带参)',
                  id: 1001,
                ),
              ),
            );

            // 打印返回值(详情页传递的内容)
            if (result != null) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('接收的返回值:$result')),
              );
            }
          },
          child: const Text('带参跳转到详情页'),
        ),
      ),
    );
  }
}

关键说明:

  1. 给下一页传参:通过目标页面的「构造函数」传递,支持所有 Dart 数据类型(字符串、数字、对象等)。
  2. 接收下一页返回值Navigator.push() 是异步操作,返回 Future,通过 await 可接收目标页面 pop 时传递的参数。
  3. Navigator.pop(context, result) :第二个参数是返回值,可省略(无返回值时)。

方案 2:命名路由(配置式跳转,适合复杂应用)

适用于 页面数量多(5 个以上)、需要统一管理、频繁跳转 的场景(如电商、社交 App),核心是「提前配置路由表,通过路由名称跳转」。

2.1 基本配置与跳转(无参)

步骤 1:在 MaterialApp 中配置 routes 路由表

路由表是一个 Map<String, WidgetBuilder>,key 是路由名称(通常以 / 开头),value 是页面构建函数。

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 命名路由示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      // 1. 配置路由表
      routes: {
        '/': (context) => const HomePage(), // 首页(默认路由)
        '/detail': (context) => const DetailPage(), // 详情页
      },
      // 2. 设置默认启动页面(等价于 home: const HomePage())
      initialRoute: '/',
    );
  }
}

步骤 2:通过路由名称跳转 / 返回

修改首页的跳转逻辑,使用 Navigator.pushNamed() 替代 Navigator.push()

// 首页
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('首页')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 核心:通过路由名称跳转
            Navigator.pushNamed(context, '/detail');
          },
          child: const Text('命名路由跳转到详情页'),
        ),
      ),
    );
  }
}

关键说明:

  • routes:路由表,统一管理所有页面,便于后期维护(修改页面只需修改路由表,无需修改所有跳转处)。
  • initialRoute:默认启动路由(替代 home),必须和路由表中的某个 key 对应。
  • Navigator.pushNamed(context, '/detail'):通过路由名称跳转,无需再创建 MaterialPageRoute
  • 返回逻辑不变,依然使用 Navigator.pop(context)

2.2 命名路由传参(带参跳转)

命名路由传参有两种常用方式:arguments 传参(推荐)、路由名称拼接参数(不推荐,仅适用于简单参数)。

方式 1:arguments 传参(推荐,支持任意数据类型)

  1. 跳转时通过 arguments 传递参数:
// 首页跳转逻辑修改
ElevatedButton(
  onPressed: () {
    // 传递参数(可传字符串、数字、Map、自定义对象等)
    Navigator.pushNamed(
      context,
      '/detail',
      arguments: {
        'title': '详情页(命名路由传参)',
        'id': 1001,
      },
    );
  },
  child: const Text('命名路由带参跳转'),
),
  1. 详情页通过 ModalRoute.of(context)?.settings.arguments 接收参数:
// 详情页修改
class DetailPage extends StatelessWidget {
  const DetailPage({super.key});

  @override
  Widget build(BuildContext context) {
    // 接收 arguments 参数
    final Map<String, dynamic> args = ModalRoute.of(context)?.settings.arguments as Map<String, dynamic>;

    return Scaffold(
      appBar: AppBar(title: Text(args['title'])),
      body: Center(
        child: Text('接收的ID:${args['id']}'),
      ),
    );
  }
}

方式 2:路由名称拼接参数(仅适用于简单参数,如 ID)

// 跳转时拼接参数
Navigator.pushNamed(context, '/detail/1001');

// 配置路由表时使用通配符
routes: {
  '/detail/:id': (context) => const DetailPage(),
},

// 详情页提取参数(需额外解析,较繁琐,不推荐)
final String id = ModalRoute.of(context)?.settings.name?.split('/').last ?? '';

2.3 未知路由处理(404 页面)

当跳转的路由名称不在路由表中时,会出现错误,可通过 onUnknownRoute 配置兜底页面(404 页面):

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 命名路由示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      routes: {
        '/': (context) => const HomePage(),
        '/detail': (context) => const DetailPage(),
      },
      initialRoute: '/',
      // 未知路由兜底
      onUnknownRoute: (settings) {
        return MaterialPageRoute(
          builder: (context) => const NotFoundPage(),
        );
      },
    );
  }
}

// 404 页面
class NotFoundPage extends StatelessWidget {
  const NotFoundPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('页面不存在')),
      body: const Center(child: Text('404 - 页面未找到')),
    );
  }
}

方案 3:进阶:路由管理(封装路由工具类)

当项目页面非常多时,直接在 MaterialApp 中配置路由表会显得臃肿,推荐封装「路由工具类」,统一管理路由跳转、传参、拦截等逻辑。

3.1 封装路由工具类

import 'package:flutter/material.dart';
import 'package:your_project_name/pages/home_page.dart';
import 'package:your_project_name/pages/detail_page.dart';
import 'package:your_project_name/pages/not_found_page.dart';

// 路由名称常量(避免硬编码,便于维护)
class RouteNames {
  static const String home = '/';
  static const String detail = '/detail';
}

// 路由工具类
class RouteManager {
  // 配置路由表
  static Map<String, WidgetBuilder> routes = {
    RouteNames.home: (context) => const HomePage(),
    RouteNames.detail: (context) => const DetailPage(),
  };

  // 路由跳转(封装 pushNamed,统一处理传参)
  static Future<T?> pushNamed<T>(BuildContext context, String routeName, {Object? arguments}) {
    return Navigator.pushNamed<T>(context, routeName, arguments: arguments);
  }

  // 返回上一页(封装 pop,统一处理返回值)
  static void pop<T>(BuildContext context, {T? result}) {
    Navigator.pop<T>(context, result);
  }

  // 未知路由处理
  static Route<dynamic> onUnknownRoute(RouteSettings settings) {
    return MaterialPageRoute(
      builder: (context) => const NotFoundPage(),
    );
  }
}

3.2 使用封装后的路由工具类

// MyApp 中配置
class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter 路由封装示例',
      theme: ThemeData(primarySwatch: Colors.blue),
      routes: RouteManager.routes,
      initialRoute: RouteNames.home,
      onUnknownRoute: RouteManager.onUnknownRoute,
    );
  }
}

// 首页跳转
ElevatedButton(
  onPressed: () {
    RouteManager.pushNamed(
      context,
      RouteNames.detail,
      arguments: {'title': '详情页(封装路由)', 'id': 1001},
    );
  },
  child: const Text('封装路由带参跳转'),
),

// 详情页返回
ElevatedButton(
  onPressed: () {
    RouteManager.pop(context, result: "从封装路由详情页返回");
  },
  child: const Text('返回首页'),
),

关键补充:路由过渡动画自定义

默认的 MaterialPageRoute 是 Material 风格动画,若需要自定义过渡动画(如淡入淡出、缩放),可使用 PageRouteBuilder

// 自定义淡入淡出动画跳转
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      // 淡入淡出动画
      return FadeTransition(
        opacity: animation,
        child: child,
      );
    },
    transitionDuration: const Duration(milliseconds: 500), // 动画时长
  ),
);

总结

  1. Flutter 导航核心是「页面栈管理」,通过 Navigator 实现 push(跳转)和 pop(返回)。
  2. 简单场景用「基础导航」(Navigator.push),快速高效;复杂场景用「命名路由」(Navigator.pushNamed),便于统一管理。
  3. 传参方式:基础导航用「构造函数」,命名路由用「arguments」(推荐)。
  4. 最佳实践:页面较多时封装「路由工具类」,统一管理路由表、跳转、未知路由,提升项目可维护性。
  5. 自定义过渡动画可使用 PageRouteBuilder,满足个性化 UI 需求。