Flutter打造OpenHarmony进度条
Flutter进度条组件应用摘要 本文详细介绍了Flutter中LinearProgressIndicator和CircularProgressIndicator两种进度条组件的使用方法。主要内容包括: 基础实现:展示了最简单的线性/圆形进度条实现方式 自定义样式:通过参数调整进度条颜色、高度、宽度等视觉属性 高级应用:包括百分比显示、多任务进度列表、缓冲进度条等场景 企业级特性:探讨了响应式设计
案例概述
本案例展示如何使用线性进度条和圆形进度条来展示任务进度。进度条是展示任务进度的重要组件,在现代应用中广泛应用于文件下载、数据加载、表单提交、数据处理、视频播放等多种场景。通过进度条,用户可以清晰地了解操作的进展情况,减少等待时的焦虑感,提高用户体验。
Flutter 提供了 LinearProgressIndicator(线性进度条)和 CircularProgressIndicator(圆形进度条)两种进度条组件,支持自定义样式、颜色、大小和动画效果。在企业应用中,进度条需要处理实时更新、多任务并行显示、缓冲进度、错误状态处理等复杂需求。
此外,进度条还应支持响应式设计以适应不同屏幕尺寸、提供无障碍支持以满足屏幕阅读器需求、支持键盘导航等功能。在 PC 端应用中,进度条需要充分利用屏幕空间,提供清晰的进度提示和详细的状态信息。
核心概念
1. LinearProgressIndicator(线性进度条)
线性进度条是最常见的进度指示器类型,适合展示单一任务的进度。其主要特点包括:
- value 参数:接受 0-1 之间的浮点数,表示进度百分比。当 value 为 null 时,显示不确定进度的动画
- backgroundColor:进度条背景颜色,通常为浅灰色
- valueColor:进度条前景颜色,表示已完成的部分
- minHeight:进度条的最小高度,默认为 4 像素
- semanticsLabel:用于无障碍访问的标签,屏幕阅读器会读取此文本
- 响应式特性:宽度自动填充父容器,高度可自定义
2. CircularProgressIndicator(圆形进度条)
圆形进度条适合展示圆形或紧凑的进度指示,常用于加载动画或进度展示。其主要特点包括:
- value 参数:0-1 之间的浮点数。当 value 为 null 时,显示旋转的不确定进度动画
- strokeWidth:进度圆形的线条宽度,默认为 4 像素
- backgroundColor:背景圆形颜色
- valueColor:进度圆形颜色
- radius:圆形的半径,可用于控制整体大小
- 动画特性:支持平滑的进度变化动画
3. 进度显示与用户反馈
进度条的显示方式直接影响用户体验:
- 百分比文字:在进度条旁边或中心显示具体的百分比数值,如"45%"
- 动画效果:使用 AnimatedBuilder 实现平滑的进度变化,避免生硬的跳跃
- 状态提示:显示当前状态(未开始、进行中、已完成、出错等)
- 多任务显示:同时展示多个任务的进度,便于用户了解整体进度
- 缓冲进度:显示已缓冲和已加载的进度,如视频播放中的缓冲进度
- 剩余时间:估算并显示剩余时间,如"预计还需 2 分钟"
代码详解
1. 基础线性进度条
最简单的线性进度条实现,直接显示进度值。这种方式适合快速原型开发或简单的进度展示场景。LinearProgressIndicator 会自动填充父容器的宽度,高度由 minHeight 参数控制。
LinearProgressIndicator(value: _progress)
这个基础实现使用默认的蓝色进度条和灰色背景。当 value 为 null 时,会显示一个不确定进度的动画效果,适合用于不知道具体进度的场景,如网络请求等待。
2. 自定义样式的线性进度条
通过自定义颜色、高度等参数,可以创建更符合应用设计的进度条。这种方式提供了更多的控制权,允许开发者根据应用的设计风格调整进度条的外观。
LinearProgressIndicator(
value: _progress,
minHeight: 8,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(Colors.green),
)
在这个例子中,我们设置了进度条的高度为 8 像素,背景颜色为浅灰色,进度颜色为绿色。AlwaysStoppedAnimation 用于指定一个固定的颜色值,而不是使用主题中的颜色。
3. 圆形进度条与百分比显示
圆形进度条常用于展示圆形的进度指示,通常与百分比文字结合使用。通过 Stack 组件可以将进度条和文字叠加显示,创建一个完整的进度指示器。
Stack(
alignment: Alignment.center,
children: [
SizedBox(
width: 120,
height: 120,
child: CircularProgressIndicator(
value: _progress,
strokeWidth: 8,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
),
Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
Text(
'${(_progress * 100).toStringAsFixed(0)}%',
style: TextStyle(fontSize: 24, fontWeight: FontWeight.bold),
),
Text('进行中...', style: TextStyle(fontSize: 12, color: Colors.grey)),
],
),
],
)
这个实现在圆形进度条中心显示百分比和状态文字,提供了更完整的进度信息。strokeWidth 参数控制圆形线条的宽度,值越大线条越粗。
4. 多任务进度条列表
在实际应用中,经常需要同时显示多个任务的进度。这种场景下,可以使用 ListView 或 Column 来展示多个进度条。
Column(
children: List.generate(_tasks.length, (index) {
final task = _tasks[index];
return Padding(
padding: EdgeInsets.symmetric(vertical: 12),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text(task['name'], style: TextStyle(fontWeight: FontWeight.bold)),
Text('${(task['progress'] * 100).toStringAsFixed(0)}%'),
],
),
SizedBox(height: 8),
LinearProgressIndicator(
value: task['progress'],
minHeight: 6,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(_getProgressColor(task['progress'])),
),
],
),
);
}),
)
这个实现展示了如何在列表中显示多个任务的进度,每个任务都有自己的进度条和百分比显示。通过 _getProgressColor 方法,可以根据进度值动态改变颜色,如进度低于 50% 时显示红色,50-80% 显示黄色,80% 以上显示绿色。
5. 带缓冲进度的进度条
在某些场景下(如视频播放),需要显示两种进度:已加载的进度和已播放的进度。这可以通过两个进度条叠加实现。
Stack(
children: [
LinearProgressIndicator(
value: _bufferProgress,
minHeight: 6,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(Colors.grey.shade400),
),
LinearProgressIndicator(
value: _playProgress,
minHeight: 6,
backgroundColor: Colors.transparent,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
],
)
这个实现中,第一个进度条显示缓冲进度(浅灰色),第二个进度条显示播放进度(蓝色)。通过设置第二个进度条的背景为透明,可以实现两个进度条的叠加显示。
高级话题:进度条的企业级应用
1. 动态/响应式设计与多屏幕适配
在企业应用中,进度条需要在不同屏幕尺寸下提供一致的用户体验。对于移动设备,进度条应该较细以节省空间;对于 PC 端,可以使用更粗的进度条以提高可见性。响应式设计确保进度条在任何设备上都清晰可见。
class ResponsiveProgressIndicator extends StatelessWidget {
final double progress;
const ResponsiveProgressIndicator({required this.progress});
Widget build(BuildContext context) {
final screenWidth = MediaQuery.of(context).size.width;
final isMobile = screenWidth < 600;
final isTablet = screenWidth < 1200;
double minHeight;
double fontSize;
if (isMobile) {
minHeight = 4;
fontSize = 12;
} else if (isTablet) {
minHeight = 6;
fontSize = 14;
} else {
minHeight = 8;
fontSize = 16;
}
return Column(
children: [
LinearProgressIndicator(
value: progress,
minHeight: minHeight,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
SizedBox(height: 8),
Text(
'进度: ${(progress * 100).toStringAsFixed(0)}%',
style: TextStyle(fontSize: fontSize),
),
],
);
}
}
这个实现根据屏幕宽度动态调整进度条的高度和文字大小,确保在所有设备上都有最佳的视觉效果。
2. 动画与过渡效果
平滑的动画效果可以提升用户体验,使进度变化看起来更自然。通过 AnimatedBuilder 和 AnimationController,可以实现进度条的平滑动画。
class AnimatedProgressIndicator extends StatefulWidget {
final double targetProgress;
const AnimatedProgressIndicator({required this.targetProgress});
State<AnimatedProgressIndicator> createState() => _AnimatedProgressIndicatorState();
}
class _AnimatedProgressIndicatorState extends State<AnimatedProgressIndicator>
with SingleTickerProviderStateMixin {
late AnimationController _controller;
late Animation<double> _animation;
void initState() {
super.initState();
_controller = AnimationController(
duration: Duration(milliseconds: 800),
vsync: this,
);
_setupAnimation();
}
void _setupAnimation() {
_animation = Tween<double>(begin: 0, end: widget.targetProgress).animate(
CurvedAnimation(parent: _controller, curve: Curves.easeInOut),
);
_controller.forward();
}
void didUpdateWidget(AnimatedProgressIndicator oldWidget) {
super.didUpdateWidget(oldWidget);
if (oldWidget.targetProgress != widget.targetProgress) {
_controller.forward(from: 0);
}
}
void dispose() {
_controller.dispose();
super.dispose();
}
Widget build(BuildContext context) {
return AnimatedBuilder(
animation: _animation,
builder: (context, child) {
return Column(
children: [
LinearProgressIndicator(
value: _animation.value,
minHeight: 8,
backgroundColor: Colors.grey.shade300,
valueColor: AlwaysStoppedAnimation(Colors.blue),
),
SizedBox(height: 8),
Text('${(_animation.value * 100).toStringAsFixed(0)}%'),
],
);
},
);
}
}
这个实现使用 CurvedAnimation 提供平滑的缓动效果,使进度条的变化看起来更加自然流畅。
3. 搜索/过滤/排序功能
在显示多个任务的进度时,可能需要根据状态、优先级或其他条件进行过滤和排序。这对于大型项目管理应用特别有用。
class ProgressFilterManager {
List<TaskProgress> _allTasks;
List<TaskProgress> _filteredTasks;
String _filterStatus = '';
String _sortBy = 'name';
bool _sortAscending = true;
ProgressFilterManager(this._allTasks) : _filteredTasks = _allTasks;
void setStatusFilter(String status) {
_filterStatus = status;
_applyFilters();
}
void setSortBy(String sortBy, bool ascending) {
_sortBy = sortBy;
_sortAscending = ascending;
_applyFilters();
}
void _applyFilters() {
_filteredTasks = _allTasks.where((task) {
if (_filterStatus.isNotEmpty && task.status != _filterStatus) {
return false;
}
return true;
}).toList();
_filteredTasks.sort((a, b) {
int comparison = 0;
switch (_sortBy) {
case 'name':
comparison = a.name.compareTo(b.name);
break;
case 'progress':
comparison = a.progress.compareTo(b.progress);
break;
case 'priority':
comparison = a.priority.compareTo(b.priority);
break;
}
return _sortAscending ? comparison : -comparison;
});
}
List<TaskProgress> get filteredTasks => _filteredTasks;
}
class TaskProgress {
final String name;
final double progress;
final String status;
final int priority;
TaskProgress({
required this.name,
required this.progress,
required this.status,
required this.priority,
});
}
4. 选择与批量操作
在任务管理应用中,用户可能需要选择多个任务进行批量操作,如批量暂停、批量删除等。
class SelectableProgressListWidget extends StatefulWidget {
final List<TaskProgress> tasks;
const SelectableProgressListWidget({required this.tasks});
State<SelectableProgressListWidget> createState() => _SelectableProgressListWidgetState();
}
class _SelectableProgressListWidgetState extends State<SelectableProgressListWidget> {
final Set<String> _selectedTaskIds = {};
void _toggleTaskSelection(String taskId) {
setState(() {
if (_selectedTaskIds.contains(taskId)) {
_selectedTaskIds.remove(taskId);
} else {
_selectedTaskIds.add(taskId);
}
});
}
void _batchPauseSelected() {
print('暂停 ${_selectedTaskIds.length} 个任务');
// 实现批量暂停逻辑
}
Widget build(BuildContext context) {
return Column(
children: [
if (_selectedTaskIds.isNotEmpty)
Container(
padding: EdgeInsets.all(16),
color: Colors.blue.shade50,
child: Row(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
children: [
Text('已选择 ${_selectedTaskIds.length} 个任务'),
ElevatedButton(
onPressed: _batchPauseSelected,
child: Text('批量暂停'),
),
],
),
),
Expanded(
child: ListView.builder(
itemCount: widget.tasks.length,
itemBuilder: (context, index) {
final task = widget.tasks[index];
final isSelected = _selectedTaskIds.contains(task.name);
return CheckboxListTile(
value: isSelected,
onChanged: (_) => _toggleTaskSelection(task.name),
title: Text(task.name),
subtitle: LinearProgressIndicator(
value: task.progress,
minHeight: 4,
),
);
},
),
),
],
);
}
}
5. 加载与缓存策略
对于需要从网络加载数据的进度条,应该实现缓存机制以避免重复加载。同时,应该处理加载失败的情况。
class ProgressDataCache {
final Map<String, TaskProgress> _cache = {};
bool _isLoading = false;
Future<List<TaskProgress>> loadTaskProgress() async {
if (_cache.isNotEmpty) {
return _cache.values.toList();
}
_isLoading = true;
try {
await Future.delayed(Duration(seconds: 2));
final tasks = [
TaskProgress(name: '任务1', progress: 0.3, status: 'running', priority: 1),
TaskProgress(name: '任务2', progress: 0.7, status: 'running', priority: 2),
TaskProgress(name: '任务3', progress: 1.0, status: 'completed', priority: 3),
];
for (var task in tasks) {
_cache[task.name] = task;
}
return tasks;
} finally {
_isLoading = false;
}
}
void updateTaskProgress(String taskName, double progress) {
if (_cache.containsKey(taskName)) {
_cache[taskName] = TaskProgress(
name: _cache[taskName]!.name,
progress: progress,
status: progress >= 1.0 ? 'completed' : 'running',
priority: _cache[taskName]!.priority,
);
}
}
void clearCache() {
_cache.clear();
}
bool get isLoading => _isLoading;
}
6. 键盘导航与快捷键
为了提高 PC 端的可用性,应该支持键盘导航。用户可以使用方向键在任务之间导航,使用 Enter 键选择任务。
class KeyboardNavigableProgressList extends StatefulWidget {
final List<TaskProgress> tasks;
const KeyboardNavigableProgressList({required this.tasks});
State<KeyboardNavigableProgressList> createState() => _KeyboardNavigableProgressListState();
}
class _KeyboardNavigableProgressListState extends State<KeyboardNavigableProgressList> {
int _focusedIndex = 0;
late FocusNode _focusNode;
void initState() {
super.initState();
_focusNode = FocusNode();
}
void dispose() {
_focusNode.dispose();
super.dispose();
}
void _handleKeyEvent(RawKeyEvent event) {
if (event.isKeyPressed(LogicalKeyboardKey.arrowDown)) {
setState(() {
_focusedIndex = (_focusedIndex + 1) % widget.tasks.length;
});
} else if (event.isKeyPressed(LogicalKeyboardKey.arrowUp)) {
setState(() {
_focusedIndex = (_focusedIndex - 1 + widget.tasks.length) % widget.tasks.length;
});
} else if (event.isKeyPressed(LogicalKeyboardKey.enter)) {
print('选择任务: ${widget.tasks[_focusedIndex].name}');
}
}
Widget build(BuildContext context) {
return RawKeyboardListener(
focusNode: _focusNode,
onKey: _handleKeyEvent,
child: ListView.builder(
itemCount: widget.tasks.length,
itemBuilder: (context, index) {
final task = widget.tasks[index];
final isFocused = index == _focusedIndex;
return Container(
decoration: BoxDecoration(
border: isFocused ? Border.all(color: Colors.blue, width: 2) : null,
),
child: ListTile(
title: Text(task.name),
subtitle: LinearProgressIndicator(value: task.progress),
),
);
},
),
);
}
}
7. 无障碍支持与屏幕阅读器
为了确保应用对所有用户都可用,应该为进度条添加适当的无障碍标签。屏幕阅读器用户可以通过这些标签了解进度信息。
class AccessibleProgressIndicator extends StatelessWidget {
final double progress;
final String taskName;
const AccessibleProgressIndicator({
required this.progress,
required this.taskName,
});
Widget build(BuildContext context) {
final percentage = (progress * 100).toStringAsFixed(0);
return Semantics(
label: '$taskName 进度 $percentage 百分比',
slider: true,
onIncrease: () => print('增加进度'),
onDecrease: () => print('减少进度'),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
Semantics(
label: '任务名称',
child: Text(taskName, style: TextStyle(fontWeight: FontWeight.bold)),
),
SizedBox(height: 8),
Semantics(
label: '进度条',
child: LinearProgressIndicator(
value: progress,
minHeight: 8,
semanticsLabel: '$percentage% 完成',
),
),
SizedBox(height: 8),
Semantics(
label: '百分比',
child: Text('$percentage%'),
),
],
),
);
}
}
8. 样式自定义与主题适配
不同的应用可能需要不同的进度条样式。通过创建可配置的主题类,可以轻松适配不同的设计风格。
class ProgressIndicatorTheme {
final Color progressColor;
final Color backgroundColor;
final double minHeight;
final Curve animationCurve;
final Duration animationDuration;
const ProgressIndicatorTheme({
this.progressColor = Colors.blue,
this.backgroundColor = const Color(0xFFE0E0E0),
this.minHeight = 4,
this.animationCurve = Curves.easeInOut,
this.animationDuration = const Duration(milliseconds: 800),
});
static ProgressIndicatorTheme light() {
return ProgressIndicatorTheme(
progressColor: Colors.blue,
backgroundColor: Colors.grey.shade300,
);
}
static ProgressIndicatorTheme dark() {
return ProgressIndicatorTheme(
progressColor: Colors.blue.shade300,
backgroundColor: Colors.grey.shade700,
);
}
static ProgressIndicatorTheme success() {
return ProgressIndicatorTheme(
progressColor: Colors.green,
backgroundColor: Colors.green.shade100,
);
}
}
9. 数据持久化与导出
在某些应用中,可能需要保存进度信息以便后续查看或分析。这可以通过将数据导出为 JSON 或 CSV 格式实现。
class ProgressDataExporter {
static String exportToJSON(List<TaskProgress> tasks) {
final jsonList = tasks.map((task) => {
'name': task.name,
'progress': task.progress,
'status': task.status,
'priority': task.priority,
}).toList();
return jsonEncode(jsonList);
}
static String exportToCSV(List<TaskProgress> tasks) {
final buffer = StringBuffer();
buffer.writeln('任务名,进度,状态,优先级');
for (var task in tasks) {
buffer.writeln('${task.name},${task.progress},${task.status},${task.priority}');
}
return buffer.toString();
}
}
10. 单元测试与集成测试
为了确保进度条功能的正确性,应该编写全面的测试用例。
void main() {
group('Progress Indicator Tests', () {
test('进度条值更新', () {
double progress = 0.0;
expect(progress, 0.0);
progress = 0.5;
expect(progress, 0.5);
progress = 1.0;
expect(progress, 1.0);
});
test('百分比计算', () {
double progress = 0.75;
final percentage = (progress * 100).toStringAsFixed(0);
expect(percentage, '75');
});
test('任务过滤', () {
final tasks = [
TaskProgress(name: '任务1', progress: 0.3, status: 'running', priority: 1),
TaskProgress(name: '任务2', progress: 0.7, status: 'running', priority: 2),
TaskProgress(name: '任务3', progress: 1.0, status: 'completed', priority: 3),
];
final completed = tasks.where((t) => t.status == 'completed').toList();
expect(completed.length, 1);
expect(completed[0].name, '任务3');
});
});
testWidgets('Progress Indicator Widget 集成测试', (WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: Scaffold(
body: LinearProgressIndicator(value: 0.5),
),
),
);
expect(find.byType(LinearProgressIndicator), findsOneWidget);
});
}
OpenHarmony PC 端适配要点
- 屏幕宽度检测:根据不同屏幕宽度调整进度条高度和文字大小
- 响应式布局:在 PC 端使用更粗的进度条以提高可见性
- 键盘导航:支持方向键在任务之间导航
- 鼠标交互:支持点击进度条直接跳转到指定进度
- 无障碍支持:为屏幕阅读器提供完整的语义标签
实际应用场景
- 文件下载:显示下载进度和剩余时间
- 数据加载:显示数据加载进度
- 视频播放:显示播放进度和缓冲进度
- 任务管理:显示多个任务的进度
- 安装程序:显示安装进度
扩展建议
- 支持暂停和恢复进度
- 实现进度预测和剩余时间估算
- 添加进度历史记录
- 支持进度通知和提醒
- 实现进度数据的导出和分析
总结
进度条是提升用户体验的重要组件。通过合理的设计和实现,可以创建出功能完整、用户体验良好的进度条系统。在 PC 端应用中,充分利用屏幕空间、提供键盘导航和无障碍支持是关键。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)