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

如何在Flutter中用CustomPainter实现指定样式的静态BottomNavigationBar

实现静态带曲线的BottomNavigationBar(无动画)

1. 实现思路

不用依赖第三方插件,直接通过Stack+CustomPainter就能搞定:

  • 用CustomPainter绘制固定的曲线背景(静态无变化)
  • 上层用Row布局导航按钮,中间按钮对齐曲线凸起位置
  • 仅处理按钮选中状态的样式变化,不做动画和页面切换

2. 绘制静态曲线背景

先写一个自定义Painter类,专门绘制底部的曲线背景:

class CurvedBottomBarPainter extends CustomPainter {
  final Color backgroundColor;
  final double curveHeight;

  CurvedBottomBarPainter({
    required this.backgroundColor,
    required this.curveHeight,
  });

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

    // 从左下角开始绘制路径
    path.moveTo(0, 0);
    // 左侧直线到曲线起点
    path.lineTo(size.width * 0.4, 0);
    // 左半段二次贝塞尔曲线(控制凸起弧度)
    path.quadraticBezierTo(
      size.width * 0.45,
      -curveHeight,
      size.width * 0.5,
      -curveHeight,
    );
    // 右半段二次贝塞尔曲线
    path.quadraticBezierTo(
      size.width * 0.55,
      -curveHeight,
      size.width * 0.6,
      0,
    );
    // 右侧直线到右下角
    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(CustomPainter oldDelegate) => false; // 静态样式,无需重绘
}

3. 组装完整的导航栏组件

写一个可复用的导航栏组件,组合曲线背景和按钮:

class StaticCurvedBottomBar extends StatefulWidget {
  final List<BottomNavItem> items;
  final Color backgroundColor;
  final double curveHeight;
  final Color selectedItemColor;
  final Color unselectedItemColor;

  const StaticCurvedBottomBar({
    super.key,
    required this.items,
    this.backgroundColor = Colors.white,
    this.curveHeight = 20,
    this.selectedItemColor = Colors.blue,
    this.unselectedItemColor = Colors.grey,
  });

  @override
  State<StaticCurvedBottomBar> createState() => _StaticCurvedBottomBarState();
}

class _StaticCurvedBottomBarState extends State<StaticCurvedBottomBar> {
  int _selectedIndex = 0;

  void _onItemTapped(int index) {
    setState(() {
      _selectedIndex = index;
    });
    // 不需要页面切换逻辑,按需求留空
  }

  @override
  Widget build(BuildContext context) {
    // 导航栏总高度=系统默认高度+曲线凸起高度
    final barHeight = kBottomNavigationBarHeight + widget.curveHeight;

    return SizedBox(
      height: barHeight,
      child: Stack(
        alignment: Alignment.bottomCenter,
        children: [
          // 底层曲线背景
          CustomPaint(
            size: Size(MediaQuery.of(context).size.width, barHeight),
            painter: CurvedBottomBarPainter(
              backgroundColor: widget.backgroundColor,
              curveHeight: widget.curveHeight,
            ),
          ),
          // 上层导航按钮组
          Padding(
            padding: EdgeInsets.only(bottom: widget.curveHeight / 2),
            child: Row(
              mainAxisAlignment: MainAxisAlignment.spaceAround,
              children: List.generate(widget.items.length, (index) {
                final isSelected = index == _selectedIndex;
                return GestureDetector(
                  onTap: () => _onItemTapped(index),
                  child: Column(
                    mainAxisSize: MainAxisSize.min,
                    children: [
                      // 图标支持资源图片,直接替换成Image.asset即可
                      isSelected 
                          ? widget.items[index].selectedIcon 
                          : widget.items[index].icon,
                      const SizedBox(height: 4),
                      Text(
                        widget.items[index].label,
                        style: TextStyle(
                          color: isSelected ? widget.selectedItemColor : widget.unselectedItemColor,
                          fontSize: 12,
                        ),
                      ),
                    ],
                  ),
                );
              }),
            ),
          ),
        ],
      ),
    );
  }
}

// 导航项数据类,支持选中/未选中图标
class BottomNavItem {
  final Widget icon;
  final Widget selectedIcon;
  final String label;

  BottomNavItem({
    required this.icon,
    required this.selectedIcon,
    required this.label,
  });
}

4. 使用示例

在页面中直接调用这个组件:

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      body: const Center(child: Text('首页内容')),
      bottomNavigationBar: StaticCurvedBottomBar(
        backgroundColor: Colors.white,
        curveHeight: 25,
        selectedItemColor: Colors.deepPurple,
        unselectedItemColor: Colors.grey.shade400,
        items: [
          BottomNavItem(
            icon: Image.asset('assets/home_unselected.png', width:24, height:24),
            selectedIcon: Image.asset('assets/home_selected.png', width:24, height:24),
            label: '首页',
          ),
          BottomNavItem(
            icon: Image.asset('assets/search_unselected.png', width:24, height:24),
            selectedIcon: Image.asset('assets/search_selected.png', width:24, height:24),
            label: '搜索',
          ),
          BottomNavItem(
            icon: Image.asset('assets/post_unselected.png', width:32, height:32),
            selectedIcon: Image.asset('assets/post_selected.png', width:32, height:32),
            label: '发布',
          ),
          BottomNavItem(
            icon: Image.asset('assets/message_unselected.png', width:24, height:24),
            selectedIcon: Image.asset('assets/message_selected.png', width:24, height:24),
            label: '消息',
          ),
          BottomNavItem(
            icon: Image.asset('assets/profile_unselected.png', width:24, height:24),
            selectedIcon: Image.asset('assets/profile_selected.png', width:24, height:24),
            label: '我的',
          ),
        ],
      ),
    );
  }
}

调整说明

  • 曲线弧度:修改quadraticBezierTo的控制点坐标,可以调整曲线的凸起高度和弧度,完全匹配你的设计图
  • 资源图片:直接把示例中的Image.asset换成你的本地资源路径即可
  • 静态无动画:因为CurvedBottomBarPainter的shouldRepaint返回false,曲线永远不会重绘,点击按钮仅切换图标和文字颜色

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.16 10:33:18