【开源鸿蒙跨平台开发--Flutter】第四部分:多页面管理与导航 (Navigation & Routing)

在前面的章节中,我们已经掌握了单个页面的几乎所有构建要素:布局、列表、表单、弹窗。现在的挑战是:如何把这些独立的页面串联起来,构建一个完整的 App?

第四部分将聚焦于:

  1. 页面跳转与管理 (Navigator):核心的栈(Stack)管理机制,推入新页面与返回上一页。
  2. 参数传递 (Data Passing):如何在页面跳转时发送数据(如:点击商品列表 -> 进入商品详情页)。
  3. 整体导航结构 (App Structure):如何实现底部导航栏 (BottomNavigationBar)、顶部选项卡 (TabBar) 和侧边菜单 (Drawer)。

第四部分:多页面管理与导航 (Navigation & Routing)

第七章:导航与路由 (Navigation)

Flutter 的导航管理采用“栈”(Stack)的概念:

  • Push (推入):将一个新页面盖在当前页面之上。
  • Pop (弹出):将当前页面移出,露出下面的页面(即返回)。

1. 基础跳转 (Navigator.push & pop)

说明:
这是最直接的跳转方式,不需要预先注册路由表,适合大多数场景。

核心方法:

  • Navigator.of(context).push(Route route): 跳转。
  • Navigator.of(context).pop(): 返回。
  • MaterialPageRoute: Material 设计风格的路由(Android 是向上/淡入动画,iOS 是侧滑动画)。

代码示例 (页面 A 跳转到 页面 B):

import 'package:flutter/material.dart';

// 页面 A (首页)
class FirstPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("第一页")),
      body: Center(
        child: ElevatedButton(
          child: Text("去第二页 ->"),
          onPressed: () {
            // 执行跳转
            Navigator.of(context).push(
              MaterialPageRoute(builder: (context) => SecondPage()),
            );
          },
        ),
      ),
    );
  }
}

// 页面 B (详情页)
class SecondPage extends StatelessWidget {
  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("第二页")),
      body: Center(
        child: ElevatedButton(
          child: Text("<- 返回"),
          onPressed: () {
            // 执行返回
            Navigator.of(context).pop();
          },
        ),
      ),
    );
  }
}

2. 页面间传值 (Passing Data)

场景: 点击一个列表项,进入详情页,并把该项的 ID 或标题传过去。

方法: 通过目标页面的构造函数传递参数。

代码示例:

// 1. 定义数据模型
class Product {
  final String name;
  final double price;
  Product(this.name, this.price);
}

// 2. 列表页
class ProductListScreen extends StatelessWidget {
  final List<Product> products = [
    Product("iPhone 15", 5999),
    Product("MacBook Pro", 12999),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("商品列表")),
      body: ListView.builder(
        itemCount: products.length,
        itemBuilder: (context, index) {
          return ListTile(
            title: Text(products[index].name),
            onTap: () {
              // 跳转并传值
              Navigator.push(
                context,
                MaterialPageRoute(
                  builder: (context) => ProductDetailScreen(product: products[index]),
                ),
              );
            },
          );
        },
      ),
    );
  }
}

// 3. 详情页 (接收参数)
class ProductDetailScreen extends StatelessWidget {
  // 定义接收的字段
  final Product product;

  // 构造函数要求必传 product
  const ProductDetailScreen({Key? key, required this.product}) : super(key: key);

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text(product.name)),
      body: Center(
        child: Text("价格: ¥${product.price}", style: TextStyle(fontSize: 24)),
      ),
    );
  }
}

进阶:从目标页面返回数据
有时我们需要从详情页带回数据(例如:在“选择地址页”选好后返回“订单确认页”)。

// 在页面 A 中
final result = await Navigator.push(context, MaterialPageRoute(...));
print("页面 B 返回了: $result");

// 在页面 B 中
Navigator.pop(context, "我是返回的数据");

3. 底部导航栏 (BottomNavigationBar)

说明:
这是 App 最常见的一级导航结构(如微信底部的:微信、通讯录、发现、我)。
实现原理是:使用 ScaffoldbottomNavigationBar 属性,配合 IndexedStack 或简单的 List<Widget> 来切换 body 的内容。

核心属性:

  • currentIndex: 当前激活项的索引 (int)。
  • onTap: 点击回调,用于更新索引。
  • items: 导航项列表 (BottomNavigationBarItem)。
  • type: BottomNavigationBarType.fixed (图标均分) 或 shifting (点击变大)。

代码示例 (标准 App 骨架):

import 'package:flutter/material.dart';

class MainAppBase extends StatefulWidget {
  
  _MainAppBaseState createState() => _MainAppBaseState();
}

class _MainAppBaseState extends State<MainAppBase> {
  int _currentIndex = 0;

  // 页面列表
  final List<Widget> _pages = [
    Center(child: Text("首页内容", style: TextStyle(fontSize: 30))),
    Center(child: Text("消息中心", style: TextStyle(fontSize: 30))),
    Center(child: Text("个人中心", style: TextStyle(fontSize: 30))),
  ];

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: Text("My App")),
      
      // 动态切换 Body
      body: _pages[_currentIndex],
      
      bottomNavigationBar: BottomNavigationBar(
        currentIndex: _currentIndex,
        type: BottomNavigationBarType.fixed, // 超过3个item建议设为fixed
        selectedItemColor: Colors.blue,
        unselectedItemColor: Colors.grey,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
        items: [
          BottomNavigationBarItem(icon: Icon(Icons.home), label: "首页"),
          BottomNavigationBarItem(icon: Icon(Icons.message), label: "消息"),
          BottomNavigationBarItem(icon: Icon(Icons.person), label: "我的"),
        ],
      ),
    );
  }
}

4. 顶部选项卡 (TabBar & TabBarView)

说明:
用于在同一页面内切换不同的视图(如 WhatsApp 顶部的 Calls, Chats, Contacts)。通常配合 AppBarbottom 属性使用。

实现方式:
最简单的方法是使用 DefaultTabController 包裹整个 Scaffold,无需手动管理 Controller。

代码示例:

import 'package:flutter/material.dart';

class TabBarExample extends StatelessWidget {
  
  Widget build(BuildContext context) {
    // length: 3 表示有三个标签
    return DefaultTabController(
      length: 3,
      child: Scaffold(
        appBar: AppBar(
          title: Text("Top Tabs"),
          // TabBar 放在 AppBar 的 bottom 位置
          bottom: TabBar(
            indicatorColor: Colors.white, // 指示器颜色
            tabs: [
              Tab(icon: Icon(Icons.directions_car), text: "汽车"),
              Tab(icon: Icon(Icons.directions_transit), text: "地铁"),
              Tab(icon: Icon(Icons.directions_bike), text: "骑行"),
            ],
          ),
        ),
        // TabBarView 的 children 数量必须与 tabs 一致
        body: TabBarView(
          children: [
            Center(child: Text("汽车页面")),
            Center(child: Text("地铁页面")),
            Center(child: Text("骑行页面")),
          ],
        ),
      ),
    );
  }
}

5. 侧边抽屉导航 (Drawer)

说明:
点击左上角汉堡菜单滑出的侧边栏。

注意:
在 Drawer 中点击跳转时,通常需要先关闭 Drawer (Navigator.pop(context)),否则返回时 Drawer 依然是打开状态。

代码示例:

Drawer(
  child: ListView(
    padding: EdgeInsets.zero, // 移除顶部 padding,让 Header 顶满屏幕
    children: [
      UserAccountsDrawerHeader(
        accountName: Text("Flutter User"),
        accountEmail: Text("user@example.com"),
        currentAccountPicture: CircleAvatar(
          backgroundColor: Colors.white,
          child: Text("F", style: TextStyle(fontSize: 40.0)),
        ),
        decoration: BoxDecoration(color: Colors.blue),
      ),
      ListTile(
        leading: Icon(Icons.settings),
        title: Text("设置"),
        onTap: () {
          // 1. 关闭侧边栏
          Navigator.pop(context); 
          // 2. 跳转到设置页 (假设有 SettingsPage)
          // Navigator.push(context, MaterialPageRoute(builder: (c) => SettingsPage()));
        },
      ),
    ],
  ),
)

附录:路由管理的常见问题

  1. Named Routes (命名路由)

    • 在大型项目中,可能会看到 Navigator.pushNamed(context, '/detail') 这种写法。
    • 这需要在 MaterialApproutes 属性中预先定义好映射关系。
    • 建议: 初学者先熟练使用 MaterialPageRoute (匿名路由),因为它传参更直观,且有类型检查。
  2. 黑屏问题

    • 如果跳转后页面一片漆黑,通常是因为新页面没有使用 Scaffold 作为根节点。记住:每个全屏页面最好都以 Scaffold 开始。
  3. Context 问题

    • Navigator.of(context) 需要的 context 必须是含在 MaterialApp 之下的。如果在 main() 函数里直接用,会报错。

第四部分总结:
恭喜你!到目前为止,你已经掌握了:

  1. UI 构建:Text, Image, Layouts, Styling.
  2. 数据展示:ListView, GridView.
  3. 用户交互:Inputs, Forms, Dialogs.
  4. 页面流转:Navigator, BottomNavigationBar, Tabs.

这四部分内容已经构成了一个完整的静态 App 开发体系。你可以做出来的 App 看起来已经和真实上线的产品没什么两样了。

最后一步:赋予灵魂。
现在的 App 还是“死”的,数据是写死的,逻辑是同步的。
第五部分 将是本系列的终章,涵盖 功能性组件与异步编程
我们将学习:

  • Theme (主题):一键切换夜间模式/主题色。
  • GestureDetector (手势):让任意组件都可点击、拖拽。
  • FutureBuilder / StreamBuilder最重要的一环,如何处理网络请求的加载中、加载失败、加载完成状态。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值