You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Flutter自定义形状底部导航栏实现技术求助

Flutter 自定义凸起式底部导航栏实现方案

根据你提供的设计需求,这里给出一套可直接复用的实现方案,核心是通过自定义绘制背景+堆叠导航项来实现中间凸起的效果。

核心实现思路

  • 用CustomPainter绘制带中间圆弧凹陷的导航栏背景
  • 用Stack组件将自定义背景、普通导航项和中间凸起按钮分层叠加
  • 利用Scaffold的extendBody属性让页面内容延伸至导航栏下方,优化视觉体验

完整代码示例

1. 自定义背景绘制类

import 'package:flutter/material.dart';

class CustomNavBarPainter extends CustomPainter {
  final Color backgroundColor;
  final double notchRadius;
  final double notchHeight;

  CustomNavBarPainter({
    required this.backgroundColor,
    this.notchRadius = 30,
    this.notchHeight = 20,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = backgroundColor;
    final path = Path();

    // 左侧直线
    path.lineTo(size.width / 2 - notchRadius, 0);
    // 左侧圆弧(形成凹陷)
    path.arcTo(
      Rect.fromCircle(
        center: Offset(size.width / 2, notchHeight),
        radius: notchRadius,
      ),
      pi,
      -pi,
      false,
    );
    // 右侧直线
    path.lineTo(size.width, 0);
    // 底部闭合路径
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    path.close();

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomNavBarPainter oldDelegate) {
    return oldDelegate.backgroundColor != backgroundColor ||
        oldDelegate.notchRadius != notchRadius ||
        oldDelegate.notchHeight != notchHeight;
  }
}

2. 页面与导航栏整合

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

  @override
  State<CustomBottomNavBarPage> createState() => _CustomBottomNavBarPageState();
}

class _CustomBottomNavBarPageState extends State<CustomBottomNavBarPage> {
  int _selectedIndex = 0;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
  }

  @override
  Widget build(BuildContext context) {
    // 导航栏总高度,预留凸起空间
    final navBarHeight = kBottomNavigationBarHeight + 10;
    return Scaffold(
      // 让页面内容延伸至导航栏下方,避免凹陷处留白
      extendBody: true,
      body: Center(
        child: Text('当前选中页面索引: $_selectedIndex'),
      ),
      bottomNavigationBar: SizedBox(
        height: navBarHeight,
        child: Stack(
          alignment: Alignment.topCenter,
          children: [
            // 绘制自定义背景
            CustomPaint(
              size: Size(MediaQuery.of(context).size.width, navBarHeight),
              painter: CustomNavBarPainter(
                backgroundColor: Colors.white,
                notchRadius: 35,
                notchHeight: 25,
              ),
            ),
            // 普通导航项(中间位置留空给凸起按钮)
            Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: [
                _buildNavItem(Icons.home, 0),
                _buildNavItem(Icons.search, 1),
                const SizedBox(width: 70), // 中间占位,匹配凸起按钮宽度
                _buildNavItem(Icons.favorite, 2),
                _buildNavItem(Icons.person, 3),
              ],
            ),
            // 中间凸起的导航按钮
            Positioned(
              top: -20, // 向上偏移实现悬浮凸起效果
              child: InkWell(
                onTap: () => _onItemTapped(4),
                borderRadius: BorderRadius.circular(35),
                child: Container(
                  width: 70,
                  height: 70,
                  decoration: const BoxDecoration(
                    color: Colors.blue,
                    shape: BoxShape.circle,
                    boxShadow: [
                      BoxShadow(
                        color: Colors.black12,
                        blurRadius: 8,
                        spreadRadius: 2,
                      ),
                    ],
                  ),
                  child: const Icon(
                    Icons.add,
                    color: Colors.white,
                    size: 32,
                  ),
                ),
              ),
            ),
          ],
        ),
      ),
    );
  }

  // 封装普通导航项
  Widget _buildNavItem(IconData icon, int index) {
    final isSelected = _selectedIndex == index;
    return InkWell(
      onTap: () => _onItemTapped(index),
      child: Padding(
        padding: const EdgeInsets.symmetric(vertical: 12),
        child: Icon(
          icon,
          color: isSelected ? Colors.blue : Colors.grey,
          size: 28,
        ),
      ),
    );
  }
}

可调参数说明

  • 修改CustomNavBarPainter的notchRadius和notchHeight可以调整中间凹陷的大小和深度
  • 调整凸起按钮的top偏移量、宽高和颜色,适配你的设计风格
  • 普通导航项的图标、选中态颜色都可以自定义修改

内容的提问来源于stack exchange,提问作者Hudaif

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.19 03:50:08