第3章:基础组件 —— 3.6 进度指示器

3.6 进度指示器

📚 章节概览

进度指示器用于显示任务的进度状态。Flutter 的 Material 库提供了两种进度指示器,本章节将学习:

  • LinearProgressIndicator - 线性进度条
  • CircularProgressIndicator - 圆形进度条
  • 确定进度 - 显示具体进度值
  • 不确定进度 - 循环动画
  • 自定义尺寸 - 控制进度条大小
  • 进度色动画 - 颜色渐变效果

🎯 核心知识点

进度指示器的两种模式

  1. 确定进度(Determinate):进度可以计算和预估

    • 用于:文件下载、数据加载、任务完成度
    • 特征:显示具体的进度百分比
  2. 不确定进度(Indeterminate):进度无法准确获得

    • 用于:下拉刷新、数据提交、等待响应
    • 特征:循环动画,不显示具体进度

1️⃣ LinearProgressIndicator

LinearProgressIndicator 是线性、条状的进度条。

构造函数

dart 复制代码
LinearProgressIndicator({
  double? value,                    // 进度值 [0.0, 1.0]
  Color? backgroundColor,           // 背景色
  Animation<Color?>? valueColor,    // 进度条颜色
  String? semanticsLabel,           // 语义标签
  String? semanticsValue,          // 语义值
})

属性说明

属性 类型 说明
value double? 进度值,null=不确定进度,0.0-1.0=确定进度
backgroundColor Color? 背景色
valueColor Animation<Color?>? 进度条颜色(支持动画)

基本用法

dart 复制代码
// 不确定进度(循环动画)
LinearProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
)

// 确定进度(50%)
LinearProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
  value: 0.5,  // 50%
)

AlwaysStoppedAnimation

valueColor 的类型是 Animation<Color?>,如果不需要动画效果,可以使用 AlwaysStoppedAnimation 来指定固定颜色:

dart 复制代码
// 固定颜色
valueColor: AlwaysStoppedAnimation(Colors.blue)

// 相当于创建一个始终停止的动画,颜色固定为蓝色

2️⃣ CircularProgressIndicator

CircularProgressIndicator 是圆形进度条。

构造函数

dart 复制代码
CircularProgressIndicator({
  double? value,                    // 进度值 [0.0, 1.0]
  Color? backgroundColor,           // 背景色
  Animation<Color?>? valueColor,    // 进度条颜色
  double strokeWidth = 4.0,         // 线条粗细
  String? semanticsLabel,           // 语义标签
  String? semanticsValue,          // 语义值
})

额外属性

属性 类型 默认值 说明
strokeWidth double 4.0 圆形进度条的粗细

基本用法

dart 复制代码
// 不确定进度(旋转动画)
CircularProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
)

// 确定进度(50%,显示半圆)
CircularProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
  value: 0.5,
)

// 自定义粗细
CircularProgressIndicator(
  backgroundColor: Colors.grey[200],
  valueColor: AlwaysStoppedAnimation(Colors.blue),
  value: 0.7,
  strokeWidth: 8.0,  // 更粗
)

3️⃣ 自定义尺寸

进度指示器本身不提供尺寸参数,它们会继承父容器的尺寸。

可以使用 SizedBox 或 ConstrainedBox 来控制尺寸。

LinearProgressIndicator 尺寸

dart 复制代码
// 高度为 2
SizedBox(
  height: 2,
  child: LinearProgressIndicator(
    backgroundColor: Colors.grey[200],
    valueColor: AlwaysStoppedAnimation(Colors.blue),
    value: 0.5,
  ),
)

// 高度为 10
SizedBox(
  height: 10,
  child: LinearProgressIndicator(
    backgroundColor: Colors.grey[200],
    valueColor: AlwaysStoppedAnimation(Colors.blue),
    value: 0.5,
  ),
)

CircularProgressIndicator 尺寸

dart 复制代码
// 直径为 30
SizedBox(
  height: 30,
  width: 30,
  child: CircularProgressIndicator(
    backgroundColor: Colors.grey[200],
    valueColor: AlwaysStoppedAnimation(Colors.blue),
    value: 0.7,
    strokeWidth: 3,
  ),
)

// 直径为 100
SizedBox(
  height: 100,
  width: 100,
  child: CircularProgressIndicator(
    backgroundColor: Colors.grey[200],
    valueColor: AlwaysStoppedAnimation(Colors.blue),
    value: 0.7,
  ),
)

椭圆形进度条

如果 CircularProgressIndicator 的宽高不相等,会显示为椭圆形:

dart 复制代码
SizedBox(
  height: 60,
  width: 100,  // 宽度大于高度
  child: CircularProgressIndicator(
    backgroundColor: Colors.grey[200],
    valueColor: AlwaysStoppedAnimation(Colors.purple),
    value: 0.7,
  ),
)

4️⃣ 进度色动画

通过 Animation<Color> 实现颜色渐变效果。

完整示例

dart 复制代码
class ProgressColorAnimation extends StatefulWidget {
  @override
  _ProgressColorAnimationState createState() => _ProgressColorAnimationState();
}

class _ProgressColorAnimationState extends State<ProgressColorAnimation>
    with SingleTickerProviderStateMixin {
  late AnimationController _animationController;

  @override
  void initState() {
    super.initState();
    // 动画执行时间 3 秒
    _animationController = AnimationController(
      vsync: this,
      duration: Duration(seconds: 3),
    );
    _animationController.forward();
    _animationController.addListener(() => setState(() {}));
  }

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

  @override
  Widget build(BuildContext context) {
    return LinearProgressIndicator(
      backgroundColor: Colors.grey[200],
      // 颜色从灰色渐变到蓝色
      valueColor: ColorTween(
        begin: Colors.grey,
        end: Colors.blue,
      ).animate(_animationController),
      // 进度从 0% 到 100%
      value: _animationController.value,
    );
  }
}

关键点

  1. SingleTickerProviderStateMixin:提供动画帧计时器
  2. AnimationController:控制动画的执行
  3. ColorTween:定义颜色的起始和结束值
  4. animate():将 Tween 绑定到 AnimationController

💡 最佳实践

1. 选择合适的进度类型

dart 复制代码
// ✅ 文件下载 - 使用确定进度
double _downloadProgress = 0.7;
LinearProgressIndicator(
  value: _downloadProgress,
)

// ✅ 下拉刷新 - 使用不确定进度
CircularProgressIndicator()  // value = null

2. 添加文字说明

dart 复制代码
Column(
  children: [
    LinearProgressIndicator(value: 0.65),
    SizedBox(height: 8),
    Text('65%'),  // 显示具体进度
  ],
)

3. 加载状态的完整示例

dart 复制代码
class LoadingWidget extends StatelessWidget {
  final bool isLoading;
  final Widget child;

  const LoadingWidget({
    required this.isLoading,
    required this.child,
  });

  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        child,
        if (isLoading)
          Container(
            color: Colors.black.withOpacity(0.3),
            child: Center(
              child: CircularProgressIndicator(),
            ),
          ),
      ],
    );
  }
}

// 使用
LoadingWidget(
  isLoading: _isLoading,
  child: YourContent(),
)

4. 按钮中的加载状态

dart 复制代码
ElevatedButton(
  onPressed: _isLoading ? null : _handleSubmit,
  child: _isLoading
      ? SizedBox(
          height: 20,
          width: 20,
          child: CircularProgressIndicator(
            strokeWidth: 2,
            valueColor: AlwaysStoppedAnimation(Colors.white),
          ),
        )
      : Text('提交'),
)

🤔 常见问题(FAQ)

Q1: 如何实现环形进度条(带百分比文字)?

A: 使用 Stack 叠加显示:

dart 复制代码
class CircularPercentIndicator extends StatelessWidget {
  final double percent;

  const CircularPercentIndicator({required this.percent});

  @override
  Widget build(BuildContext context) {
    return SizedBox(
      height: 100,
      width: 100,
      child: Stack(
        alignment: Alignment.center,
        children: [
          CircularProgressIndicator(
            backgroundColor: Colors.grey[200],
            valueColor: AlwaysStoppedAnimation(Colors.blue),
            value: percent,
            strokeWidth: 8,
          ),
          Text(
            '${(percent * 100).toInt()}%',
            style: TextStyle(
              fontSize: 20,
              fontWeight: FontWeight.bold,
            ),
          ),
        ],
      ),
    );
  }
}

// 使用
CircularPercentIndicator(percent: 0.65)

Q2: 如何改变进度条的颜色?

A: 使用 valueColor 属性:

dart 复制代码
// 方法1:固定颜色
LinearProgressIndicator(
  valueColor: AlwaysStoppedAnimation(Colors.red),
)

// 方法2:根据进度改变颜色
LinearProgressIndicator(
  valueColor: AlwaysStoppedAnimation(
    _progress < 0.5 ? Colors.orange : Colors.green,
  ),
  value: _progress,
)

// 方法3:动画颜色
valueColor: ColorTween(
  begin: Colors.red,
  end: Colors.green,
).animate(_animationController)

Q3: 如何实现多段进度条(不同颜色的分段)?

A: 使用 Stack 叠加多个进度条:

dart 复制代码
class SegmentedProgress extends StatelessWidget {
  @override
  Widget build(BuildContext context) {
    return Stack(
      children: [
        // 底层:总背景
        LinearProgressIndicator(
          backgroundColor: Colors.grey[200],
          valueColor: AlwaysStoppedAnimation(Colors.transparent),
          value: 1.0,
        ),
        // 第一段:0-30% 绿色
        LinearProgressIndicator(
          backgroundColor: Colors.transparent,
          valueColor: AlwaysStoppedAnimation(Colors.green),
          value: 0.3,
        ),
        // 第二段:30-60% 黄色
        ClipRect(
          child: Align(
            alignment: Alignment.centerLeft,
            widthFactor: 0.6,
            child: LinearProgressIndicator(
              backgroundColor: Colors.transparent,
              valueColor: AlwaysStoppedAnimation(Colors.yellow),
              value: 1.0,
            ),
          ),
        ),
      ],
    );
  }
}

Q4: 如何实现下载进度条?

A: 完整示例:

dart 复制代码
class DownloadProgress extends StatefulWidget {
  @override
  _DownloadProgressState createState() => _DownloadProgressState();
}

class _DownloadProgressState extends State<DownloadProgress> {
  double _progress = 0.0;
  bool _isDownloading = false;

  Future<void> _startDownload() async {
    setState(() {
      _isDownloading = true;
      _progress = 0.0;
    });

    // 模拟下载
    for (int i = 0; i <= 100; i++) {
      await Future.delayed(Duration(milliseconds: 50));
      if (!mounted) return;
      setState(() {
        _progress = i / 100;
      });
    }

    setState(() {
      _isDownloading = false;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Column(
      children: [
        LinearProgressIndicator(
          backgroundColor: Colors.grey[200],
          valueColor: AlwaysStoppedAnimation(
            _progress == 1.0 ? Colors.green : Colors.blue,
          ),
          value: _progress,
        ),
        SizedBox(height: 8),
        Text('${(_progress * 100).toStringAsFixed(0)}%'),
        SizedBox(height: 16),
        ElevatedButton(
          onPressed: _isDownloading ? null : _startDownload,
          child: Text(_isDownloading ? '下载中...' : '开始下载'),
        ),
      ],
    );
  }
}

Q5: 为什么我的进度条不动?

A: 检查以下几点:

  1. 忘记 setState :更新进度值时必须调用 setState
dart 复制代码
// ❌ 错误:没有调用 setState
void _updateProgress(double value) {
  _progress = value;
}

// ✅ 正确:调用 setState
void _updateProgress(double value) {
  setState(() {
    _progress = value;
  });
}
  1. value 没有变化:确保 value 的值确实在改变
dart 复制代码
// 检查值是否在变化
print('Progress: $_progress');
  1. value 超出范围:value 必须在 0.0, 1.0 范围内
dart 复制代码
// ❌ 错误:超出范围
LinearProgressIndicator(value: 50)  // 应该是 0.5,不是 50

// ✅ 正确
LinearProgressIndicator(value: 50 / 100)  // 0.5

🎯 跟着做练习

练习1:实现一个文件上传组件

目标: 创建文件上传UI,显示上传进度

步骤:

  1. 显示文件图标和文件名
  2. 显示线性进度条
  3. 显示上传百分比
  4. 上传完成后显示成功提示

💡 查看答案

dart 复制代码
class FileUploadWidget extends StatefulWidget {
  const FileUploadWidget({super.key});

  @override
  State<FileUploadWidget> createState() => _FileUploadWidgetState();
}

class _FileUploadWidgetState extends State<FileUploadWidget> {
  double _uploadProgress = 0.0;
  bool _isUploading = false;
  bool _uploadComplete = false;

  Future<void> _startUpload() async {
    setState(() {
      _isUploading = true;
      _uploadProgress = 0.0;
      _uploadComplete = false;
    });

    // 模拟上传
    for (int i = 0; i <= 100; i++) {
      await Future.delayed(const Duration(milliseconds: 30));
      if (!mounted) return;
      setState(() {
        _uploadProgress = i / 100;
      });
    }

    setState(() {
      _isUploading = false;
      _uploadComplete = true;
    });
  }

  @override
  Widget build(BuildContext context) {
    return Card(
      child: Padding(
        padding: const EdgeInsets.all(16),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Row(
              children: [
                Icon(
                  _uploadComplete ? Icons.check_circle : Icons.insert_drive_file,
                  size: 40,
                  color: _uploadComplete ? Colors.green : Colors.blue,
                ),
                const SizedBox(width: 16),
                Expanded(
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      const Text(
                        'document.pdf',
                        style: TextStyle(fontWeight: FontWeight.bold),
                      ),
                      const SizedBox(height: 4),
                      Text(
                        _uploadComplete
                            ? '上传完成'
                            : _isUploading
                                ? '正在上传...'
                                : '准备上传',
                        style: const TextStyle(fontSize: 12, color: Colors.grey),
                      ),
                    ],
                  ),
                ),
                if (_uploadComplete)
                  const Icon(Icons.done, color: Colors.green),
              ],
            ),
            const SizedBox(height: 12),
            LinearProgressIndicator(
              backgroundColor: Colors.grey[200],
              valueColor: AlwaysStoppedAnimation(
                _uploadComplete ? Colors.green : Colors.blue,
              ),
              value: _uploadProgress,
            ),
            const SizedBox(height: 8),
            Text(
              '${(_uploadProgress * 100).toStringAsFixed(0)}%',
              style: const TextStyle(fontSize: 12, color: Colors.grey),
            ),
            const SizedBox(height: 12),
            if (!_uploadComplete)
              SizedBox(
                width: double.infinity,
                child: ElevatedButton(
                  onPressed: _isUploading ? null : _startUpload,
                  child: Text(_isUploading ? '上传中...' : '开始上传'),
                ),
              ),
          ],
        ),
      ),
    );
  }
}

练习2:实现一个技能等级展示组件

目标: 使用进度条展示多项技能的等级

步骤:

  1. 创建技能列表
  2. 为每个技能显示名称和进度条
  3. 不同等级使用不同颜色

💡 查看答案

dart 复制代码
class SkillLevel extends StatelessWidget {
  final String skillName;
  final double level; // 0.0 - 1.0

  const SkillLevel({
    super.key,
    required this.skillName,
    required this.level,
  });

  Color _getColor() {
    if (level >= 0.8) return Colors.green;
    if (level >= 0.5) return Colors.orange;
    return Colors.red;
  }

  String _getLevelText() {
    if (level >= 0.8) return '精通';
    if (level >= 0.5) return '熟悉';
    return '了解';
  }

  @override
  Widget build(BuildContext context) {
    return Padding(
      padding: const EdgeInsets.symmetric(vertical: 8),
      child: Column(
        crossAxisAlignment: CrossAxisAlignment.start,
        children: [
          Row(
            mainAxisAlignment: MainAxisAlignment.spaceBetween,
            children: [
              Text(
                skillName,
                style: const TextStyle(
                  fontSize: 14,
                  fontWeight: FontWeight.bold,
                ),
              ),
              Text(
                _getLevelText(),
                style: TextStyle(
                  fontSize: 12,
                  color: _getColor(),
                  fontWeight: FontWeight.bold,
                ),
              ),
            ],
          ),
          const SizedBox(height: 4),
          LinearProgressIndicator(
            backgroundColor: Colors.grey[200],
            valueColor: AlwaysStoppedAnimation(_getColor()),
            value: level,
          ),
        ],
      ),
    );
  }
}

// 使用示例
class SkillsPage extends StatelessWidget {
  const SkillsPage({super.key});

  @override
  Widget build(BuildContext context) {
    return ListView(
      padding: const EdgeInsets.all(16),
      children: const [
        SkillLevel(skillName: 'Flutter', level: 0.9),
        SkillLevel(skillName: 'Dart', level: 0.85),
        SkillLevel(skillName: 'React', level: 0.7),
        SkillLevel(skillName: 'Vue', level: 0.6),
        SkillLevel(skillName: 'Node.js', level: 0.5),
      ],
    );
  }
}

📋 小结

核心要点

组件 特点 适用场景
LinearProgressIndicator 线性、条状 下载、加载、任务进度
CircularProgressIndicator 圆形、旋转 加载中、等待响应

进度模式

模式 value 值 表现 使用场景
确定进度 0.0 - 1.0 静止或增长 可预估的任务
不确定进度 null 循环动画 不可预估的任务

记忆技巧

  1. value = null:不知道进度 → 循环动画
  2. value ∈ 0, 1:知道进度 → 显示具体值
  3. AlwaysStoppedAnimation:固定颜色不需要动画
  4. SizedBox 包裹:控制进度条尺寸

🔗 相关资源


🎉 恭喜完成第3章!

相关推荐
Geek_Vison10 小时前
保险APP已经用了Flutter,还能接入小程序吗?
flutter·小程序·uni-app·mpaas·小程序容器
FungLeo10 小时前
成为全栈·Flutter App 篇·go_router 与四 Tab App Shell:保留导航状态、深链和登录回跳
flutter·移动开发·成为全栈·go_router·路由设计·深链接
m0_7381858210 小时前
Flutter 鸿蒙化实战:screen_protector 适配 OpenHarmony,防截屏/防泄露
flutter·华为·harmonyos·鸿蒙
梦想的颜色16 小时前
【编程实战】AI 时代 APP 开发全栈硬核指南:技术选型 + AI 架构 + 模型落地全维度决策
python·flutter·react native·react.js·ai·桌面应用·milvus
yuanlaile1 天前
2026年全新Flutter 跨平台开发全栈学习资料整理
flutter·getx·鸿蒙 next·flutter开发鸿蒙·flutter入门实战·flutter全栈
恋猫de小郭2 天前
Genkit Dart 1.0 发布,Flutter 原生的 AI Agent 终于完整了
android·前端·flutter
第10086个小白2 天前
flutter桌面端打包方式(Inno Setup Compiler)版本
前端·flutter
陆断枫3 天前
Flutter 3.47 动画引擎盘点:Impeller 全平台,和那些"新特性"
flutter
程序员老刘3 天前
Flet 1.0 思路很好,可惜来晚了
flutter·ai编程·客户端
m0_738185823 天前
Flutter 鸿蒙化实战:qr_code_scanner_plus 适配 OpenHarmony,二维码扫描
flutter·华为·harmonyos·鸿蒙