如何不使用第三方包在Flutter中实现指定样式的自定义底部导航栏
实现自定义凸起样式底部导航栏(无第三方依赖)
本方案实现的是底部导航栏中间带凸起悬浮按钮的效果,全程使用Flutter官方组件,无需引入任何第三方依赖。
实现步骤
- 整体使用
Scaffold组件的bottomNavigationBar属性承载底部导航主体,floatingActionButton属性承载中间凸起的按钮,搭配floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked实现按钮嵌入底部导航的凹槽效果。 - 自定义底部导航栏主体,使用
BottomAppBar组件作为容器,设置shape: CircularNotchedRectangle()实现适配中间按钮的凹槽剪裁效果。 - 在
BottomAppBar内部使用Row布局排列两侧的导航项,左右各留空一个位置给中间的悬浮按钮占位,导航项包含图标和文字,根据选中状态切换样式。 - 定义
int _currentIndex变量存储当前选中的导航索引,点击导航项或者中间按钮时更新索引,配合IndexedStack实现对应页面的切换,保证页面状态不会丢失。 - 处理选中态逻辑,导航项点击时触发
setState更新_currentIndex,对应项的图标和文字设置高亮颜色,未选中项置灰。
核心代码逻辑
1. 主页面框架代码
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: '自定义底部导航', theme: ThemeData(primarySwatch: Colors.blue), home: const HomePage(), ); } } class HomePage extends StatefulWidget { const HomePage({super.key}); @override State<HomePage> createState() => _HomePageState(); } class _HomePageState extends State<HomePage> { int _currentIndex = 0; // 对应导航的页面列表,可替换为自己的业务页面 final List<Widget> _pages = const [ HomeScreen(), CategoryScreen(), PublishScreen(), // 中间凸起按钮对应页面 MessageScreen(), MineScreen(), ]; @override Widget build(BuildContext context) { return Scaffold( body: IndexedStack( index: _currentIndex, children: _pages, ), // 中间凸起按钮 floatingActionButton: FloatingActionButton( onPressed: () => setState(() => _currentIndex = 2), backgroundColor: _currentIndex == 2 ? Colors.blue : Colors.grey, child: const Icon(Icons.add, color: Colors.white), ), // 悬浮按钮位置:居中嵌入底部导航 floatingActionButtonLocation: FloatingActionButtonLocation.centerDocked, // 底部导航主体 bottomNavigationBar: BottomAppBar( shape: const CircularNotchedRectangle(), // 适配凸起按钮的凹槽形状 notchMargin: 6, // 按钮和凹槽的留白间距 child: SizedBox( height: 60, child: Row( mainAxisAlignment: MainAxisAlignment.spaceAround, children: [ // 左侧第一个导航项 _buildNavItem(0, Icons.home, "首页"), _buildNavItem(1, Icons.category, "分类"), // 中间占位,给凸起按钮留空间 const SizedBox(width: 40), // 右侧导航项 _buildNavItem(3, Icons.message, "消息"), _buildNavItem(4, Icons.person, "我的"), ], ), ), ), ); } // 封装单个导航项组件 Widget _buildNavItem(int index, IconData icon, String label) { return GestureDetector( onTap: () => setState(() => _currentIndex = index), child: Column( mainAxisSize: MainAxisSize.min, children: [ Icon( icon, color: _currentIndex == index ? Colors.blue : Colors.grey, ), Text( label, style: TextStyle( color: _currentIndex == index ? Colors.blue : Colors.grey, fontSize: 12, ), ) ], ), ); } }
2. 占位页面示例代码(可根据业务自行替换)
class HomeScreen extends StatelessWidget {const HomeScreen({super.key});@override Widget build(BuildContext context) => const Center(child: Text("首页"));} class CategoryScreen extends StatelessWidget {const CategoryScreen({super.key});@override Widget build(BuildContext context) => const Center(child: Text("分类"));} class PublishScreen extends StatelessWidget {const PublishScreen({super.key});@override Widget build(BuildContext context) => const Center(child: Text("发布页"));} class MessageScreen extends StatelessWidget {const MessageScreen({super.key});@override Widget build(BuildContext context) => const Center(child: Text("消息"));} class MineScreen extends StatelessWidget {const MineScreen({super.key});@override Widget build(BuildContext context) => const Center(child: Text("我的"));}
自定义调整说明
- 如果需要修改凹槽的大小或者形状,可以自定义
ShapeBorder替换CircularNotchedRectangle即可 - 可以修改
FloatingActionButton的样式实现不同的凸起按钮效果,比如方形、带边框、自定义图标等 - 导航项的选中态样式、图标、文字都可以根据设计稿自行调整参数
内容的提问来源于stack exchange,提问作者Ahmed Sabahy
相关产品推荐
相关产品推荐

