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

如何在Flutter中创建指定自定义形状?寻求技术指导

使用CustomPainter创建目标自定义形状

一、CustomPainter实现方案

从你提供的图片来看,这个形状的核心特征是顶部左右为圆角,底部左侧带斜切角、右侧为直角,下面是具体的实现代码:

1. 自定义Painter类

class CustomShapePainter extends CustomPainter {
  final Color color;
  final double borderRadius;
  final double bottomLeftSlantHeight;

  CustomShapePainter({
    required this.color,
    this.borderRadius = 16.0,
    this.bottomLeftSlantHeight = 24.0,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = color
      ..style = PaintingStyle.fill;

    final path = Path();
    // 起点:顶部左侧圆角的起始点
    path.moveTo(borderRadius, 0);
    // 绘制顶部右侧圆角
    path.quadraticBezierTo(size.width, 0, size.width, borderRadius);
    // 绘制右侧竖直边
    path.lineTo(size.width, size.height);
    // 绘制底部右侧到斜切角起点
    path.lineTo(bottomLeftSlantHeight, size.height);
    // 绘制底部左侧斜切角
    path.lineTo(0, size.height - bottomLeftSlantHeight);
    // 绘制左侧竖直边到顶部圆角终点
    path.lineTo(0, borderRadius);
    // 绘制顶部左侧圆角并闭合路径
    path.quadraticBezierTo(0, 0, borderRadius, 0);

    canvas.drawPath(path, paint);
  }

  @override
  bool shouldRepaint(covariant CustomShapePainter oldDelegate) {
    return oldDelegate.color != color ||
        oldDelegate.borderRadius != borderRadius ||
        oldDelegate.bottomLeftSlantHeight != bottomLeftSlantHeight;
  }
}

2. 封装成可复用Widget

为了方便在项目中使用,我们可以把它封装成带裁剪的Widget,支持添加子元素:

class CustomShapeWidget extends StatelessWidget {
  final Widget? child;
  final Color color;
  final double borderRadius;
  final double bottomLeftSlantHeight;

  const CustomShapeWidget({
    super.key,
    this.child,
    this.color = Colors.blue,
    this.borderRadius = 16.0,
    this.bottomLeftSlantHeight = 24.0,
  });

  @override
  Widget build(BuildContext context) {
    return ClipPath(
      clipper: _CustomShapeClipper(
        borderRadius: borderRadius,
        bottomLeftSlantHeight: bottomLeftSlantHeight,
      ),
      child: Container(
        color: color,
        child: child,
      ),
    );
  }
}

// 配套的裁剪器,用于同步裁剪子Widget
class _CustomShapeClipper extends CustomClipper<Path> {
  final double borderRadius;
  final double bottomLeftSlantHeight;

  _CustomShapeClipper({
    required this.borderRadius,
    required this.bottomLeftSlantHeight,
  });

  @override
  Path getClip(Size size) {
    final path = Path();
    path.moveTo(borderRadius, 0);
    path.quadraticBezierTo(size.width, 0, size.width, borderRadius);
    path.lineTo(size.width, size.height);
    path.lineTo(bottomLeftSlantHeight, size.height);
    path.lineTo(0, size.height - bottomLeftSlantHeight);
    path.lineTo(0, borderRadius);
    path.quadraticBezierTo(0, 0, borderRadius, 0);
    return path;
  }

  @override
  bool shouldReclip(covariant _CustomShapeClipper oldClipper) {
    return oldClipper.borderRadius != borderRadius ||
        oldClipper.bottomLeftSlantHeight != bottomLeftSlantHeight;
  }
}

3. 使用示例

CustomShapeWidget(
  color: Colors.orange,
  borderRadius: 20.0,
  bottomLeftSlantHeight: 30.0,
  child: const Padding(
    padding: EdgeInsets.all(16.0),
    child: Text(
      "自定义形状内容",
      style: TextStyle(color: Colors.white, fontSize: 18.0),
    ),
  ),
)

二、极简替代方案:直接用ClipPath+自定义Path

如果不想写复杂的Painter,也可以直接用ClipPath配合自定义Path快速实现,代码更简洁:

Container(
  width: 200,
  height: 150,
  child: ClipPath(
    clipper: _QuickShapeClipper(),
    child: Container(
      color: Colors.green,
      child: const Center(
        child: Text("快速实现", style: TextStyle(color: Colors.white)),
      ),
    ),
  ),
)

class _QuickShapeClipper extends CustomClipper<Path> {
  @override
  Path getClip(Size size) {
    final double radius = 16;
    final double slantHeight = 24;
    final path = Path();
    path.moveTo(radius, 0);
    path.quadraticBezierTo(size.width, 0, size.width, radius);
    path.lineTo(size.width, size.height);
    path.lineTo(slantHeight, size.height);
    path.lineTo(0, size.height - slantHeight);
    path.lineTo(0, radius);
    path.quadraticBezierTo(0, 0, radius, 0);
    return path;
  }

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

三、调整参数匹配你的需求

如果形状和预期有差异,只需要修改几个核心参数:

  • borderRadius:控制顶部圆角的大小
  • bottomLeftSlantHeight:控制底部左侧斜切角的高度
  • 如果需要底部右侧斜切,只需要修改Path中的线段:把path.lineTo(slantHeight, size.height);改为path.lineTo(size.width - slantHeight, size.height);,同时把path.lineTo(0, size.height - slantHeight);改为path.lineTo(size.width, size.height - slantHeight);即可。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.07 06:50:00