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

如何在Flutter中创建底部中心带平滑凹口的容器?

Flutter 实现带底部中心平滑凹口的圆角容器形状

设计效果展示

无内容的设计效果:
无内容的设计效果

有内容的设计效果:
有内容的设计效果

需求说明

  • 容器四个角均为圆角;
  • 底部中心有一个向内的平滑凹口;
  • 支持添加阴影或 elevation 效果。

问题描述

使用ClipPath搭配自定义CustomClipper<Path>尝试实现,但无法复现精确形状,尤其是凹口部分难以做到平滑自然。以下是原实现代码:

原使用代码

PhysicalShape(
  clipper: NotchedClipper(),
  elevation: 8,
  color: Colors.white,
  shadowColor: Colors.black26,
  child: Container(
    width: 200,
    height: 400,
    child: Center(child: Text("Shadow Shape")),
  ),
);

原NotchedClipper代码

class NotchedClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final path = Path();
    final notchWidth = 90.0;
    final notchDepth = 70.0;
    final radius = 50.0;

    path.moveTo(0, radius);
    path.quadraticBezierTo(0, 0, radius, 0);
    path.lineTo(size.width - radius, 0);
    path.quadraticBezierTo(size.width, 0, size.width, radius);
    path.lineTo(size.width, size.height - radius);
    path.quadraticBezierTo(size.width, size.height, size.width - radius, size.height);

    // Right side of notch
    path.lineTo(size.width / 2 + notchWidth / 2, size.height);
    path.quadraticBezierTo(
        size.width / 2, size.height + notchDepth, size.width / 2 - notchWidth / 2, size.height);

    // Left side of notch
    path.lineTo(radius, size.height);
    path.quadraticBezierTo(0, size.height, 0, size.height - radius);

    path.close();
    return path;
  }

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

解决方案

原代码核心问题是凹口曲线控制点方向错误(向外凸而非向内凹),且路径衔接存在冗余线条。以下是修正后的实现:

修正后的NotchedClipper

class NotchedClipper extends CustomClipper<Path> {
  final double cornerRadius;
  final double notchWidth;
  final double notchDepth;

  NotchedClipper({
    this.cornerRadius = 50.0,
    this.notchWidth = 90.0,
    this.notchDepth = 30.0, // 根据设计图调整深度,避免过度凹陷
  });

  @override
  Path getClip(Size size) {
    final path = Path();
    final halfWidth = size.width / 2;
    final notchHalfWidth = notchWidth / 2;

    // 左上角圆角
    path.moveTo(cornerRadius, 0);
    path.quadraticBezierTo(0, 0, 0, cornerRadius);
    // 左侧边
    path.lineTo(0, size.height - cornerRadius);
    // 左下角圆角
    path.quadraticBezierTo(0, size.height, cornerRadius, size.height);

    // 从左下角到凹口左侧起点
    path.lineTo(halfWidth - notchHalfWidth, size.height);
    // 绘制向内平滑凹口(二次贝塞尔曲线)
    path.quadraticBezierTo(
      halfWidth,
      size.height - notchDepth,
      halfWidth + notchHalfWidth,
      size.height,
    );

    // 从凹口右侧到右下角
    path.lineTo(size.width - cornerRadius, size.height);
    // 右下角圆角
    path.quadraticBezierTo(size.width, size.height, size.width, size.height - cornerRadius);
    // 右侧边
    path.lineTo(size.width, cornerRadius);
    // 右上角圆角
    path.quadraticBezierTo(size.width, 0, size.width - cornerRadius, 0);
    // 顶部边回到起始点
    path.lineTo(cornerRadius, 0);

    path.close();
    return path;
  }

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

使用方式

保持原PhysicalShape用法,可根据需求调整参数:

PhysicalShape(
  clipper: NotchedClipper(
    cornerRadius: 50.0,
    notchWidth: 90.0,
    notchDepth: 30.0,
  ),
  elevation: 8,
  color: Colors.white,
  shadowColor: Colors.black26,
  child: Container(
    width: 200,
    height: 400,
    padding: const EdgeInsets.all(16.0),
    child: const Center(child: Text("Shadow Shape")),
  ),
);

优化说明

  1. 调整凹口曲线控制点位置,将size.height + notchDepth改为size.height - notchDepth,实现向内凹陷效果;
  2. 重新梳理路径绘制顺序,确保线段与曲线平滑衔接,消除突兀转角;
  3. 将参数封装为可配置构造函数,方便根据设计需求调整圆角大小、凹口宽度和深度;
  4. 保留PhysicalShape组件,确保阴影(elevation)效果正常生效。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 02:22:03