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

Flutter如何为组合实现的不规则红色图形添加贴合外边框

Flutter组合异形组件外边框实现

我编写了如下代码:

Row(
  crossAxisAlignment: CrossAxisAlignment.start,
  children: [
    Column(
      mainAxisAlignment: MainAxisAlignment.start,
      crossAxisAlignment: CrossAxisAlignment.start,
      children: [
        Container(
          height: 40.0,
          width: MediaQuery.of(context).size.width / 2 - 20,
          decoration: BoxDecoration(
            color: Colors.red,
            borderRadius: BorderRadius.only(
              topLeft: Radius.circular(15.0),
              topRight: Radius.circular(15.0),
            ),
          ),
        ),
        Container(
          height: 80.0,
          width: MediaQuery.of(context).size.width - 40,
          decoration: BoxDecoration(
            color: Colors.red,
            borderRadius: BorderRadius.only(
              bottomLeft: Radius.circular(15.0),
              bottomRight: Radius.circular(15.0),
              topRight: Radius.circular(15.0),
            ),
          ),
        ),
      ],
    ),
  ],
),

上述代码的运行效果如下:
红色组合图形运行效果

需求为该红色组合图形添加贴合轮廓的外边框,直接嵌套Container设置Border.all()无法实现预期效果。


普通Container的边框默认是矩形,无法匹配这个L型带圆角的不规则轮廓,以下是两种可直接落地的实现方案:

方案1:CustomPaint 自绘完整轮廓

直接在一个画布上同时绘制图形填充和外边框,完全规避多组件拼接导致的衔接处断边、边框重叠问题:

// 替换原有Row/Column拼接布局
SizedBox(
  width: MediaQuery.of(context).size.width - 40,
  height: 120,
  child: CustomPaint(
    painter: RedShapePainter(
      borderColor: Colors.black, // 自定义边框颜色
      borderWidth: 2, // 自定义边框粗细
    ),
  ),
)

class RedShapePainter extends CustomPainter {
  final Color fillColor = Colors.red;
  final Color borderColor;
  final double borderWidth;
  final double radius = 15;

  RedShapePainter({required this.borderColor, required this.borderWidth});

  @override
  void paint(Canvas canvas, Size size) {
    final double topSectionWidth = size.width / 2;
    // 绘制红色填充
    final fillPaint = Paint()..color = fillColor..style = PaintingStyle.fill;
    final fillPath = Path()
      ..addRRect(RRect.fromRectAndCorners(
        Rect.fromLTWH(0, 0, topSectionWidth, 40),
        topLeft: Radius.circular(radius),
        topRight: Radius.circular(radius),
      ))
      ..addRRect(RRect.fromRectAndCorners(
        Rect.fromLTWH(0, 40, size.width, 80),
        bottomLeft: Radius.circular(radius),
        bottomRight: Radius.circular(radius),
        topRight: Radius.circular(radius),
      ));
    canvas.drawPath(fillPath, fillPaint);

    // 绘制外轮廓边框,跳过两个块衔接的内部边
    final borderPaint = Paint()
      ..color = borderColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = borderWidth
      ..strokeCap = StrokeCap.round;
    final borderPath = Path()
      ..moveTo(0, radius)
      ..arcToPoint(Offset(radius, 0), radius: Radius.circular(radius))
      ..lineTo(topSectionWidth - radius, 0)
      ..arcToPoint(Offset(topSectionWidth, radius), radius: Radius.circular(radius))
      ..lineTo(topSectionWidth, 40)
      ..lineTo(size.width - radius, 40)
      ..arcToPoint(Offset(size.width, 40 + radius), radius: Radius.circular(radius))
      ..lineTo(size.width, size.height - radius)
      ..arcToPoint(Offset(size.width - radius, size.height), radius: Radius.circular(radius))
      ..lineTo(radius, size.height)
      ..arcToPoint(Offset(0, size.height - radius), radius: Radius.circular(radius))
      ..lineTo(0, radius)
      ..close();
    canvas.drawPath(borderPath, borderPaint);
  }

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

方案2:自定义ShapeBorder 实现轮廓边框

通过继承OutlinedBorder自定义形状,直接用在Container的decoration里,代码复用性更高:

Container(
  width: MediaQuery.of(context).size.width - 40,
  height: 120,
  decoration: ShapeDecoration(
    color: Colors.red,
    shape: LShapeBorder(
      side: BorderSide(color: Colors.black, width: 2), // 边框配置
      radius: 15,
    ),
  ),
)

class LShapeBorder extends OutlinedBorder {
  final double radius;
  const LShapeBorder({
    required this.radius,
    super.side = BorderSide.none,
  });

  @override
  OutlinedBorder copyWith({BorderSide? side}) {
    return LShapeBorder(radius: radius, side: side ?? this.side);
  }

  @override
  Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
    return getOuterPath(rect).shift(Offset(side.strokeInset, side.strokeInset));
  }

  @override
  Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
    final topWidth = rect.width / 2;
    final path = Path()
      ..addRRect(RRect.fromRectAndCorners(
        Rect.fromLTWH(rect.left, rect.top, topWidth, 40),
        topLeft: Radius.circular(radius),
        topRight: Radius.circular(radius),
      ))
      ..addRRect(RRect.fromRectAndCorners(
        Rect.fromLTWH(rect.left, rect.top + 40, rect.width, rect.height - 40),
        bottomLeft: Radius.circular(radius),
        bottomRight: Radius.circular(radius),
        topRight: Radius.circular(radius),
      ));
    path.fillType = PathFillType.nonZero;
    return path;
  }

  @override
  void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {
    if (side.style == BorderStyle.none) return;
    final outerPath = getOuterPath(rect);
    final borderPath = Path()..addPath(outerPath, Offset.zero);
    canvas.drawPath(borderPath, side.toPaint()..strokeJoin = StrokeJoin.round);
  }

  @override
  ShapeBorder scale(double t) {
    return LShapeBorder(radius: radius * t, side: side.scale(t));
  }
}

避坑提示:不要尝试给两个拼接的Container分别加边框,衔接位置会出现边框重叠、断缝的问题,单组件绘制完整轮廓是最稳定的实现方式。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.26 18:54:23