Skip to content

第8章:Flutter 路由与导航

8.1 路由的概念

什么是路由?

在 Flutter 中,路由是指页面之间的导航机制,类似于网页中的 URL 跳转。

路由的作用

  • 实现页面之间的跳转
  • 传递数据和参数
  • 管理页面栈
  • 控制页面的进出场动画

路由的基本概念

  • 路由栈:Flutter 使用栈(Stack)来管理路由,新页面入栈,返回时出栈
  • 路由对象:每个页面都是一个 Widget,通过 Navigator 进行管理
  • 路由名称:为路由分配一个唯一的字符串标识符,方便管理

8.2 基础路由(Navigator)

Navigator 是 Flutter 中用于管理路由的核心类,提供了基本的路由操作方法。

基本路由操作

跳转到新页面

使用 Navigator.push() 方法跳转到新页面:

dart
// 主页面
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 跳转到第二页
            Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const SecondPage(),
              ),
            );
          },
          child: const Text('Go to Second Page'),
        ),
      ),
    );
  }
}

// 第二页
class SecondPage extends StatelessWidget {
  const SecondPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 返回上一页
            Navigator.pop(context);
          },
          child: const Text('Go Back'),
        ),
      ),
    );
  }
}

返回上一页

使用 Navigator.pop() 方法返回上一页:

dart
// 返回上一页
Navigator.pop(context);

// 返回上一页并传递数据
Navigator.pop(context, '返回的数据');

接收返回数据

使用 async/await 接收返回的数据:

dart
// 主页面
class HomePage extends StatelessWidget {
  const HomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 跳转到第二页并等待返回数据
            final result = await Navigator.push(
              context,
              MaterialPageRoute(
                builder: (context) => const SecondPage(),
              ),
            );
            
            // 显示返回的数据
            if (result != null) {
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('Received: $result')),
              );
            }
          },
          child: const Text('Go to Second Page'),
        ),
      ),
    );
  }
}

// 第二页
class SecondPage extends StatelessWidget {
  const SecondPage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Second Page')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 返回上一页并传递数据
            Navigator.pop(context, 'Hello from Second Page');
          },
          child: const Text('Go Back with Data'),
        ),
      ),
    );
  }
}

8.3 命名路由

命名路由的优势

  • 集中管理:所有路由在一个地方配置
  • 代码简洁:使用路由名称跳转,无需创建 MaterialPageRoute
  • 参数传递:支持通过 arguments 传递参数
  • 路由守卫:可以通过 onGenerateRoute 实现路由拦截

配置命名路由

MaterialApproutes 属性中配置命名路由:

dart
void main() {
  runApp(
    MaterialApp(
      title: 'Named Routes Demo',
      initialRoute: '/', // 默认路由
      routes: {
        '/': (context) => const HomePage(),
        '/second': (context) => const SecondPage(),
        '/third': (context) => const ThirdPage(),
      },
    ),
  );
}

使用命名路由跳转

使用 Navigator.pushNamed() 方法通过路由名称跳转:

dart
// 跳转到第二页
Navigator.pushNamed(context, '/second');

// 跳转到第二页并传递参数
Navigator.pushNamed(
  context, 
  '/second',
  arguments: 'Hello from Home Page',
);

接收路由参数

使用 ModalRoute.of(context)?.settings.arguments 接收参数:

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

  @override
  Widget build(BuildContext context) {
    // 接收参数
    final arguments = ModalRoute.of(context)?.settings.arguments;
    
    return Scaffold(
      appBar: AppBar(title: const Text('Second Page')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Received: $arguments'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}

命名路由返回数据

dart
// 跳转到第二页并等待返回数据
final result = await Navigator.pushNamed(context, '/second');

// 第二页返回数据
Navigator.pop(context, 'Hello from Second Page');

8.4 路由守卫

什么是路由守卫?

路由守卫是一种拦截路由跳转的机制,可以在路由跳转前进行检查,如权限验证、登录状态检查等。

使用 onGenerateRoute 实现路由守卫

dart
void main() {
  runApp(
    MaterialApp(
      title: 'Route Guard Demo',
      initialRoute: '/',
      onGenerateRoute: (settings) {
        // 路由守卫逻辑
        if (settings.name == '/profile') {
          // 检查登录状态
          if (!isLoggedIn) {
            // 未登录,跳转到登录页
            return MaterialPageRoute(
              builder: (context) => const LoginPage(),
            );
          }
        }
        
        // 正常路由处理
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(builder: (context) => const HomePage());
          case '/login':
            return MaterialPageRoute(builder: (context) => const LoginPage());
          case '/profile':
            return MaterialPageRoute(builder: (context) => const ProfilePage());
          default:
            return MaterialPageRoute(builder: (context) => const NotFoundPage());
        }
      },
    ),
  );
}

完整示例

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

// 模拟登录状态
bool isLoggedIn = false;

void main() {
  runApp(
    MaterialApp(
      title: 'Route Guard Demo',
      initialRoute: '/',
      onGenerateRoute: (settings) {
        // 路由守卫逻辑
        if (settings.name == '/profile') {
          if (!isLoggedIn) {
            return MaterialPageRoute(
              builder: (context) => const LoginPage(),
            );
          }
        }
        
        switch (settings.name) {
          case '/':
            return MaterialPageRoute(builder: (context) => const HomePage());
          case '/login':
            return MaterialPageRoute(builder: (context) => const LoginPage());
          case '/profile':
            return MaterialPageRoute(builder: (context) => const ProfilePage());
          default:
            return MaterialPageRoute(builder: (context) => const NotFoundPage());
        }
      },
    ),
  );
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/profile');
              },
              child: const Text('Go to Profile'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/login');
              },
              child: Text(isLoggedIn ? 'Logout' : 'Login'),
            ),
          ],
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Login')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 模拟登录
            isLoggedIn = true;
            Navigator.pop(context);
          },
          child: const Text('Login'),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Profile')),
      body: Center(
        child: ElevatedButton(
          onPressed: () {
            // 模拟登出
            isLoggedIn = false;
            Navigator.pop(context);
          },
          child: const Text('Logout'),
        ),
      ),
    );
  }
}

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('404')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            const Text('Page Not Found'),
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(context, '/');
              },
              child: const Text('Go Home'),
            ),
          ],
        ),
      ),
    );
  }
}

8.5 页面跳转动画

自定义页面跳转动画

使用 PageRouteBuilder 自定义页面跳转动画:

dart
// 跳转到新页面,使用自定义动画
Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const SecondPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      // 淡入淡出动画
      return FadeTransition(
        opacity: animation,
        child: child,
      );
      
      // 缩放动画
      /*
      return ScaleTransition(
        scale: animation,
        child: child,
      );
      */
      
      // 滑动动画
      /*
      return SlideTransition(
        position: Tween<Offset>(
          begin: const Offset(1.0, 0.0),
          end: Offset.zero,
        ).animate(animation),
        child: child,
      );
      */
    },
    transitionDuration: const Duration(milliseconds: 500),
  ),
);

常见动画类型

淡入淡出动画

dart
FadeTransition(
  opacity: animation,
  child: child,
)

缩放动画

dart
ScaleTransition(
  scale: animation,
  child: child,
)

滑动动画

dart
SlideTransition(
  position: Tween<Offset>(
    begin: const Offset(1.0, 0.0), // 从右侧进入
    end: Offset.zero,
  ).animate(animation),
  child: child,
)

旋转动画

dart
RotationTransition(
  turns: animation,
  child: child,
)

8.6 实操案例:实现多页面跳转

目标

创建一个包含多个页面的应用,实现页面之间的跳转、参数传递和返回。

步骤 1:创建页面

HomePage.dart

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

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Home')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            ElevatedButton(
              onPressed: () {
                Navigator.pushNamed(
                  context, 
                  '/details',
                  arguments: 'Item 123',
                );
              },
              child: const Text('Go to Details'),
            ),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () async {
                final result = await Navigator.pushNamed(context, '/settings');
                if (result != null) {
                  ScaffoldMessenger.of(context).showSnackBar(
                    SnackBar(content: Text('Settings saved: $result')),
                  );
                }
              },
              child: const Text('Go to Settings'),
            ),
          ],
        ),
      ),
    );
  }
}

DetailsPage.dart

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

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

  @override
  Widget build(BuildContext context) {
    final arguments = ModalRoute.of(context)?.settings.arguments;

    return Scaffold(
      appBar: AppBar(title: const Text('Details')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('Selected: $arguments'),
            const SizedBox(height: 20),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(context);
              },
              child: const Text('Go Back'),
            ),
          ],
        ),
      ),
    );
  }
}

SettingsPage.dart

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

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

  @override
  State<SettingsPage> createState() => _SettingsPageState();
}

class _SettingsPageState extends State<SettingsPage> {
  bool _notifications = true;
  bool _darkMode = false;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('Settings')),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            SwitchListTile(
              title: const Text('Notifications'),
              value: _notifications,
              onChanged: (value) {
                setState(() {
                  _notifications = value;
                });
              },
            ),
            SwitchListTile(
              title: const Text('Dark Mode'),
              value: _darkMode,
              onChanged: (value) {
                setState(() {
                  _darkMode = value;
                });
              },
            ),
            const SizedBox(height: 40),
            ElevatedButton(
              onPressed: () {
                Navigator.pop(
                  context, 
                  {'notifications': _notifications, 'darkMode': _darkMode}
                );
              },
              child: const Text('Save Settings'),
            ),
          ],
        ),
      ),
    );
  }
}

步骤 2:配置路由

dart
import 'package:flutter/material.dart';
import 'pages/home_page.dart';
import 'pages/details_page.dart';
import 'pages/settings_page.dart';

void main() {
  runApp(
    MaterialApp(
      title: 'Navigation Demo',
      initialRoute: '/',
      routes: {
        '/': (context) => const HomePage(),
        '/details': (context) => const DetailsPage(),
        '/settings': (context) => const SettingsPage(),
      },
    ),
  );
}

步骤 3:运行应用

  1. 启动模拟器或连接真机
  2. 运行项目
  3. 测试页面跳转、参数传递和返回功能

8.7 路由管理最佳实践

1. 使用命名路由

  • 集中管理所有路由
  • 使用常量定义路由名称
  • 方便维护和调试

2. 路由参数传递

  • 使用 arguments 参数传递数据
  • 对于复杂参数,使用对象或 Map
  • 接收参数时进行类型检查

3. 路由守卫

  • 使用 onGenerateRoute 实现权限控制
  • 处理未找到的路由
  • 统一路由处理逻辑

4. 动画效果

  • 根据应用风格选择合适的动画
  • 保持动画一致
  • 避免过度动画影响用户体验

5. 路由栈管理

  • 合理使用 pushpoppushReplacement 等方法
  • 避免路由栈过深
  • 处理返回逻辑

8.8 新手易错点

路由配置错误

错误

  • 忘记在 MaterialApp 中配置路由
  • 路由名称拼写错误
  • 路由名称缺少斜杠

解决方案

  • 仔细检查路由配置
  • 使用常量定义路由名称
  • 确保路由名称以斜杠开头

参数传递错误

错误

  • 传递参数类型与接收类型不匹配
  • 未处理 null 参数
  • 参数传递方式错误

解决方案

  • 确保参数类型一致
  • 使用 ???. 操作符处理 null
  • 正确使用 arguments 参数

路由守卫逻辑错误

错误

  • 路由守卫逻辑过于复杂
  • 未处理所有路由情况
  • 循环跳转

解决方案

  • 保持路由守卫逻辑简洁
  • 处理所有可能的路由情况
  • 避免循环跳转

动画性能问题

错误

  • 动画持续时间过长
  • 动画效果过于复杂
  • 频繁触发动画

解决方案

  • 保持动画简洁
  • 控制动画持续时间
  • 避免频繁触发动画

8.9 小结

本章介绍了 Flutter 的路由与导航,包括基础路由、命名路由、路由守卫、页面跳转动画等内容。路由与导航是 Flutter 应用中不可或缺的一部分,掌握好路由管理对于构建流畅的用户体验至关重要。

我们学习了:

  • 基础路由:使用 Navigator.push()Navigator.pop() 进行页面跳转
  • 命名路由:集中管理路由,使用路由名称跳转
  • 路由守卫:实现权限控制和路由拦截
  • 页面跳转动画:自定义页面跳转效果
  • 路由参数传递:在页面之间传递数据

通过实操案例,我们实现了一个包含多个页面的应用,体验了页面跳转、参数传递和返回的完整流程。在实际开发中,你应该根据应用的复杂度选择合适的路由管理方式。

在接下来的章节中,我们将学习 Flutter 的网络请求,掌握如何与后端接口交互,获取和处理数据。

© 2026 编程马·菜鸟教程 版权所有