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

Flutter中如何在CircularPercentIndicator环形内添加弧形标题文本?

实现堆叠环形进度条内的环绕式标题

我正在开发一个仪表盘项目,使用堆叠的CircularPercentIndicator组件,现在需要给每个环形内部添加贴合弧度的环绕式标题,现有代码如下:

@override
Widget build(BuildContext context) {
  return CircularPercentIndicator(
    radius: 170.0,
    animation: true,
    animationDuration: 1200,
    lineWidth: 15.0,
    percent: totAggr,
    center: CircularPercentIndicator(
      radius: 150.0,
      lineWidth: 12.5,
      animation: true,
      percent: btc,
      center: CircularPercentIndicator(
        radius: 130.0,
        lineWidth: 12.5,
        animation: true,
        percent: eth,
        center: CircularPercentIndicator(
          radius: 110,
          lineWidth: 12.5,
          percent: alt,
          center: totAggr > 0.51
              ? Image.asset('assets/logos/bull.png')
              : Image.asset('assets/logos/bear.png'),
          backgroundColor: Colors.red,
          progressColor: Colors.green,
        ),
        circularStrokeCap: CircularStrokeCap.butt,
        backgroundColor: Colors.red,
        progressColor: Colors.green,
      ),
      circularStrokeCap: CircularStrokeCap.butt,
      backgroundColor: Colors.red,
      progressColor: Colors.green,
    ),
    footer: const Text(
      "BULL/BEAR",
      style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0),
    ),
    circularStrokeCap: CircularStrokeCap.butt,
    backgroundColor: Colors.red,
    progressColor: Colors.green,
  );
}

当前效果如图,外圈示例为预期的环绕标题效果:
环形进度条预期效果


解决方案

CircularPercentIndicator本身不支持环绕文本,可通过以下两种方式实现需求:

方案一:使用第三方包circular_text

  1. 在pubspec.yaml中添加依赖:
dependencies:
  circular_text: ^2.0.0 # 可替换为最新版本
  1. 改造堆叠结构,用Stack将环形进度条与环绕文本组合,确保文本贴合环形内部:
@override
Widget build(BuildContext context) {
  return Stack(
    alignment: Alignment.center,
    children: [
      // 最外层环形进度条
      CircularPercentIndicator(
        radius: 170.0,
        animation: true,
        animationDuration: 1200,
        lineWidth: 15.0,
        percent: totAggr,
        circularStrokeCap: CircularStrokeCap.butt,
        backgroundColor: Colors.red,
        progressColor: Colors.green,
      ),
      // 最外层环绕标题
      CircularText(
        text: "TOTAL AGGREGATE",
        style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
        // 计算文本半径:环形半径 - 线宽一半 - 文本间距
        radius: 170 - 15/2 - 8,
        startAngle: 90, // 文本从顶部开始
        direction: CircularTextDirection.clockwise, // 顺时针环绕
      ),
      // 第二层环形+标题
      Stack(
        alignment: Alignment.center,
        children: [
          CircularPercentIndicator(
            radius: 150.0,
            lineWidth: 12.5,
            animation: true,
            percent: btc,
            circularStrokeCap: CircularStrokeCap.butt,
            backgroundColor: Colors.red,
            progressColor: Colors.green,
          ),
          CircularText(
            text: "BTC PERCENT",
            style: TextStyle(fontWeight: FontWeight.bold, fontSize: 13),
            radius: 150 - 12.5/2 - 8,
            startAngle: 90,
            direction: CircularTextDirection.clockwise,
          ),
          // 第三层环形+标题
          Stack(
            alignment: Alignment.center,
            children: [
              CircularPercentIndicator(
                radius: 130.0,
                lineWidth: 12.5,
                animation: true,
                percent: eth,
                circularStrokeCap: CircularStrokeCap.butt,
                backgroundColor: Colors.red,
                progressColor: Colors.green,
              ),
              CircularText(
                text: "ETH PERCENT",
                style: TextStyle(fontWeight: FontWeight.bold, fontSize: 12),
                radius: 130 - 12.5/2 - 8,
                startAngle: 90,
                direction: CircularTextDirection.clockwise,
              ),
              // 最内层环形+中心图标
              CircularPercentIndicator(
                radius: 110,
                lineWidth: 12.5,
                percent: alt,
                center: totAggr > 0.51
                    ? Image.asset('assets/logos/bull.png')
                    : Image.asset('assets/logos/bear.png'),
                backgroundColor: Colors.red,
                progressColor: Colors.green,
              ),
            ],
          ),
        ],
      ),
      // 底部文本
      Positioned(
        bottom: -30,
        child: const Text(
          "BULL/BEAR",
          style: TextStyle(fontWeight: FontWeight.bold, fontSize: 17.0),
        ),
      ),
    ],
  );
}

方案二:自定义绘制环绕文本

如果不想依赖第三方包,可通过CustomPaint手动绘制:

  1. 定义自定义绘制器:
import 'dart:math';
import 'package:flutter/material.dart';

class CircularTextPainter extends CustomPainter {
  final String text;
  final double radius;
  final TextStyle style;
  final double startAngle;

  CircularTextPainter({
    required this.text,
    required this.radius,
    required this.style,
    this.startAngle = 90,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final textSpan = TextSpan(text: text, style: style);
    final textPainter = TextPainter(
      text: textSpan,
      textDirection: TextDirection.ltr,
    );
    textPainter.layout();

    final anglePerChar = 2 * pi / text.length;
    final startRadians = startAngle * pi / 180;

    for (int i = 0; i < text.length; i++) {
      final char = text[i];
      final charSpan = TextSpan(text: char, style: style);
      final charPainter = TextPainter(
        text: charSpan,
        textDirection: TextDirection.ltr,
      );
      charPainter.layout();

      final angle = startRadians + i * anglePerChar;
      final x = size.width / 2 + radius * cos(angle) - charPainter.width / 2;
      final y = size.height / 2 + radius * sin(angle) - charPainter.height / 2;

      canvas.save();
      canvas.translate(x + charPainter.width / 2, y + charPainter.height / 2);
      canvas.rotate(angle + pi / 2); // 旋转字符使其垂直于半径
      canvas.translate(-charPainter.width / 2, -charPainter.height / 2);
      charPainter.paint(canvas, Offset.zero);
      canvas.restore();
    }
  }

  @override
  bool shouldRepaint(CircularTextPainter oldDelegate) {
    return text != oldDelegate.text ||
        radius != oldDelegate.radius ||
        style != oldDelegate.style ||
        startAngle != oldDelegate.startAngle;
  }
}
  1. 使用自定义绘制器:
CustomPaint(
  size: Size(324, 324), // 对应环形直径:2*(170-15/2-8)
  painter: CircularTextPainter(
    text: "TOTAL AGGREGATE",
    radius: 170 - 15/2 - 8,
    style: TextStyle(fontWeight: FontWeight.bold, fontSize: 14),
  ),
)

注意事项

  • 调整文本字号和间距,避免文本超出环形范围
  • 可修改startAngle和环绕方向,实现不同的文本起始位置和走向
  • 堆叠层级需注意,确保文本显示在环形上方(或根据需求调整)

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.02 21:50:39