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

如何在Flutter中实现该自定义形状的AppBar?

实现底部弧形AppBar的几种替代方案

你遇到的AppBar和body分隔的问题,主要是因为默认AppBar的阴影(elevation)以及形状裁剪后和body的衔接问题,除了用Stack模拟的方式,还有以下几种更简洁的实现思路:

1. 自定义带弧形的PreferredSize AppBar

直接把AppBar整体裁剪成带弧形的形状,同时去掉阴影,让body顶部和弧形自然衔接:

自定义AppBar代码

class CurvedAppBar extends StatelessWidget implements PreferredSizeWidget {
  final String title;

  const CurvedAppBar({super.key, required this.title});

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: _CurvedAppBarClipper(),
      child: AppBar(
        title: Text(title),
        backgroundColor: Colors.blue,
        elevation: 0, // 移除阴影,避免和body产生分隔感
      ),
    );
  }

  @override
  Size get preferredSize => const Size.fromHeight(kToolbarHeight);
}

// 自定义裁剪器,绘制底部弧形
class _CurvedAppBarClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0, size.height);
    // 二次贝塞尔曲线绘制弧形,可调整controlPoint的y值改变弧度大小
    final controlPoint = Offset(size.width / 2, size.height + 20);
    final endPoint = Offset(size.width, size.height);
    path.quadraticBezierTo(controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy);
    path.lineTo(size.width, 0);
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

衔接body的处理

为了让body顶部和AppBar的弧形完全贴合,需要在body最顶部添加一个匹配的弧形部件:

Container(
  color: Colors.white,
  child: ClipPath(
    clipper: _CurvedBodyClipper(),
    child: Container(
      height: 20, // 和AppBar弧形的高度对应
      color: Colors.blue,
    ),
  ),
)

// body顶部的弧形裁剪器
class _CurvedBodyClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0, 0);
    final controlPoint = Offset(size.width / 2, -20);
    final endPoint = Offset(size.width, 0);
    path.quadraticBezierTo(controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy);
    path.lineTo(size.width, size.height);
    path.lineTo(0, size.height);
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

2. 利用extendBodyBehindAppBar实现背景延伸

让AppBar透明,然后在body的顶部用自定义绘制实现弧形背景,同时保留AppBar的导航元素:

Scaffold(
  extendBodyBehindAppBar: true, // 让body延伸到AppBar下方
  appBar: AppBar(
    title: const Text("弧形AppBar"),
    backgroundColor: Colors.transparent,
    elevation: 0,
  ),
  body: Stack(
    children: [
      // 顶部弧形背景,高度包含AppBar和弧形区域
      CustomPaint(
        painter: _CurvedBackgroundPainter(),
        child: SizedBox(height: kToolbarHeight + 20),
      ),
      // 主体内容,设置padding避开顶部弧形区域
      Padding(
        padding: EdgeInsets.only(top: kToolbarHeight + 20),
        child: Container(
          color: Colors.white,
          child: const Center(child: Text("页面内容")),
        ),
      ),
    ],
  ),
)

// 绘制弧形背景的Painter
class _CurvedBackgroundPainter extends CustomPainter {
  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()..color = Colors.blue;
    final path = Path();
    path.lineTo(0, size.height);
    final controlPoint = Offset(size.width / 2, size.height + 20);
    final endPoint = Offset(size.width, size.height);
    path.quadraticBezierTo(controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy);
    path.lineTo(size.width, 0);
    path.close();
    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) => false;
}

这种方式的优势是不需要修改AppBar的结构,只需要处理背景,适合需要背景色渐变或者复杂效果的场景。

3. 给AppBar添加弧形底部组件

利用AppBar的bottom属性,添加一个自定义的弧形部件,让它和AppBar主体颜色一致,看起来像是整体的一部分:

AppBar(
  title: const Text("弧形AppBar"),
  backgroundColor: Colors.blue,
  elevation: 0,
  bottom: PreferredSize(
    preferredSize: const Size.fromHeight(20), // 弧形区域的高度
    child: ClipPath(
      clipper: _CurvedBottomClipper(),
      child: Container(
        height: 20,
        color: Colors.blue, // 和AppBar颜色保持一致
      ),
    ),
  ),
)

// 底部弧形裁剪器
class _CurvedBottomClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    path.lineTo(0, size.height);
    final controlPoint = Offset(size.width / 2, -10);
    final endPoint = Offset(size.width, size.height);
    path.quadraticBezierTo(controlPoint.dx, controlPoint.dy, endPoint.dx, endPoint.dy);
    path.lineTo(size.width, 0);
    return path;
  }

  @override
  bool shouldReclip(covariant CustomClipper<Path> oldClipper) => false;
}

这种方法实现最简单,不需要修改body的结构,只需要给AppBar添加bottom组件即可,适合快速实现需求。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.01 08:13:28