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

Flutter实现带中间文字的Container完整边框(类分割线效果)

实现带内嵌留白文字的完整边框容器

方案一:用Stack快速实现(推荐)

利用Stack将带边框的容器和带背景的文字叠加,文字背景会覆盖下方的边框,形成留白效果,实现简单易维护。

class BorderedTitleContainer extends StatelessWidget {
  final String title;
  final Widget child;
  final Color borderColor;
  final Color bgColor;
  final double borderWidth;
  final double borderRadius;

  const BorderedTitleContainer({
    super.key,
    required this.title,
    required this.child,
    this.borderColor = Colors.grey,
    this.bgColor = Colors.white,
    this.borderWidth = 1.0,
    this.borderRadius = 8.0,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      alignment: Alignment.topCenter,
      children: [
        // 底层带边框的容器,顶部预留文字空间
        Container(
          margin: const EdgeInsets.only(top: 12),
          padding: const EdgeInsets.all(16),
          decoration: BoxDecoration(
            border: Border.all(color: borderColor, width: borderWidth),
            borderRadius: BorderRadius.circular(borderRadius),
          ),
          child: child,
        ),
        // 上层标题,用背景色覆盖边框
        Container(
          padding: const EdgeInsets.symmetric(horizontal: 8),
          color: bgColor,
          child: Text(
            title,
            style: TextStyle(
              fontSize: 14,
              fontWeight: FontWeight.w500,
              color: borderColor,
            ),
          ),
        ),
      ],
    );
  }
}

使用示例

BorderedTitleContainer(
  title: "用户信息",
  child: Column(
    crossAxisAlignment: CrossAxisAlignment.start,
    children: const [
      Text("姓名: 张三"),
      SizedBox(height: 8),
      Text("年龄: 28"),
      SizedBox(height: 8),
      Text("邮箱: zhangsan@example.com"),
    ],
  ),
)

方案二:自定义Painter实现(灵活定制)

如果需要文字在左侧/右侧/底部,或者边框样式更复杂,用CustomPainter可以完全自定义绘制逻辑。

首先定义位置枚举和绘制类:

import 'dart:math' as math;

enum TitlePosition { topCenter, leftCenter, rightCenter, bottomCenter }

class BorderWithTitlePainter extends CustomPainter {
  final String title;
  final Color borderColor;
  final Color titleBgColor;
  final double borderWidth;
  final TextStyle titleStyle;
  final TitlePosition titlePosition;
  final double borderRadius;

  BorderWithTitlePainter({
    required this.title,
    required this.borderColor,
    required this.titleBgColor,
    required this.borderWidth,
    required this.titleStyle,
    required this.titlePosition,
    this.borderRadius = 8.0,
  });

  @override
  void paint(Canvas canvas, Size size) {
    // 绘制完整边框
    final borderPaint = Paint()
      ..color = borderColor
      ..strokeWidth = borderWidth
      ..style = PaintingStyle.stroke;
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, size.width, size.height),
        Radius.circular(borderRadius),
      ),
      borderPaint,
    );

    // 计算文字尺寸
    final textSpan = TextSpan(text: title, style: titleStyle);
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    )..layout();
    final textW = textPainter.width;
    final textH = textPainter.height;
    const padding = 8.0;

    // 根据位置计算文字和背景的偏移
    Offset textOffset;
    Rect bgRect;

    switch (titlePosition) {
      case TitlePosition.topCenter:
        textOffset = Offset((size.width - textW)/2, -textH/2);
        bgRect = Rect.fromLTWH(
          (size.width - textW - padding*2)/2,
          -textH/2,
          textW + padding*2,
          textH,
        );
        break;
      case TitlePosition.leftCenter:
        textOffset = Offset(-textW/2, (size.height - textH)/2);
        bgRect = Rect.fromLTWH(
          -textW/2 - padding,
          (size.height - textH)/2,
          textW + padding*2,
          textH,
        );
        canvas.rotate(-math.pi/2);
        canvas.translate(-size.height, 0);
        break;
      // 可自行扩展其他位置的逻辑
      default:
        textOffset = Offset((size.width - textW)/2, -textH/2);
        bgRect = Rect.fromLTWH(
          (size.width - textW - padding*2)/2,
          -textH/2,
          textW + padding*2,
          textH,
        );
    }

    // 绘制文字背景(覆盖边框)
    canvas.drawRect(bgRect, Paint()..color = titleBgColor);
    // 绘制文字
    textPainter.paint(canvas, textOffset);
  }

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

使用示例

CustomPaint(
  painter: BorderWithTitlePainter(
    title: "高级设置",
    borderColor: Colors.blue,
    titleBgColor: Colors.white,
    borderWidth: 2.0,
    titleStyle: const TextStyle(
      fontSize: 16,
      fontWeight: FontWeight.bold,
      color: Colors.blue,
    ),
    titlePosition: TitlePosition.topCenter,
  ),
  child: const SizedBox(
    width: 350,
    height: 250,
    child: Padding(
      padding: EdgeInsets.all(20.0),
      child: Text("这里是高级设置的内容区域"),
    ),
  ),
)

为什么分割线组件无法实现?

分割线是单一方向的线条组件,只能实现局部分割效果,无法组合成完整边框,更没法在边框中间实现带背景留白的文字嵌入。而上述两种方案通过叠加或自定义绘制,能轻松实现边框与文字的融合效果。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 10:50:30