第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 小时前
第3章:基础组件 —— 3.3 图片及ICON
flutter
GISer_Jing4 小时前
跨端框架对决:React Native vs Flutter深度对比
flutter·react native·react.js
猪哥帅过吴彦祖8 小时前
Flutter 从入门到精通:深入 Navigator 2.0 - GoRouter 路由完全指南
android·flutter·ios
恋猫de小郭10 小时前
来了解一下,为什么你的 Flutter WebView 在 iOS 26 上有点击问题?
android·前端·flutter
你听得到111 天前
肝了半个月,我用 Flutter 写了个功能强大的图片编辑器,告别image_cropper
android·前端·flutter
旧时光_1 天前
第3章:基础组件 —— 3.2 按钮
flutter
旧时光_1 天前
第3章:基础组件 —— 3.1 文本及样式
flutter
旧时光_1 天前
第2章:第一个Flutter应用 —— 2.8 Flutter异常捕获
flutter
猪哥帅过吴彦祖1 天前
Flutter 系列教程:应用导航 - Navigator 1.0 与命名路由
android·flutter·ios