进阶实战 Flutter for OpenHarmony:高级进度指示器系统 - 用户体验优化实现

欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net


一、进度指示器系统架构深度解析

在现代移动应用中,进度指示器是用户反馈系统的重要组成部分。从简单的线性进度条到复杂的动画进度效果,Flutter 提供了灵活的组件体系。理解这套体系的底层原理,是构建高性能进度指示器系统的基础。

📱 1.1 Flutter 进度指示器架构

Flutter 的进度指示器系统由多个核心层次组成,每一层都有其特定的职责:

复制代码
┌─────────────────────────────────────────────────────────────────┐
│                      应用层 (Application Layer)                  │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │  LinearProgressIndicator, CircularProgressIndicator,    │    │
│  │  RefreshIndicator, Custom Progress Widgets              │    │
│  └─────────────────────────────────────────────────────────┘    │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              动画控制层 (Animation Control Layer)         │    │
│  │  AnimationController, Tween, CurvedAnimation...         │    │
│  └─────────────────────────────────────────────────────────┘    │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              渲染层 (Rendering Layer)                     │    │
│  │  CustomPainter, Canvas, Paint, Shader...                │    │
│  └─────────────────────────────────────────────────────────┘    │
│                              │                                   │
│                              ▼                                   │
│  ┌─────────────────────────────────────────────────────────┐    │
│  │              状态管理层 (State Management Layer)          │    │
│  │  ValueNotifier, StreamController, ChangeNotifier...     │    │
│  └─────────────────────────────────────────────────────────┘    │
└─────────────────────────────────────────────────────────────────┘

🔬 1.2 进度指示器核心组件详解

Flutter 进度指示器系统的核心组件包括以下几个部分:

ProgressIndicator(进度指示器基类)

ProgressIndicator 是所有进度指示器的抽象基类,定义了进度指示器的基本属性和行为。

dart 复制代码
// 线性进度指示器
LinearProgressIndicator(
  value: 0.7,                    // 当前进度值 (0.0 - 1.0)
  backgroundColor: Colors.grey,  // 背景颜色
  color: Colors.blue,            // 进度颜色
  minHeight: 8,                  // 最小高度
  borderRadius: BorderRadius.circular(4),
)

// 圆形进度指示器
CircularProgressIndicator(
  value: 0.5,                    // 当前进度值
  backgroundColor: Colors.grey,
  color: Colors.blue,
  strokeWidth: 4,                // 线条宽度
  strokeCap: StrokeCap.round,    // 线条端点样式
)

AnimationController(动画控制器)

动画控制器用于控制进度变化的动画效果,使进度更新更加平滑自然。

dart 复制代码
final controller = AnimationController(
  duration: const Duration(milliseconds: 500),
  vsync: this,
);

// 平滑过渡到目标进度
controller.animateTo(targetProgress);

CustomPainter(自定义绘制)

CustomPainter 允许完全自定义进度指示器的外观,是实现复杂进度效果的核心。

dart 复制代码
class CustomProgressPainter extends CustomPainter {
  final double progress;
  
  @override
  void paint(Canvas canvas, Size size) {
    // 自定义绘制逻辑
  }
  
  @override
  bool shouldRepaint(CustomProgressPainter oldDelegate) {
    return progress != oldDelegate.progress;
  }
}

🎯 1.3 进度指示器设计原则

设计优秀的进度指示器需要遵循以下原则:

视觉反馈原则:

复制代码
┌─────────────────────────────────────────────────────────────┐
│                    进度指示器设计原则                        │
├─────────────────────────────────────────────────────────────┤
│  ┌─────────────────────────────────────────────────────┐   │
│  │  1. 即时反馈 - 用户操作后立即显示进度变化            │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  2. 状态清晰 - 明确区分确定/不确定状态               │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  3. 视觉层次 - 进度与背景形成对比                    │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  4. 动画流畅 - 进度变化使用平滑过渡                  │   │
│  └─────────────────────────────────────────────────────┘   │
│  ┌─────────────────────────────────────────────────────┐   │
│  │  5. 信息完整 - 显示百分比或剩余时间                  │   │
│  └─────────────────────────────────────────────────────┘   │
└─────────────────────────────────────────────────────────────┘

进度状态类型:

状态类型 说明 适用场景
确定进度 显示具体百分比 文件下载、上传
不确定进度 显示加载动画 网络请求、初始化
分段进度 显示多个阶段 多步骤任务
缓冲进度 显示缓冲与播放进度 视频播放器

二、基础进度指示器实现

基础进度指示器包括线性进度条、圆形进度指示器和分段进度条。这些是构建复杂进度系统的基础组件。

👆 2.1 渐变线性进度条

渐变线性进度条通过颜色渐变增强视觉效果,使进度变化更加直观。

dart 复制代码
import 'dart:math';
import 'package:flutter/material.dart';

/// 渐变线性进度条
class GradientLinearProgress extends StatelessWidget {
  final double progress;
  final List<Color> gradientColors;
  final double height;
  final Color backgroundColor;
  final double borderRadius;

  const GradientLinearProgress({
    super.key,
    required this.progress,
    this.gradientColors = const [Colors.blue, Colors.purple],
    this.height = 12,
    this.backgroundColor = Colors.grey,
    this.borderRadius = 6,
  });

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Container(
          height: height,
          decoration: BoxDecoration(
            color: backgroundColor.withOpacity(0.2),
            borderRadius: BorderRadius.circular(borderRadius),
          ),
          child: Stack(
            children: [
              AnimatedContainer(
                duration: const Duration(milliseconds: 300),
                curve: Curves.easeOutCubic,
                width: constraints.maxWidth * progress.clamp(0.0, 1.0),
                decoration: BoxDecoration(
                  gradient: LinearGradient(
                    colors: gradientColors,
                  ),
                  borderRadius: BorderRadius.circular(borderRadius),
                  boxShadow: [
                    BoxShadow(
                      color: gradientColors.last.withOpacity(0.4),
                      blurRadius: 8,
                      offset: const Offset(0, 2),
                    ),
                  ],
                ),
              ),
              Center(
                child: Text(
                  '${(progress * 100).toStringAsFixed(0)}%',
                  style: TextStyle(
                    color: progress > 0.5 ? Colors.white : Colors.black54,
                    fontSize: height * 0.7,
                    fontWeight: FontWeight.bold,
                  ),
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

🔄 2.2 圆形进度指示器

圆形进度指示器适合显示在有限空间内,常用于按钮、卡片等组件中。

dart 复制代码
/// 圆形进度指示器
class CircleProgressPainter extends CustomPainter {
  final double progress;
  final Color progressColor;
  final Color backgroundColor;
  final double strokeWidth;
  final List<Color>? gradientColors;

  CircleProgressPainter({
    required this.progress,
    this.progressColor = Colors.blue,
    this.backgroundColor = Colors.grey,
    this.strokeWidth = 8,
    this.gradientColors,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = min(size.width, size.height) / 2 - strokeWidth / 2;

    // 绘制背景圆
    final bgPaint = Paint()
      ..color = backgroundColor.withOpacity(0.2)
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;
    canvas.drawCircle(center, radius, bgPaint);

    // 绘制进度弧
    final progressPaint = Paint()
      ..style = PaintingStyle.stroke
      ..strokeWidth = strokeWidth
      ..strokeCap = StrokeCap.round;

    if (gradientColors != null && gradientColors!.length >= 2) {
      progressPaint.shader = SweepGradient(
        center: Alignment.center,
        colors: gradientColors!,
        transform: const GradientRotation(-pi / 2),
      ).createShader(Rect.fromCircle(center: center, radius: radius));
    } else {
      progressPaint.color = progressColor;
    }

    final sweepAngle = 2 * pi * progress.clamp(0.0, 1.0);
    canvas.drawArc(
      Rect.fromCircle(center: center, radius: radius),
      -pi / 2,
      sweepAngle,
      false,
      progressPaint,
    );
  }

  @override
  bool shouldRepaint(CircleProgressPainter oldDelegate) {
    return progress != oldDelegate.progress;
  }
}

/// 带中心文字的圆形进度
class CircleProgressWithLabel extends StatelessWidget {
  final double progress;
  final double size;
  final double strokeWidth;
  final List<Color> gradientColors;
  final String? label;
  final TextStyle? labelStyle;

  const CircleProgressWithLabel({
    super.key,
    required this.progress,
    this.size = 120,
    this.strokeWidth = 10,
    this.gradientColors = const [Colors.blue, Colors.purple],
    this.label,
    this.labelStyle,
  });

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      width: size,
      height: size,
      child: Stack(
        alignment: Alignment.center,
        children: [
          CustomPaint(
            size: Size(size, size),
            painter: CircleProgressPainter(
              progress: progress,
              strokeWidth: strokeWidth,
              gradientColors: gradientColors,
            ),
          ),
          Column(
            mainAxisSize: MainAxisSize.min,
            children: [
              Text(
                label ?? '${(progress * 100).toStringAsFixed(0)}%',
                style: labelStyle ??
                    TextStyle(
                      fontSize: size * 0.2,
                      fontWeight: FontWeight.bold,
                      color: gradientColors.last,
                    ),
              ),
            ],
          ),
        ],
      ),
    );
  }
}

🌊 2.3 波浪进度指示器

波浪进度指示器通过水波效果展示进度,视觉效果生动有趣。

dart 复制代码
/// 波浪进度指示器
class WaveProgressPainter extends CustomPainter {
  final double progress;
  final Color waveColor;
  final Color backgroundColor;
  final double amplitude;
  final double frequency;
  final double phase;

  WaveProgressPainter({
    required this.progress,
    required this.phase,
    this.waveColor = Colors.blue,
    this.backgroundColor = Colors.grey,
    this.amplitude = 10,
    this.frequency = 2,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final waterLevel = size.height * (1 - progress.clamp(0.0, 1.0));

    // 绘制背景圆
    final bgPaint = Paint()
      ..color = backgroundColor.withOpacity(0.2)
      ..style = PaintingStyle.fill;
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      min(size.width, size.height) / 2,
      bgPaint,
    );

    // 绘制波浪
    final wavePath = Path();
    wavePath.moveTo(0, size.height);
    wavePath.lineTo(0, waterLevel);

    for (double x = 0; x <= size.width; x++) {
      final y = waterLevel +
          amplitude * sin((x / size.width * 2 * pi * frequency) + phase);
      wavePath.lineTo(x, y);
    }

    wavePath.lineTo(size.width, size.height);
    wavePath.close();

    // 裁剪为圆形
    canvas.save();
    canvas.clipPath(
      Path()
        ..addOval(Rect.fromLTWH(0, 0, size.width, size.height)),
    );

    final wavePaint = Paint()
      ..shader = LinearGradient(
        begin: Alignment.topCenter,
        end: Alignment.bottomCenter,
        colors: [
          waveColor.withOpacity(0.8),
          waveColor,
        ],
      ).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
    canvas.drawPath(wavePath, wavePaint);
    canvas.restore();

    // 绘制边框
    final borderPaint = Paint()
      ..color = waveColor
      ..style = PaintingStyle.stroke
      ..strokeWidth = 3;
    canvas.drawCircle(
      Offset(size.width / 2, size.height / 2),
      min(size.width, size.height) / 2 - 1.5,
      borderPaint,
    );
  }

  @override
  bool shouldRepaint(WaveProgressPainter oldDelegate) {
    return progress != oldDelegate.progress || phase != oldDelegate.phase;
  }
}

/// 动画波浪进度
class AnimatedWaveProgress extends StatefulWidget {
  final double progress;
  final double size;
  final Color waveColor;

  const AnimatedWaveProgress({
    super.key,
    required this.progress,
    this.size = 150,
    this.waveColor = Colors.blue,
  });

  @override
  State<AnimatedWaveProgress> createState() => _AnimatedWaveProgressState();
}

class _AnimatedWaveProgressState extends State<AnimatedWaveProgress>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(seconds: 2),
      vsync: this,
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return SizedBox(
          width: widget.size,
          height: widget.size,
          child: Stack(
            alignment: Alignment.center,
            children: [
              CustomPaint(
                size: Size(widget.size, widget.size),
                painter: WaveProgressPainter(
                  progress: widget.progress,
                  phase: _controller.value * 2 * pi,
                  waveColor: widget.waveColor,
                ),
              ),
              Text(
                '${(widget.progress * 100).toStringAsFixed(0)}%',
                style: TextStyle(
                  fontSize: widget.size * 0.2,
                  fontWeight: FontWeight.bold,
                  color: Colors.white,
                  shadows: [
                    Shadow(
                      color: Colors.black.withOpacity(0.3),
                      blurRadius: 4,
                    ),
                  ],
                ),
              ),
            ],
          ),
        );
      },
    );
  }
}

三、高级进度指示器实现

高级进度指示器包括分段进度条、步骤进度、下载进度条和动画进度效果,满足复杂业务场景需求。

📊 3.1 分段进度条

分段进度条将进度分为多个阶段,每个阶段可以有不同的颜色和状态。

dart 复制代码
/// 分段进度条
class SegmentedProgressPainter extends CustomPainter {
  final List<Segment> segments;
  final double spacing;
  final double borderRadius;

  SegmentedProgressPainter({
    required this.segments,
    this.spacing = 4,
    this.borderRadius = 8,
  });

  @override
  void paint(Canvas canvas, Size size) {
    final totalWeight = segments.fold<double>(0, (sum, s) => sum + s.weight);
    double currentX = 0;

    for (final segment in segments) {
      final segmentWidth =
          (size.width - spacing * (segments.length - 1)) * segment.weight / totalWeight;

      final rect = RRect.fromRectAndRadius(
        Rect.fromLTWH(currentX, 0, segmentWidth, size.height),
        Radius.circular(borderRadius),
      );

      final paint = Paint()
        ..color = segment.color
        ..style = PaintingStyle.fill;

      canvas.drawRRect(rect, paint);

      // 绘制标签
      if (segment.label != null) {
        final textPainter = TextPainter(
          text: TextSpan(
            text: segment.label!,
            style: TextStyle(
              color: Colors.white,
              fontSize: size.height * 0.5,
              fontWeight: FontWeight.bold,
            ),
          ),
          textDirection: TextDirection.ltr,
        )..layout();
        textPainter.paint(
          canvas,
          Offset(
            currentX + segmentWidth / 2 - textPainter.width / 2,
            size.height / 2 - textPainter.height / 2,
          ),
        );
      }

      currentX += segmentWidth + spacing;
    }
  }

  @override
  bool shouldRepaint(SegmentedProgressPainter oldDelegate) {
    if (segments.length != oldDelegate.segments.length) return true;
    for (int i = 0; i < segments.length; i++) {
      if (segments[i] != oldDelegate.segments[i]) return true;
    }
    return false;
  }
}

/// 分段数据模型
class Segment {
  final double weight;
  final Color color;
  final String? label;

  const Segment({
    required this.weight,
    required this.color,
    this.label,
  });

  @override
  bool operator ==(Object other) =>
      identical(this, other) ||
      other is Segment &&
          weight == other.weight &&
          color == other.color &&
          label == other.label;

  @override
  int get hashCode => Object.hash(weight, color, label);
}

/// 分段进度条组件
class SegmentedProgressBar extends StatelessWidget {
  final List<Segment> segments;
  final double height;
  final double spacing;

  const SegmentedProgressBar({
    super.key,
    required this.segments,
    this.height = 24,
    this.spacing = 4,
  });

  @override
  Widget build(BuildContext context) {
    return CustomPaint(
      size: Size.fromHeight(height),
      painter: SegmentedProgressPainter(
        segments: segments,
        spacing: spacing,
      ),
    );
  }
}

📝 3.2 步骤进度指示器

步骤进度指示器显示多步骤任务的当前进度,常用于表单、向导等场景。

dart 复制代码
/// 步骤进度指示器
class StepProgressIndicator extends StatelessWidget {
  final int totalSteps;
  final int currentStep;
  final List<String>? stepLabels;
  final Color activeColor;
  final Color inactiveColor;
  final double stepSize;
  final double lineHeight;

  const StepProgressIndicator({
    super.key,
    required this.totalSteps,
    required this.currentStep,
    this.stepLabels,
    this.activeColor = Colors.blue,
    this.inactiveColor = Colors.grey,
    this.stepSize = 32,
    this.lineHeight = 3,
  });

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(
          children: List.generate(totalSteps, (index) {
            final isActive = index < currentStep;
            final isCurrent = index == currentStep - 1;

            return Expanded(
              child: Row(
                children: [
                  _buildStep(index, isActive, isCurrent),
                  if (index < totalSteps - 1)
                    Expanded(
                      child: AnimatedContainer(
                        duration: const Duration(milliseconds: 300),
                        height: lineHeight,
                        color: index < currentStep - 1
                            ? activeColor
                            : inactiveColor.withOpacity(0.3),
                      ),
                    ),
                ],
              ),
            );
          }),
        ),
        if (stepLabels != null && stepLabels!.isNotEmpty) ...[
          const SizedBox(height: 8),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: stepLabels!
                .map((label) => SizedBox(
                      width: stepSize,
                      child: Text(
                        label,
                        textAlign: TextAlign.center,
                        style: TextStyle(
                          fontSize: 12,
                          color: stepLabels!.indexOf(label) < currentStep
                              ? activeColor
                              : inactiveColor,
                        ),
                      ),
                    ))
                .toList(),
          ),
        ],
      ],
    );
  }

  Widget _buildStep(int index, bool isActive, bool isCurrent) {
    return Container(
      width: stepSize,
      height: stepSize,
      decoration: BoxDecoration(
        shape: BoxShape.circle,
        color: isActive ? activeColor : inactiveColor.withOpacity(0.2),
        border: Border.all(
          color: isActive ? activeColor : inactiveColor,
          width: 2,
        ),
        boxShadow: isCurrent
            ? [
                BoxShadow(
                  color: activeColor.withOpacity(0.4),
                  blurRadius: 8,
                  spreadRadius: 2,
                ),
              ]
            : null,
      ),
      child: Center(
        child: isActive
            ? const Icon(Icons.check, color: Colors.white, size: 18)
            : Text(
                '${index + 1}',
                style: TextStyle(
                  color: inactiveColor,
                  fontWeight: FontWeight.bold,
                ),
              ),
      ),
    );
  }
}

⬇️ 3.3 下载进度条

下载进度条显示文件下载进度,支持暂停、继续和取消操作。

dart 复制代码
/// 下载进度条
class DownloadProgressBar extends StatefulWidget {
  final double progress;
  final String fileName;
  final String fileSize;
  final VoidCallback? onPause;
  final VoidCallback? onResume;
  final VoidCallback? onCancel;
  final DownloadStatus status;

  const DownloadProgressBar({
    super.key,
    required this.progress,
    required this.fileName,
    required this.fileSize,
    this.onPause,
    this.onResume,
    this.onCancel,
    this.status = DownloadStatus.downloading,
  });

  @override
  State<DownloadProgressBar> createState() => _DownloadProgressBarState();
}

class _DownloadProgressBarState extends State<DownloadProgressBar>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    );
  }

  @override
  void didUpdateWidget(DownloadProgressBar oldWidget) {
    super.didUpdateWidget(oldWidget);
    if (widget.status == DownloadStatus.downloading &&
        oldWidget.status != DownloadStatus.downloading) {
      _controller.repeat();
    } else if (widget.status != DownloadStatus.downloading) {
      _controller.stop();
    }
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(
        color: Colors.white,
        borderRadius: BorderRadius.circular(12),
        boxShadow: [
          BoxShadow(
            color: Colors.black.withOpacity(0.1),
            blurRadius: 10,
          ),
        ],
      ),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            children: [
              Container(
                width: 48,
                height: 48,
                decoration: BoxDecoration(
                  color: Colors.blue.withOpacity(0.1),
                  borderRadius: BorderRadius.circular(8),
                ),
                child: const Icon(Icons.insert_drive_file, color: Colors.blue),
              ),
              const SizedBox(width: 12),
              Expanded(
                child: Column(
                  crossAxisAlignment: CrossAxisAlignment.start,
                  children: [
                    Text(
                      widget.fileName,
                      style: const TextStyle(
                        fontWeight: FontWeight.bold,
                        fontSize: 14,
                      ),
                      maxLines: 1,
                      overflow: TextOverflow.ellipsis,
                    ),
                    const SizedBox(height: 4),
                    Text(
                      widget.fileSize,
                      style: TextStyle(
                        color: Colors.grey[600],
                        fontSize: 12,
                      ),
                    ),
                  ],
                ),
              ),
              _buildActionButton(),
            ],
          ),
          const SizedBox(height: 12),
          Stack(
            children: [
              Container(
                height: 8,
                decoration: BoxDecoration(
                  color: Colors.grey[200],
                  borderRadius: BorderRadius.circular(4),
                ),
              ),
              AnimatedBuilder(
                animation: _controller,
                builder: (context, child) {
                  return FractionallySizedBox(
                    widthFactor: widget.progress.clamp(0.0, 1.0),
                    child: Container(
                      height: 8,
                      decoration: BoxDecoration(
                        gradient: LinearGradient(
                          colors: [
                            Colors.blue,
                            Colors.blue.withOpacity(0.7),
                          ],
                        ),
                        borderRadius: BorderRadius.circular(4),
                      ),
                    ),
                  );
                },
              ),
            ],
          ),
          const SizedBox(height: 8),
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                '${(widget.progress * 100).toStringAsFixed(1)}%',
                style: TextStyle(
                  color: Colors.grey[600],
                  fontSize: 12,
                ),
              ),
              Text(
                _getStatusText(),
                style: TextStyle(
                  color: Colors.blue,
                  fontSize: 12,
                ),
              ),
            ],
          ),
        ],
      ),
    );
  }

  Widget _buildActionButton() {
    IconData icon;
    VoidCallback? onTap;

    switch (widget.status) {
      case DownloadStatus.downloading:
        icon = Icons.pause;
        onTap = widget.onPause;
        break;
      case DownloadStatus.paused:
        icon = Icons.play_arrow;
        onTap = widget.onResume;
        break;
      case DownloadStatus.completed:
        icon = Icons.check_circle;
        onTap = null;
        break;
      case DownloadStatus.failed:
        icon = Icons.refresh;
        onTap = widget.onResume;
        break;
    }

    return GestureDetector(
      onTap: onTap,
      child: Container(
        width: 40,
        height: 40,
        decoration: BoxDecoration(
          color: Colors.grey[100],
          shape: BoxShape.circle,
        ),
        child: Icon(icon, color: Colors.blue),
      ),
    );
  }

  String _getStatusText() {
    switch (widget.status) {
      case DownloadStatus.downloading:
        return '正在下载...';
      case DownloadStatus.paused:
        return '已暂停';
      case DownloadStatus.completed:
        return '下载完成';
      case DownloadStatus.failed:
        return '下载失败';
    }
  }
}

enum DownloadStatus {
  downloading,
  paused,
  completed,
  failed,
}

✨ 3.4 闪光进度条

闪光进度条通过动画效果增强视觉吸引力,适合强调重要进度。

dart 复制代码
/// 闪光进度条
class ShimmerProgressPainter extends CustomPainter {
  final double progress;
  final double shimmerPosition;
  final Color progressColor;
  final Color shimmerColor;

  ShimmerProgressPainter({
    required this.progress,
    required this.shimmerPosition,
    this.progressColor = Colors.blue,
    this.shimmerColor = Colors.white,
  });

  @override
  void paint(Canvas canvas, Size size) {
    // 背景轨道
    final bgPaint = Paint()
      ..color = Colors.grey.withOpacity(0.2)
      ..style = PaintingStyle.fill;
    canvas.drawRRect(
      RRect.fromRectAndRadius(
        Rect.fromLTWH(0, 0, size.width, size.height),
        const Radius.circular(4),
      ),
      bgPaint,
    );

    // 进度条
    final progressWidth = size.width * progress.clamp(0.0, 1.0);
    final progressRect = Rect.fromLTWH(0, 0, progressWidth, size.height);

    // 闪光效果
    final shimmerGradient = LinearGradient(
      begin: Alignment.centerLeft,
      end: Alignment.centerRight,
      colors: [
        progressColor,
        shimmerColor.withOpacity(0.5),
        progressColor,
      ],
      stops: [
        (shimmerPosition - 0.2).clamp(0.0, 1.0),
        shimmerPosition.clamp(0.0, 1.0),
        (shimmerPosition + 0.2).clamp(0.0, 1.0),
      ],
    );

    final progressPaint = Paint()
      ..shader = shimmerGradient.createShader(progressRect)
      ..style = PaintingStyle.fill;

    canvas.drawRRect(
      RRect.fromRectAndRadius(
        progressRect,
        const Radius.circular(4),
      ),
      progressPaint,
    );
  }

  @override
  bool shouldRepaint(ShimmerProgressPainter oldDelegate) {
    return progress != oldDelegate.progress ||
        shimmerPosition != oldDelegate.shimmerPosition;
  }
}

/// 动画闪光进度条
class AnimatedShimmerProgress extends StatefulWidget {
  final double progress;
  final double height;
  final Color progressColor;

  const AnimatedShimmerProgress({
    super.key,
    required this.progress,
    this.height = 12,
    this.progressColor = Colors.blue,
  });

  @override
  State<AnimatedShimmerProgress> createState() =>
      _AnimatedShimmerProgressState();
}

class _AnimatedShimmerProgressState extends State<AnimatedShimmerProgress>
    with SingleTickerProviderStateMixin {
  late AnimationController _controller;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(
      duration: const Duration(milliseconds: 1500),
      vsync: this,
    )..repeat();
  }

  @override
  void dispose() {
    _controller.dispose();
    super.dispose();
  }

  @override
  Widget build(BuildContext context) {
    return AnimatedBuilder(
      animation: _controller,
      builder: (context, child) {
        return CustomPaint(
          size: Size.fromHeight(widget.height),
          painter: ShimmerProgressPainter(
            progress: widget.progress,
            shimmerPosition: _controller.value,
            progressColor: widget.progressColor,
          ),
        );
      },
    );
  }
}

四、完整示例:进度指示器系统

下面是一个完整的进度指示器系统示例,整合了所有进度组件:

dart 复制代码
import 'dart:math';
import 'package:flutter/material.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      debugShowCheckedModeBanner: false,
      theme: ThemeData(
        colorScheme: ColorScheme.fromSeed(seedColor: Colors.indigo),
        useMaterial3: true,
      ),
      home: const ProgressHomePage(),
    );
  }
}

class ProgressHomePage extends StatelessWidget {
  const ProgressHomePage({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('📊 高级进度指示器系统')),
      body: ListView(
        padding: const EdgeInsets.all(16),
        children: [
          _buildSectionCard(context, title: '渐变进度条', description: '多彩渐变效果', icon: Icons.gradient, color: Colors.purple, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const GradientProgressDemo()))),
          _buildSectionCard(context, title: '圆形进度', description: '环形进度指示器', icon: Icons.circle, color: Colors.blue, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const CircleProgressDemo()))),
          _buildSectionCard(context, title: '波浪进度', description: '水波动画效果', icon: Icons.water_drop, color: Colors.cyan, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const WaveProgressDemo()))),
          _buildSectionCard(context, title: '分段进度', description: '多段进度展示', icon: Icons.view_module, color: Colors.orange, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const SegmentedProgressDemo()))),
          _buildSectionCard(context, title: '步骤进度', description: '步骤流程指示', icon: Icons.stairs, color: Colors.teal, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const StepProgressDemo()))),
          _buildSectionCard(context, title: '下载进度', description: '文件下载组件', icon: Icons.download, color: Colors.green, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const DownloadProgressDemo()))),
          _buildSectionCard(context, title: '闪光进度', description: '闪光动画效果', icon: Icons.auto_awesome, color: Colors.amber, onTap: () => Navigator.push(context, MaterialPageRoute(builder: (_) => const ShimmerProgressDemo()))),
        ],
      ),
    );
  }

  Widget _buildSectionCard(BuildContext context, {required String title, required String description, required IconData icon, required Color color, required VoidCallback onTap}) {
    return Card(
      margin: const EdgeInsets.only(bottom: 12),
      child: InkWell(
        onTap: onTap,
        borderRadius: BorderRadius.circular(12),
        child: Padding(
          padding: const EdgeInsets.all(16),
          child: Row(
            children: [
              Container(width: 56, height: 56, decoration: BoxDecoration(color: color.withOpacity(0.1), borderRadius: BorderRadius.circular(12)), child: Icon(icon, color: color, size: 28)),
              const SizedBox(width: 16),
              Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(title, style: const TextStyle(fontSize: 16, fontWeight: FontWeight.bold)), const SizedBox(height: 4), Text(description, style: TextStyle(fontSize: 13, color: Colors.grey[600]))])),
              Icon(Icons.chevron_right, color: Colors.grey[400]),
            ],
          ),
        ),
      ),
    );
  }
}

class GradientProgressDemo extends StatefulWidget {
  const GradientProgressDemo({super.key});
  @override
  State<GradientProgressDemo> createState() => _GradientProgressDemoState();
}

class _GradientProgressDemoState extends State<GradientProgressDemo> {
  double _progress = 0.65;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('渐变进度条')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            const Text('水平渐变', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 12),
            GradientLinearProgress(progress: _progress, gradientColors: const [Colors.purple, Colors.blue]),
            const SizedBox(height: 32),
            const Text('彩虹渐变', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 12),
            GradientLinearProgress(progress: _progress, gradientColors: const [Colors.red, Colors.orange, Colors.yellow, Colors.green, Colors.blue, Colors.purple]),
            const SizedBox(height: 32),
            const Text('温度渐变', style: TextStyle(fontWeight: FontWeight.bold)),
            const SizedBox(height: 12),
            GradientLinearProgress(progress: _progress, gradientColors: const [Colors.blue, Colors.cyan, Colors.green, Colors.yellow, Colors.orange, Colors.red]),
            const Spacer(),
            Slider(value: _progress, min: 0, max: 1, divisions: 100, activeColor: Colors.purple, onChanged: (value) => setState(() => _progress = value)),
          ],
        ),
      ),
    );
  }
}

class GradientLinearProgress extends StatelessWidget {
  final double progress;
  final List<Color> gradientColors;
  final double height;

  const GradientLinearProgress({
    super.key,
    required this.progress,
    this.gradientColors = const [Colors.blue, Colors.purple],
    this.height = 12,
  });

  @override
  Widget build(BuildContext context) {
    return LayoutBuilder(
      builder: (context, constraints) {
        return Container(
          height: height,
          decoration: BoxDecoration(color: Colors.grey.withOpacity(0.2), borderRadius: BorderRadius.circular(6)),
          child: Stack(
            children: [
              AnimatedContainer(
                duration: const Duration(milliseconds: 300),
                width: constraints.maxWidth * progress.clamp(0.0, 1.0),
                decoration: BoxDecoration(gradient: LinearGradient(colors: gradientColors), borderRadius: BorderRadius.circular(6)),
              ),
              Center(child: Text('${(progress * 100).toStringAsFixed(0)}%', style: TextStyle(color: progress > 0.5 ? Colors.white : Colors.black54, fontSize: height * 0.7, fontWeight: FontWeight.bold))),
            ],
          ),
        );
      },
    );
  }
}

class CircleProgressDemo extends StatefulWidget {
  const CircleProgressDemo({super.key});
  @override
  State<CircleProgressDemo> createState() => _CircleProgressDemoState();
}

class _CircleProgressDemoState extends State<CircleProgressDemo> with SingleTickerProviderStateMixin {
  double _progress = 0.75;
  late AnimationController _controller;
  late Animation<double> _animation;

  @override
  void initState() {
    super.initState();
    _controller = AnimationController(duration: const Duration(milliseconds: 1500), vsync: this);
    _animation = Tween<double>(begin: 0, end: _progress).animate(CurvedAnimation(parent: _controller, curve: Curves.easeOutCubic))..addListener(() => setState(() => _progress = _animation.value));
    _controller.forward();
  }

  @override
  void dispose() { _controller.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('圆形进度')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            SizedBox(width: 180, height: 180, child: CustomPaint(painter: CircleProgressPainter(progress: _progress, gradientColors: const [Colors.blue, Colors.purple]))),
            const SizedBox(height: 16),
            Text('${(_progress * 100).toStringAsFixed(0)}%', style: const TextStyle(fontSize: 48, fontWeight: FontWeight.bold)),
            const SizedBox(height: 32),
            SizedBox(width: 250, child: Slider(value: _progress, min: 0, max: 1, divisions: 100, activeColor: Colors.blue, onChanged: (value) => setState(() => _progress = value))),
          ],
        ),
      ),
    );
  }
}

class CircleProgressPainter extends CustomPainter {
  final double progress;
  final double strokeWidth;
  final List<Color>? gradientColors;

  CircleProgressPainter({required this.progress, this.strokeWidth = 12, this.gradientColors});

  @override
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = min(size.width, size.height) / 2 - strokeWidth / 2;

    final bgPaint = Paint()..color = Colors.grey.withOpacity(0.2)..style = PaintingStyle.stroke..strokeWidth = strokeWidth..strokeCap = StrokeCap.round;
    canvas.drawCircle(center, radius, bgPaint);

    final progressPaint = Paint()..style = PaintingStyle.stroke..strokeWidth = strokeWidth..strokeCap = StrokeCap.round;
    if (gradientColors != null && gradientColors!.length >= 2) {
      progressPaint.shader = SweepGradient(center: Alignment.center, colors: gradientColors!, transform: const GradientRotation(-pi / 2)).createShader(Rect.fromCircle(center: center, radius: radius));
    } else {
      progressPaint.color = Colors.blue;
    }

    canvas.drawArc(Rect.fromCircle(center: center, radius: radius), -pi / 2, 2 * pi * progress.clamp(0.0, 1.0), false, progressPaint);
  }

  @override
  bool shouldRepaint(CircleProgressPainter oldDelegate) => progress != oldDelegate.progress;
}

class WaveProgressDemo extends StatefulWidget {
  const WaveProgressDemo({super.key});
  @override
  State<WaveProgressDemo> createState() => _WaveProgressDemoState();
}

class _WaveProgressDemoState extends State<WaveProgressDemo> with SingleTickerProviderStateMixin {
  double _progress = 0.6;
  late AnimationController _waveController;

  @override
  void initState() {
    super.initState();
    _waveController = AnimationController(duration: const Duration(seconds: 2), vsync: this)..repeat();
  }

  @override
  void dispose() { _waveController.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('波浪进度')),
      body: Center(
        child: Column(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            AnimatedBuilder(
              animation: _waveController,
              builder: (context, child) {
                return SizedBox(width: 180, height: 180, child: CustomPaint(painter: WaveProgressPainter(progress: _progress, phase: _waveController.value * 2 * pi, waveColor: Colors.blue)));
              },
            ),
            const SizedBox(height: 32),
            SizedBox(width: 250, child: Slider(value: _progress, min: 0, max: 1, divisions: 100, activeColor: Colors.blue, onChanged: (value) => setState(() => _progress = value))),
          ],
        ),
      ),
    );
  }
}

class WaveProgressPainter extends CustomPainter {
  final double progress;
  final double phase;
  final Color waveColor;

  WaveProgressPainter({required this.progress, required this.phase, this.waveColor = Colors.blue});

  @override
  void paint(Canvas canvas, Size size) {
    final waterLevel = size.height * (1 - progress.clamp(0.0, 1.0));
    final center = Offset(size.width / 2, size.height / 2);
    final radius = min(size.width, size.height) / 2;

    final bgPaint = Paint()..color = Colors.grey.withOpacity(0.1)..style = PaintingStyle.fill;
    canvas.drawCircle(center, radius, bgPaint);

    final wavePath = Path()..moveTo(0, size.height)..lineTo(0, waterLevel);
    for (double x = 0; x <= size.width; x++) {
      final y = waterLevel + 8 * sin((x / size.width * 4 * pi) + phase);
      wavePath.lineTo(x, y);
    }
    wavePath.lineTo(size.width, size.height);
    wavePath.close();

    canvas.save();
    canvas.clipPath(Path()..addOval(Rect.fromLTWH(0, 0, size.width, size.height)));
    final wavePaint = Paint()..shader = LinearGradient(begin: Alignment.topCenter, end: Alignment.bottomCenter, colors: [waveColor.withOpacity(0.7), waveColor]).createShader(Rect.fromLTWH(0, 0, size.width, size.height));
    canvas.drawPath(wavePath, wavePaint);
    canvas.restore();

    final borderPaint = Paint()..color = waveColor..style = PaintingStyle.stroke..strokeWidth = 3;
    canvas.drawCircle(center, radius - 1.5, borderPaint);
  }

  @override
  bool shouldRepaint(WaveProgressPainter oldDelegate) => progress != oldDelegate.progress || phase != oldDelegate.phase;
}

class SegmentedProgressDemo extends StatelessWidget {
  const SegmentedProgressDemo({super.key});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('分段进度')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            const Text('项目进度', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
            const SizedBox(height: 16),
            SegmentedProgressBar(segments: const [Segment(weight: 3, color: Colors.green, label: '完成'), Segment(weight: 2, color: Colors.blue, label: '进行'), Segment(weight: 1, color: Colors.orange, label: '待办'), Segment(weight: 1, color: Colors.grey, label: '暂停')]),
            const SizedBox(height: 32),
            const Text('存储空间', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
            const SizedBox(height: 16),
            SegmentedProgressBar(segments: const [Segment(weight: 45, color: Colors.blue), Segment(weight: 30, color: Colors.green), Segment(weight: 15, color: Colors.orange), Segment(weight: 10, color: Colors.grey)]),
          ],
        ),
      ),
    );
  }
}

class SegmentedProgressBar extends StatelessWidget {
  final List<Segment> segments;
  final double height;

  const SegmentedProgressBar({super.key, required this.segments, this.height = 24});

  @override
  Widget build(BuildContext context) {
    return CustomPaint(size: Size.fromHeight(height), painter: SegmentedProgressPainter(segments: segments));
  }
}

class SegmentedProgressPainter extends CustomPainter {
  final List<Segment> segments;

  SegmentedProgressPainter({required this.segments});

  @override
  void paint(Canvas canvas, Size size) {
    final totalWeight = segments.fold<double>(0, (sum, s) => sum + s.weight);
    double currentX = 0;
    const spacing = 4;

    for (final segment in segments) {
      final segmentWidth = (size.width - spacing * (segments.length - 1)) * segment.weight / totalWeight;
      final rect = RRect.fromRectAndRadius(Rect.fromLTWH(currentX, 0, segmentWidth, size.height), const Radius.circular(8));
      canvas.drawRRect(rect, Paint()..color = segment.color);
      currentX += segmentWidth + spacing;
    }
  }

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

class Segment {
  final double weight;
  final Color color;
  final String? label;
  const Segment({required this.weight, required this.color, this.label});
}

class StepProgressDemo extends StatefulWidget {
  const StepProgressDemo({super.key});
  @override
  State<StepProgressDemo> createState() => _StepProgressDemoState();
}

class _StepProgressDemoState extends State<StepProgressDemo> {
  int _currentStep = 2;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('步骤进度')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            StepProgressIndicator(totalSteps: 4, currentStep: _currentStep, stepLabels: const ['购物车', '地址', '支付', '完成']),
            const SizedBox(height: 32),
            Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
              ElevatedButton(onPressed: _currentStep > 1 ? () => setState(() => _currentStep--) : null, child: const Text('上一步')),
              ElevatedButton(onPressed: _currentStep < 4 ? () => setState(() => _currentStep++) : null, child: const Text('下一步')),
            ]),
          ],
        ),
      ),
    );
  }
}

class StepProgressIndicator extends StatelessWidget {
  final int totalSteps;
  final int currentStep;
  final List<String>? stepLabels;

  const StepProgressIndicator({super.key, required this.totalSteps, required this.currentStep, this.stepLabels});

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        Row(children: List.generate(totalSteps, (index) {
          final isActive = index < currentStep;
          return Expanded(child: Row(children: [
            Container(width: 32, height: 32, decoration: BoxDecoration(shape: BoxShape.circle, color: isActive ? Colors.blue : Colors.grey.withOpacity(0.2), border: Border.all(color: isActive ? Colors.blue : Colors.grey, width: 2)), child: Center(child: isActive ? const Icon(Icons.check, color: Colors.white, size: 18) : Text('${index + 1}', style: const TextStyle(color: Colors.grey, fontWeight: FontWeight.bold)))),
            if (index < totalSteps - 1) Expanded(child: Container(height: 3, color: index < currentStep - 1 ? Colors.blue : Colors.grey.withOpacity(0.3))),
          ]));
        })),
        if (stepLabels != null) ...[
          const SizedBox(height: 8),
          Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: stepLabels!.map((label) => SizedBox(width: 32, child: Text(label, textAlign: TextAlign.center, style: TextStyle(fontSize: 12, color: stepLabels!.indexOf(label) < currentStep ? Colors.blue : Colors.grey)))).toList()),
        ],
      ],
    );
  }
}

class DownloadProgressDemo extends StatefulWidget {
  const DownloadProgressDemo({super.key});
  @override
  State<DownloadProgressDemo> createState() => _DownloadProgressDemoState();
}

class _DownloadProgressDemoState extends State<DownloadProgressDemo> {
  double _progress = 0.45;
  DownloadStatus _status = DownloadStatus.downloading;

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('下载进度')),
      body: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          children: [
            DownloadProgressBar(progress: _progress, fileName: 'flutter_sdk_v3.19.0.zip', fileSize: '1.2 GB', status: _status, onPause: () => setState(() => _status = DownloadStatus.paused), onResume: () => setState(() => _status = DownloadStatus.downloading)),
            const SizedBox(height: 24),
            Row(mainAxisAlignment: MainAxisAlignment.spaceEvenly, children: [
              ElevatedButton(onPressed: () => setState(() => _progress = (_progress + 0.1).clamp(0.0, 1.0)), child: const Text('增加进度')),
              ElevatedButton(onPressed: () => setState(() => _progress = (_progress - 0.1).clamp(0.0, 1.0)), child: const Text('减少进度')),
            ]),
          ],
        ),
      ),
    );
  }
}

class DownloadProgressBar extends StatelessWidget {
  final double progress;
  final String fileName;
  final String fileSize;
  final DownloadStatus status;
  final VoidCallback? onPause;
  final VoidCallback? onResume;

  const DownloadProgressBar({super.key, required this.progress, required this.fileName, required this.fileSize, required this.status, this.onPause, this.onResume});

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      decoration: BoxDecoration(color: Colors.white, borderRadius: BorderRadius.circular(12), boxShadow: [BoxShadow(color: Colors.black.withOpacity(0.1), blurRadius: 10)]),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(children: [
            Container(width: 48, height: 48, decoration: BoxDecoration(color: Colors.blue.withOpacity(0.1), borderRadius: BorderRadius.circular(8)), child: const Icon(Icons.insert_drive_file, color: Colors.blue)),
            const SizedBox(width: 12),
            Expanded(child: Column(crossAxisAlignment: CrossAxisAlignment.start, children: [Text(fileName, style: const TextStyle(fontWeight: FontWeight.bold, fontSize: 14), maxLines: 1, overflow: TextOverflow.ellipsis), const SizedBox(height: 4), Text(fileSize, style: TextStyle(color: Colors.grey[600], fontSize: 12))])),
            GestureDetector(onTap: status == DownloadStatus.downloading ? onPause : onResume, child: Container(width: 40, height: 40, decoration: BoxDecoration(color: Colors.grey[100], shape: BoxShape.circle), child: Icon(status == DownloadStatus.downloading ? Icons.pause : Icons.play_arrow, color: Colors.blue))),
          ]),
          const SizedBox(height: 12),
          Stack(children: [Container(height: 8, decoration: BoxDecoration(color: Colors.grey[200], borderRadius: BorderRadius.circular(4))), FractionallySizedBox(widthFactor: progress.clamp(0.0, 1.0), child: Container(height: 8, decoration: BoxDecoration(gradient: const LinearGradient(colors: [Colors.blue, Colors.lightBlue]), borderRadius: BorderRadius.circular(4))))]),
          const SizedBox(height: 8),
          Row(mainAxisAlignment: MainAxisAlignment.spaceBetween, children: [Text('${(progress * 100).toStringAsFixed(1)}%', style: TextStyle(color: Colors.grey[600], fontSize: 12)), Text(status == DownloadStatus.downloading ? '正在下载...' : '已暂停', style: const TextStyle(color: Colors.blue, fontSize: 12))]),
        ],
      ),
    );
  }
}

enum DownloadStatus { downloading, paused, completed, failed }

class ShimmerProgressDemo extends StatefulWidget {
  const ShimmerProgressDemo({super.key});
  @override
  State<ShimmerProgressDemo> createState() => _ShimmerProgressDemoState();
}

class _ShimmerProgressDemoState extends State<ShimmerProgressDemo> with SingleTickerProviderStateMixin {
  double _progress = 0.7;
  late AnimationController _shimmerController;

  @override
  void initState() {
    super.initState();
    _shimmerController = AnimationController(duration: const Duration(milliseconds: 1500), vsync: this)..repeat();
  }

  @override
  void dispose() { _shimmerController.dispose(); super.dispose(); }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(title: const Text('闪光进度')),
      body: Padding(
        padding: const EdgeInsets.all(24),
        child: Column(
          children: [
            const Text('闪光效果进度条', style: TextStyle(fontWeight: FontWeight.bold, fontSize: 16)),
            const SizedBox(height: 24),
            AnimatedBuilder(
              animation: _shimmerController,
              builder: (context, child) {
                return CustomPaint(size: const Size.fromHeight(16), painter: ShimmerProgressPainter(progress: _progress, shimmerPosition: _shimmerController.value));
              },
            ),
            const Spacer(),
            Slider(value: _progress, min: 0, max: 1, divisions: 100, activeColor: Colors.amber, onChanged: (value) => setState(() => _progress = value)),
          ],
        ),
      ),
    );
  }
}

class ShimmerProgressPainter extends CustomPainter {
  final double progress;
  final double shimmerPosition;

  ShimmerProgressPainter({required this.progress, required this.shimmerPosition});

  @override
  void paint(Canvas canvas, Size size) {
    canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(0, 0, size.width, size.height), const Radius.circular(8)), Paint()..color = Colors.grey.withOpacity(0.2));

    final progressWidth = size.width * progress.clamp(0.0, 1.0);
    final shimmerGradient = LinearGradient(colors: [Colors.amber, Colors.white.withOpacity(0.5), Colors.amber], stops: [(shimmerPosition - 0.2).clamp(0.0, 1.0), shimmerPosition.clamp(0.0, 1.0), (shimmerPosition + 0.2).clamp(0.0, 1.0)]);
    canvas.drawRRect(RRect.fromRectAndRadius(Rect.fromLTWH(0, 0, progressWidth, size.height), const Radius.circular(8)), Paint()..shader = shimmerGradient.createShader(Rect.fromLTWH(0, 0, progressWidth, size.height)));
  }

  @override
  bool shouldRepaint(ShimmerProgressPainter oldDelegate) => progress != oldDelegate.progress || shimmerPosition != oldDelegate.shimmerPosition;
}

五、最佳实践与性能优化

🎨 5.1 性能优化建议

  1. 使用 const 构造函数:对于不变的组件使用 const 构造函数
  2. 避免过度重绘:在 shouldRepaint 中精确判断是否需要重绘
  3. 合理使用动画:避免创建过多的 AnimationController
  4. 状态管理优化:使用 ValueNotifier 替代 setState 进行局部更新

🔧 5.2 无障碍支持

dart 复制代码
Semantics(
  label: '下载进度 ${progress * 100}%',
  value: progress.toStringAsFixed(2),
  child: CustomPaint(...),
)

📱 5.3 适配不同平台

在 OpenHarmony 平台上,需要注意:

  • 使用平台原生字体
  • 遵循平台设计规范
  • 处理不同屏幕密度

六、总结

本文详细介绍了 Flutter for OpenHarmony 的高级进度指示器系统,包括:

组件类型 核心技术 应用场景
渐变进度条 LinearGradient 通用进度展示
圆形进度 CustomPainter + SweepGradient 按钮内进度
波浪进度 Path + 动画控制器 液体/容量展示
分段进度 多段绘制 项目/存储分布
步骤进度 状态管理 表单/向导流程
下载进度 状态机 + 动画 文件下载
闪光进度 Shader + 动画 强调/吸引注意

参考资料


💡 提示:进度指示器是用户反馈系统的重要组成部分,合理使用可以显著提升用户体验。建议根据具体场景选择合适的进度类型,并注意动画性能优化。

相关推荐
Hello.Reader2 小时前
Flame_3D把 3D 带进 Flutter 游戏引擎 Flame 的实验性方案(上手、Shader、踩坑与选型)
flutter·flame_3d
lili-felicity2 小时前
进阶实战 Flutter for OpenHarmony:TabBar 高级标签系统 - 导航交互优化实现
flutter
阿林来了2 小时前
Flutter三方库适配OpenHarmony【flutter_speech】— 生产环境部署与发布
flutter
lili-felicity3 小时前
进阶实战 Flutter for OpenHarmony:Hero 动画转场系统 - 页面过渡动画实现
flutter
2601_949593653 小时前
进阶实战 Flutter for OpenHarmony:ValueNotifier 组件实战 - 轻量级状态管理系统
flutter
忙碌5443 小时前
2026年Flutter 3.16全栈实战:从UI到后端的一体化开发革命
flutter·ui
九狼JIULANG4 小时前
Flutter Riverpod + MVI 状态管理实现的提示词优化器
flutter
lili-felicity4 小时前
进阶实战 Flutter for OpenHarmony:NestedScrollView 嵌套滚动系统 - 复杂滚动交互实现
flutter
lili-felicity4 小时前
进阶实战 Flutter for OpenHarmony:PageView 无限轮播系统 - 轮播交互优化实现
flutter·交互