在这里插入图片描述

上一篇文章我们搭建好了项目的基础架构,今天来实现应用的底部导航栏。这个导航栏是整个应用的骨架,用户通过它在不同功能模块之间切换。我选择了convex_bottom_bar这个库,它能做出中间凸起的效果,视觉上比较有特色。

为什么选择convex_bottom_bar

Flutter自带的BottomNavigationBar虽然也能用,但样式比较普通。convex_bottom_bar提供了多种样式,特别是那个中间凸起的圆形按钮,看起来更有设计感。而且这个库在鸿蒙系统上也能正常工作,不用担心兼容性问题。

先在pubspec.yaml里添加依赖:

dependencies:
  convex_bottom_bar: ^3.0.0

主页面的实现

主页面MainPage是整个应用的容器,它管理着四个Tab页面的切换。来看看具体代码:

import 'package:flutter/material.dart';
import 'package:convex_bottom_bar/convex_bottom_bar.dart';
import 'home/home_page.dart';
import 'finance/finance_page.dart';
import 'life/life_page.dart';
import 'profile/profile_page.dart';

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

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

class _MainPageState extends State<MainPage> {
  int _currentIndex = 0;

  final List<Widget> _pages = [
    const HomePage(),
    const FinancePage(),
    const LifePage(),
    const ProfilePage(),
  ];

  
  void initState() {
    super.initState();
    debugPrint('📄 MainPage initialized');
  }

  
  Widget build(BuildContext context) {
    debugPrint('🎨 MainPage building with index: $_currentIndex');
    
    return Scaffold(
      body: _pages[_currentIndex],
      bottomNavigationBar: ConvexAppBar(
        style: TabStyle.reactCircle,
        backgroundColor: Colors.blue,
        items: const [
          TabItem(icon: Icons.home, title: '首页'),
          TabItem(icon: Icons.account_balance_wallet, title: '记账'),
          TabItem(icon: Icons.calendar_today, title: '生活'),
          TabItem(icon: Icons.person, title: '我的'),
        ],
        initialActiveIndex: _currentIndex,
        onTap: (index) {
          setState(() {
            _currentIndex = index;
          });
        },
      ),
    );
  }
}

这段代码的核心逻辑很简单:

状态管理:用_currentIndex记录当前选中的Tab索引,初始值是0,也就是首页。

页面列表_pages数组存放四个页面的实例,通过索引来显示对应的页面。

导航栏配置ConvexAppBarstyle设置为TabStyle.reactCircle,这样点击时会有圆形的反应效果。backgroundColor设置成蓝色,和应用的主题色保持一致。

点击事件onTap回调里调用setState更新索引,触发页面重建,显示新的页面。

关于页面切换的性能优化

你可能注意到,每次切换Tab都会重建页面。如果页面比较复杂,这样会有性能问题。有个简单的优化方法,就是用IndexedStack替代直接显示页面:

body: IndexedStack(
  index: _currentIndex,
  children: _pages,
)

IndexedStack会把所有页面都保持在内存里,只是显示当前索引对应的那个。这样切换时不需要重建页面,滚动位置、输入内容这些状态都能保留。

不过这样做也有代价,就是内存占用会增加。如果页面不是特别复杂,用原来的方式就够了。我在实际使用中发现,对于这个应用来说,直接切换页面的性能已经很流畅了。

导航栏的样式定制

convex_bottom_bar提供了好几种样式,我试过几个:

TabStyle.reactCircle:点击时有圆形反应效果,这是我最终选择的样式。

TabStyle.fixedCircle:中间按钮固定凸起,不会跟随点击移动。

TabStyle.textIn:文字显示在图标内部。

TabStyle.titled:文字显示在图标下方,比较传统的样式。

如果你想自定义颜色,可以这样配置:

ConvexAppBar(
  backgroundColor: Colors.blue,        // 背景色
  activeColor: Colors.white,           // 选中时的颜色
  color: Colors.white70,               // 未选中时的颜色
  height: 50,                          // 导航栏高度
  // ...
)

四个Tab页面的基础框架

现在四个页面还只是占位符,先创建最基本的结构。以首页为例:

import 'package:flutter/material.dart';

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

  
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('生活助手'),
      ),
      body: const Center(
        child: Text('首页 - 健康管理'),
      ),
    );
  }
}

其他三个页面也是类似的结构,只是标题和内容不同。这样先把框架搭起来,后面再逐个实现具体功能。

调试技巧

开发过程中,我在initStatebuild方法里加了一些调试日志:


void initState() {
  super.initState();
  debugPrint('📄 MainPage initialized');
}


Widget build(BuildContext context) {
  debugPrint('🎨 MainPage building with index: $_currentIndex');
  // ...
}

这些日志能帮我了解页面的生命周期,比如什么时候初始化,什么时候重建。用emoji做标记,在一堆日志里很容易找到自己关心的信息。

遇到的一个小问题

一开始我发现切换Tab时,键盘如果是打开状态,会把导航栏顶上去。解决方法是在Scaffold里加一行配置:

Scaffold(
  resizeToAvoidBottomInset: false,  // 不调整大小
  // ...
)

这样键盘弹出时,导航栏的位置就不会变了。

关于页面状态保持

如果你希望切换Tab后,页面的状态能保持住(比如滚动位置、输入的内容),可以让页面继承AutomaticKeepAliveClientMixin

class HomePage extends StatefulWidget {
  
  _HomePageState createState() => _HomePageState();
}

class _HomePageState extends State<HomePage> 
    with AutomaticKeepAliveClientMixin {
  
  
  bool get wantKeepAlive => true;
  
  
  Widget build(BuildContext context) {
    super.build(context);  // 这行很重要,不能忘
    return Scaffold(/* ... */);
  }
}

wantKeepAlive返回true表示要保持状态,build方法里必须调用super.build(context),这是mixin的要求。

实际使用体验

导航栏实现后,我在真机上测试了一下,切换很流畅,没有卡顿。中间凸起的按钮点击起来手感也不错,视觉上比平铺的导航栏更有层次感。

唯一需要注意的是,convex_bottom_bar的高度是固定的,如果你的设计稿要求特定高度,可能需要调整一下。我用的是默认高度,在不同屏幕上看起来都还可以。

下一步计划

底部导航栏搭好了,接下来要实现首页的健康仪表盘功能。那里会用到图表展示,涉及到fl_chart库的使用,还有一些数据可视化的技巧,我会在下一篇详细介绍。

小结

今天实现了应用的底部导航栏,用convex_bottom_bar做出了凸起式的效果。核心是通过_currentIndex状态来控制页面切换,配合setState触发UI更新。还分享了一些性能优化和状态保持的技巧。

这个导航栏是整个应用的基础框架,后面所有功能都会在这四个Tab里展开。有了这个框架,开发起来就有条理多了。


欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐