第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️⃣ 自定义尺寸

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

可以使用 SizedBoxConstrainedBox 来控制尺寸。

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章!

相关推荐
于慨3 小时前
flutter基础组件用法
开发语言·javascript·flutter
恋猫de小郭5 小时前
Android CLI ,谷歌为 Android 开发者专研的 AI Agent,提速三倍
android·前端·flutter
火柴就是我6 小时前
flutter pushAndRemoveUntil 的一次小疑惑
flutter
于慨6 小时前
flutter doctor问题解决
flutter
唔666 小时前
flutter 图片加载类 图片的安全使用
安全·flutter
Nathan202406167 小时前
Flutter - InheritedWidget
flutter·dart
恋猫de小郭8 小时前
JetBrains Amper 0.10 ,期待它未来替代 Gradle
android·前端·flutter
Lanren的编程日记9 小时前
Flutter鸿蒙应用开发:实时聊天功能集成实战
flutter·华为·harmonyos
Utopia^18 小时前
鸿蒙flutter第三方库适配 - 联系人备份工具
flutter·华为·harmonyos
念格1 天前
Flutter 仿微信输入框最佳实践:自适应高度 + 超行数智能切换全屏
前端·flutter