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

Flutter:带圆形进度指示器的自定义步骤条长文本处理

带步骤进度条的长文本显示问题

我使用Flutter的CustomPaint和Stack组件实现了一个带步骤的进度条,但遇到labelText过长时无法在SizedBox内完整显示的问题。

我的代码

import 'package:flutter/material.dart';
import 'package:percent_indicator/percent_indicator.dart';

class StepperProgressBar extends StatelessWidget {
  final List<Widget> stepUnits;

  StepperProgressBar({
    super.key,
    required this.stepUnits,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 300,
      width: 450,
      child: ListView(
        scrollDirection: Axis.horizontal,
        children: stepUnits,
      ),
    );
  }
}

class StepUnit extends StatelessWidget {
  final double progressBarHeight;
  double? progressBarWidth;
  double? circularStepSize;
  double? circularStepWidth;
  final double progressBarValue;
  final Icon? icon;
  bool isFirst = false;
  final Color? backgroundColor;
  final Color? progressColor;
  final String? labelText;

  StepUnit({
    super.key,
    this.progressBarHeight = 5,
    this.progressBarWidth,
    this.circularStepSize,
    this.circularStepWidth,
    this.progressBarValue = 0.0,
    this.icon,
    this.progressColor = const Color.fromARGB(255, 90, 116, 125),
    this.backgroundColor = const Color.fromARGB(255, 161, 169, 172),
    this.labelText,
  });

  @override
  Widget build(BuildContext context) {
    bool isDone = progressBarValue >= 1;
    bool isInProgress = progressBarValue > 0 && progressBarValue < 1;
    final stepUnitBoxWidth = SizedBox(width: 11);

    progressBarWidth ??= MediaQuery.of(context).size.width * 0.3;
    circularStepSize ??= MediaQuery.of(context).size.width * 0.11;
    circularStepWidth ??= MediaQuery.of(context).size.width * 0.1;

    return Row(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        if (!isFirst) ...[
          stepUnitBoxWidth,
          LinearPercentIndicator(
            width: progressBarWidth,
            animation: false,
            lineHeight: progressBarHeight,
            animationDuration: 2500,
            percent: progressBarValue,
            progressColor: progressColor,
            backgroundColor: backgroundColor,
            barRadius: Radius.circular(10),
            alignment: MainAxisAlignment.start,
          ),
        ],
        stepUnitBoxWidth,
        SizedBox(
          height: 300,
          width: 103,  // 扩大宽度以适配长文本
          child: Stack(
            alignment: Alignment.center,
            children: [
              CustomPaint(
                painter: CirclePainter(
                  isDone: isDone,
                  width: circularStepWidth!,
                  isInProgress: isInProgress,
                  backgroundColor: backgroundColor!,
                  progressColor: progressColor!,
                ),
                size: Size(circularStepSize!, circularStepSize!),
              ),
              if (isDone)
                icon ?? const Icon(Icons.done, size: 25, color: Colors.white)
              else if (icon != null)
                icon!,
              if (labelText != null)
                Positioned(
                  bottom: 30,
                  child: Text(
                    labelText!,
                    style: TextStyle(fontSize: 15, color: Colors.blueGrey),
                  ),
                ),
            ],
          ),
        ),
      ],
    );
  }
}

class CirclePainter extends CustomPainter {
  final bool isDone;
  final double width;
  final bool isInProgress;
  final Color backgroundColor;
  final Color progressColor;

  CirclePainter({
    this.isDone = false,
    this.width = 4,
    this.isInProgress = false,
    required this.progressColor,
    required this.backgroundColor,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final paint = Paint()
      ..color = isDone ? progressColor : backgroundColor
      ..strokeWidth = 4.0
      ..style = isInProgress ? PaintingStyle.stroke : PaintingStyle.fill;

    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;

    canvas.drawCircle(center, radius, paint);
    if (isInProgress) {
      paint.style = PaintingStyle.fill;
      canvas.drawCircle(center, radius - radius / 3, paint);
    }
  }

  @override
  bool shouldRepaint(covariant CustomPainter oldDelegate) {
    return false;
  }
}

示例使用

List<StepUnit> stepUnits = [
  StepUnit(
    progressBarValue: 1,
    labelText: 'STEP 1',
  ),
  StepUnit(
    progressBarValue: 1,
    labelText: 'TEXTTOOLONG',
  ),
  StepUnit(
    progressBarValue: 1,
    labelText: 'STEP 3',
  ),
  StepUnit(
    progressBarValue: 1,
    labelText: 'STEP 4',
  ),
];

问题与尝试

当labelText为长字符串时,无法在SizedBox内完整显示。我尝试扩大Stack外层SizedBox的宽度:

SizedBox(
  height: 300,
  width: 103,
  child: Stack(
    alignment: Alignment.center,
    children: [
      CustomPaint(
        painter: CirclePainter(
          isDone: isDone,
          width: circularStepWidth!,
          isInProgress: isInProgress,
          backgroundColor: backgroundColor!,
          progressColor: progressColor!,
        ),
        size: Size(circularStepSize!, circularStepSize!),
      ),
      if (isDone)
        icon ?? const Icon(Icons.done, size: 25, color: Colors.white)
      else if (icon != null)
        icon!,
      if (labelText != null)
        Positioned(
          bottom: 30,
          child: Text(
            labelText!,
            style: TextStyle(fontSize: 15, color: Colors.blueGrey),
          ),
        ),
    ],
  ),
),

但扩大SizedBox宽度会破坏组件整体布局,无法达到预期效果。

需求

我需要实现Text组件可向右扩展,无论文本长度或字体大小如何,都不会破坏原有布局。


内容的提问来源于stack exchange,提问作者Zaknafein Do'urden

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.23 04:00:10