3.6 进度指示器
📚 章节概览
进度指示器用于显示任务的进度状态。Flutter 的 Material 库提供了两种进度指示器,本章节将学习:
- LinearProgressIndicator - 线性进度条
- CircularProgressIndicator - 圆形进度条
- 确定进度 - 显示具体进度值
- 不确定进度 - 循环动画
- 自定义尺寸 - 控制进度条大小
- 进度色动画 - 颜色渐变效果
🎯 核心知识点
进度指示器的两种模式
-
确定进度(Determinate):进度可以计算和预估
- 用于:文件下载、数据加载、任务完成度
- 特征:显示具体的进度百分比
-
不确定进度(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,
);
}
}
关键点
- SingleTickerProviderStateMixin:提供动画帧计时器
- AnimationController:控制动画的执行
- ColorTween:定义颜色的起始和结束值
- 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: 检查以下几点:
- 忘记 setState :更新进度值时必须调用
setState
dart
// ❌ 错误:没有调用 setState
void _updateProgress(double value) {
_progress = value;
}
// ✅ 正确:调用 setState
void _updateProgress(double value) {
setState(() {
_progress = value;
});
}
- value 没有变化:确保 value 的值确实在改变
dart
// 检查值是否在变化
print('Progress: $_progress');
- value 超出范围:value 必须在 [0.0, 1.0] 范围内
dart
// ❌ 错误:超出范围
LinearProgressIndicator(value: 50) // 应该是 0.5,不是 50
// ✅ 正确
LinearProgressIndicator(value: 50 / 100) // 0.5
🎯 跟着做练习
练习1:实现一个文件上传组件
目标: 创建文件上传UI,显示上传进度
步骤:
- 显示文件图标和文件名
- 显示线性进度条
- 显示上传百分比
- 上传完成后显示成功提示
💡 查看答案
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:实现一个技能等级展示组件
目标: 使用进度条展示多项技能的等级
步骤:
- 创建技能列表
- 为每个技能显示名称和进度条
- 不同等级使用不同颜色
💡 查看答案
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 | 循环动画 | 不可预估的任务 |
记忆技巧
- value = null:不知道进度 → 循环动画
- value ∈ [0, 1]:知道进度 → 显示具体值
- AlwaysStoppedAnimation:固定颜色不需要动画
- SizedBox 包裹:控制进度条尺寸
🔗 相关资源
- LinearProgressIndicator 官方文档
- CircularProgressIndicator 官方文档
- flutter_spinkit - 多种风格的进度指示器
- percent_indicator - 带百分比的进度指示器
🎉 恭喜完成第3章!