Flutter 小白入门教程Flutter 小白入门教程
首页
学习指南
项目实战
Flutter 官网
编程指南
首页
学习指南
项目实战
Flutter 官网
编程指南
  • 入门基础

    • 📚 基础教程
    • 第1章 - 认识 Flutter
    • 第2章 - 环境搭建
    • 第3章 - Dart 语言基础
    • 第4章 - 第一个 Flutter 应用
    • 第5章 - Widget 基础
    • 第6章 - 布局系统
    • 第7章 - 状态管理入门
    • 第8章 - 页面导航
    • 第9章 - 资源管理
  • 进阶开发

    • 第10章 - 网络请求
    • 第11章 - 本地存储
    • 第12章 - 对话框与反馈
    • 第13章 - 列表进阶
    • 第14章 - 主题定制
    • 第15章 - 状态管理进阶
    • 第16章 - 动画入门
    • 第17章 - 常用第三方包
  • 调试与发布

    • 第18章 - 调试与性能优化
    • 第19章 - 打包与发布
  • 附录

    • 附录A - UI 框架与组件库推荐
    • 附录B - 项目结构最佳实践
    • 附录C - 国际化配置
    • 附录D - 权限处理

第8章 - 页面导航

嗨,朋友!我是长安。

一个完整的 App 通常有多个页面,比如首页、详情页、设置页等。这一章,我们来学习 Flutter 中的页面导航(Navigation),实现页面之间的跳转和传参。

🤔 Flutter 中的路由

在 Flutter 中,每个页面被称为一个路由(Route)。页面导航就是在不同路由之间切换。

Flutter 使用**栈(Stack)**来管理路由:

         ┌─────────┐
         │  页面C   │  ← 栈顶(当前显示的页面)
         ├─────────┤
         │  页面B   │
         ├─────────┤
         │  页面A   │  ← 栈底
         └─────────┘

push(页面C) → 页面C 入栈
pop() → 页面C 出栈,显示页面B

🚀 基础导航

1. 跳转到新页面(push)

// 方式一:使用 MaterialPageRoute
Navigator.push(
  context,
  MaterialPageRoute(builder: (context) => const SecondPage()),
);

// 方式二:简写
Navigator.of(context).push(
  MaterialPageRoute(builder: (context) => const SecondPage()),
);

2. 返回上一页(pop)

Navigator.pop(context);

// 或
Navigator.of(context).pop();

3. 完整示例

// 首页
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.push(
              context,
              MaterialPageRoute(builder: (context) => const DetailPage()),
            );
          },
          child: const Text('去详情页'),
        ),
      ),
    );
  }
}

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

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

📤 页面间传递数据

1. 跳转时传参

通过构造函数传递数据:

// 定义接收参数的页面
class ProductDetailPage extends StatelessWidget {
  final String productId;
  final String productName;

  const ProductDetailPage({
    super.key,
    required this.productId,
    required this.productName,
  });

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(productName)),
      body: Center(
        child: Text('商品ID: $productId'),
      ),
    );
  }
}

// 跳转时传参
Navigator.push(
  context,
  MaterialPageRoute(
    builder: (context) => ProductDetailPage(
      productId: '12345',
      productName: 'Flutter 入门书籍',
    ),
  ),
);

2. 返回时带数据

// 页面A:等待返回结果
class PageA extends StatelessWidget {
  const PageA({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('页面A')),
      body: Center(
        child: ElevatedButton(
          onPressed: () async {
            // 等待页面B返回的结果
            final result = await Navigator.push<String>(
              context,
              MaterialPageRoute(builder: (context) => const PageB()),
            );
            
            if (result != null) {
              // 使用返回的数据
              ScaffoldMessenger.of(context).showSnackBar(
                SnackBar(content: Text('选择了: $result')),
              );
            }
          },
          child: const Text('选择选项'),
        ),
      ),
    );
  }
}

// 页面B:返回数据
class PageB extends StatelessWidget {
  const PageB({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('页面B')),
      body: ListView(
        children: [
          ListTile(
            title: const Text('选项 1'),
            onTap: () => Navigator.pop(context, '选项 1'),  // 返回数据
          ),
          ListTile(
            title: const Text('选项 2'),
            onTap: () => Navigator.pop(context, '选项 2'),
          ),
          ListTile(
            title: const Text('选项 3'),
            onTap: () => Navigator.pop(context, '选项 3'),
          ),
        ],
      ),
    );
  }
}

🏷️ 命名路由

当应用页面较多时,可以使用命名路由统一管理。

1. 定义路由表

在 MaterialApp 中定义:

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'Flutter Demo',
      // 初始路由
      initialRoute: '/',
      // 路由表
      routes: {
        '/': (context) => const HomePage(),
        '/detail': (context) => const DetailPage(),
        '/settings': (context) => const SettingsPage(),
        '/profile': (context) => const ProfilePage(),
      },
    );
  }
}

2. 使用命名路由跳转

// 跳转到命名路由
Navigator.pushNamed(context, '/detail');

// 带参数跳转
Navigator.pushNamed(
  context,
  '/detail',
  arguments: {'id': '123', 'title': '商品标题'},
);

3. 获取传递的参数

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

  @override
  Widget build(BuildContext context) {
    // 获取传递的参数
    final args = ModalRoute.of(context)!.settings.arguments as Map<String, String>;
    
    return Scaffold(
      appBar: AppBar(title: Text(args['title'] ?? '')),
      body: Center(
        child: Text('ID: ${args['id']}'),
      ),
    );
  }
}

🔄 导航的其他操作

1. 替换当前页面

// 用新页面替换当前页面(不能返回)
Navigator.pushReplacement(
  context,
  MaterialPageRoute(builder: (context) => const NewPage()),
);

// 命名路由版本
Navigator.pushReplacementNamed(context, '/new');

2. 清空栈并跳转

// 清空所有页面,跳转到新页面(常用于登录后跳转首页)
Navigator.pushAndRemoveUntil(
  context,
  MaterialPageRoute(builder: (context) => const HomePage()),
  (route) => false,  // 移除所有之前的路由
);

// 命名路由版本
Navigator.pushNamedAndRemoveUntil(context, '/home', (route) => false);

3. 返回到指定页面

// 返回到第一个页面
Navigator.popUntil(context, (route) => route.isFirst);

// 返回到指定命名路由
Navigator.popUntil(context, ModalRoute.withName('/home'));

📱 底部导航栏

大多数 App 都有底部导航栏,这是一种特殊的导航模式。

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

  @override
  State<MainPage> createState() => _MainPageState();
}

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;
  
  // 页面列表
  final List<Widget> _pages = [
    const HomePage(),
    const SearchPage(),
    const MessagePage(),
    const ProfilePage(),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        type: BottomNavigationBarType.fixed,  // 固定模式
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: const [
          BottomNavigationBarItem(
            icon: Icon(Icons.home),
            label: '首页',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.search),
            label: '搜索',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.message),
            label: '消息',
          ),
          BottomNavigationBarItem(
            icon: Icon(Icons.person),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

// 各个页面
class HomePage extends StatelessWidget {
  const HomePage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('首页'));
  }
}

class SearchPage extends StatelessWidget {
  const SearchPage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('搜索'));
  }
}

class MessagePage extends StatelessWidget {
  const MessagePage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('消息'));
  }
}

class ProfilePage extends StatelessWidget {
  const ProfilePage({super.key});
  @override
  Widget build(BuildContext context) {
    return const Center(child: Text('我的'));
  }
}

📑 TabBar 导航

顶部 Tab 切换导航:

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

  @override
  Widget build(BuildContext context) {
    return DefaultTabController(
      length: 3,  // Tab 数量
      child: Scaffold(
        appBar: AppBar(
          title: const Text('TabBar 示例'),
          bottom: const TabBar(
            tabs: [
              Tab(icon: Icon(Icons.directions_car), text: '汽车'),
              Tab(icon: Icon(Icons.directions_transit), text: '公交'),
              Tab(icon: Icon(Icons.directions_bike), text: '自行车'),
            ],
          ),
        ),
        body: const TabBarView(
          children: [
            Center(child: Text('汽车内容')),
            Center(child: Text('公交内容')),
            Center(child: Text('自行车内容')),
          ],
        ),
      ),
    );
  }
}

🎯 页面转场动画

自定义转场动画

Navigator.push(
  context,
  PageRouteBuilder(
    pageBuilder: (context, animation, secondaryAnimation) => const DetailPage(),
    transitionsBuilder: (context, animation, secondaryAnimation, child) {
      // 淡入效果
      return FadeTransition(opacity: animation, child: child);
      
      // 或者滑动效果
      // return SlideTransition(
      //   position: Tween<Offset>(
      //     begin: const Offset(1, 0),
      //     end: Offset.zero,
      //   ).animate(animation),
      //   child: child,
      // );
    },
    transitionDuration: const Duration(milliseconds: 300),
  ),
);

📝 小结

这一章我们学习了:

  • ✅ 路由的基本概念(栈结构)
  • ✅ push 和 pop 导航
  • ✅ 页面间传递数据(跳转传参、返回带参)
  • ✅ 命名路由的使用
  • ✅ 导航的其他操作(替换、清空栈)
  • ✅ 底部导航栏实现
  • ✅ TabBar 导航
  • ✅ 自定义转场动画

导航选择指南

场景推荐方式
简单应用基础导航(push/pop)
中型应用命名路由
主要页面切换底部导航栏 / TabBar
复杂应用go_router 等路由库

💪 练习题

  1. 创建一个包含 3 个页面的应用,实现页面之间的跳转和返回
  2. 实现一个商品列表页,点击商品跳转到详情页,并传递商品信息
  3. 创建一个带底部导航栏的应用,包含首页、分类、购物车、我的四个页面
点击查看答案

练习2:商品列表和详情

// 商品模型
class Product {
  final String id;
  final String name;
  final double price;
  
  Product({required this.id, required this.name, required this.price});
}

// 商品列表页
class ProductListPage extends StatelessWidget {
  final List<Product> products = [
    Product(id: '1', name: 'Flutter 实战', price: 99.0),
    Product(id: '2', name: 'Dart 入门', price: 59.0),
    Product(id: '3', name: '移动开发指南', price: 79.0),
  ];

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('商品列表')),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          final product = products[index];
          return ListTile(
            title: Text(product.name),
            subtitle: Text('¥${product.price}'),
            onTap: () {
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProductDetailPage(product: product),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

// 商品详情页
class ProductDetailPage extends StatelessWidget {
  final Product product;
  
  const ProductDetailPage({super.key, required this.product});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(product.name)),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text(product.name, style: const TextStyle(fontSize: 24)),
            const SizedBox(height: 10),
            Text('¥${product.price}', 
                style: const TextStyle(fontSize: 20, color: Colors.red)),
            const Spacer(),
            SizedBox(
              width: double.infinity,
              child: ElevatedButton(
                onPressed: () {},
                child: const Text('加入购物车'),
              ),
            ),
          ],
        ),
      ),
    );
  }
}

🚀 下一步

恭喜你!基础教程全部完成!🎉

现在你已经掌握了 Flutter 的核心知识,是时候用项目实战来巩固所学了。前往 项目实战,我们一起来开发一个完整的 App!


由 编程指南 提供

最近更新: 2026/2/3 16:24
Contributors: 王长安
Prev
第7章 - 状态管理入门
Next
第9章 - 资源管理