进度的力量
人类天生喜欢看到进度。游戏里的经验条、下载时的百分比、健身 App 的步数环,这些进度指示器让我们感觉"在前进"。
在 TodoList 应用中,进度条告诉用户"你完成了多少"。当进度条从 0% 慢慢涨到 100%,用户会有成就感。这种正向反馈能激励用户继续完成任务。
我们的应用有两个进度条:任务列表页的小进度条和统计页的大进度条。它们显示的是同一个数据,但视觉呈现不同。
进度数据的计算
进度条需要三个数据:总任务数、已完成数、完成百分比。
tsx
const totalTasks = tasks.length;
const completedTasks = tasks.filter(t => t.completed).length;
const progress = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
总任务数
tasks.length 直接获取数组长度,这是最简单的。
已完成数
tasks.filter(t => t.completed).length 先筛选出已完成的任务,再取长度。filter 方法返回一个新数组,包含所有 completed 为 true 的任务。
完成百分比
tsx
const progress = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
这里有一个三元运算符处理边界情况。如果 totalTasks 是 0,直接除会得到 NaN(除以零)。所以先判断,如果没有任务就返回 0。
百分比的计算
(completedTasks / totalTasks) * 100 是标准的百分比计算。比如完成了 3 个任务,总共 5 个,那就是 (3 / 5) * 100 = 60。
任务列表页的进度条
任务列表页顶部的统计区域有一个小进度条:
tsx
<View style={styles.progressContainer}>
<View style={[styles.progressBar, {backgroundColor: theme.border}]}>
<View style={[styles.progressFill, {width: `${progress}%`, backgroundColor: theme.accent}]} />
</View>
<Text style={[styles.progressText, {color: theme.subText}]}>{Math.round(progress)}%</Text>
</View>
三层结构
进度条是三层嵌套的 View:
progressContainer是最外层容器,负责整体布局progressBar是进度条的背景轨道progressFill是进度条的填充部分
宽度的动态设置
tsx
{width: `${progress}%`}
填充部分的宽度用百分比设置。如果 progress 是 60,宽度就是 '60%'。这样填充部分会占据轨道的 60%。
百分比文字
tsx
<Text style={[styles.progressText, {color: theme.subText}]}>{Math.round(progress)}%</Text>
Math.round(progress) 对百分比四舍五入。如果 progress 是 66.666...,显示的是 67%。小数点后的数字对用户没有意义,整数更清晰。
进度条的样式
tsx
progressContainer: {flex: 1.5, alignItems: 'center'},
progressBar: {width: '100%', height: 8, borderRadius: 4, overflow: 'hidden'},
progressFill: {height: '100%', borderRadius: 4},
progressText: {fontSize: 12, marginTop: 4},
progressContainer 容器样式
flex: 1.5让进度条区域比其他统计项宽一些,因为进度条需要更多空间alignItems: 'center'让内容水平居中
progressBar 轨道样式
width: '100%'占满容器宽度height: 8轨道高度 8 像素,不太粗也不太细borderRadius: 4圆角是高度的一半,让轨道两端是半圆形overflow: 'hidden'隐藏超出部分,确保填充部分的圆角不会超出轨道
progressFill 填充样式
height: '100%'高度填满轨道borderRadius: 4同样的圆角,让填充部分也是圆润的
progressText 文字样式
fontSize: 12小字体,不抢眼marginTop: 4与进度条的间距
统计页的大进度条
统计页有一个更大、更醒目的进度条:
tsx
<View style={[styles.statsCard, {backgroundColor: theme.card, borderColor: theme.border}]}>
<Text style={[styles.statsCardTitle, {color: theme.text}]}>完成率</Text>
<View style={styles.progressLarge}>
<View style={[styles.progressBarLarge, {backgroundColor: theme.border}]}>
<View style={[styles.progressFillLarge, {width: `${progress}%`, backgroundColor: theme.accent}]} />
</View>
<Text style={[styles.progressPercent, {color: theme.accent}]}>{Math.round(progress)}%</Text>
</View>
</View>
卡片包装
大进度条放在一个卡片里,有标题"完成率"。这让它成为一个独立的信息模块。
更大的视觉呈现
大进度条的百分比文字更大、颜色更醒目,用主题强调色而不是次要文字颜色。
大进度条的样式
tsx
progressLarge: {alignItems: 'center'},
progressBarLarge: {width: '100%', height: 12, borderRadius: 6, overflow: 'hidden'},
progressFillLarge: {height: '100%', borderRadius: 6},
progressPercent: {fontSize: 24, fontWeight: 'bold', marginTop: 12},
更粗的轨道
height: 12 比小进度条的 8 像素更粗,视觉上更有分量。
更大的百分比
fontSize: 24 和 fontWeight: 'bold' 让百分比数字成为视觉焦点。用户一眼就能看到完成了多少。
更大的间距
marginTop: 12 让百分比和进度条之间有更多空间,不会显得拥挤。
进度条的颜色
tsx
<View style={[styles.progressBar, {backgroundColor: theme.border}]}>
<View style={[styles.progressFill, {width: `${progress}%`, backgroundColor: theme.accent}]} />
</View>
轨道颜色
轨道用 theme.border 颜色,是一个低调的灰色。它是背景,不应该太抢眼。
填充颜色
填充用 theme.accent 颜色,是主题的强调色紫色。它是前景,应该醒目。
颜色对比
灰色轨道配紫色填充,对比明显。用户能清楚地看到"完成了多少"和"还剩多少"。
进度为 0 和 100 的特殊情况
进度为 0
当没有完成任何任务时,progress 是 0,填充部分宽度是 '0%',也就是不显示。用户只能看到灰色的轨道。
进度为 100
当所有任务都完成时,progress 是 100,填充部分宽度是 '100%',完全覆盖轨道。用户看到的是一条完整的紫色进度条。
没有任务
当任务列表为空时,totalTasks 是 0,progress 被设为 0。显示一个空的进度条,而不是报错。
进度条的动画效果
当前的实现没有动画,进度变化是瞬间的。如果想要平滑的动画效果:
tsx
const progressAnim = useRef(new Animated.Value(0)).current;
useEffect(() => {
Animated.timing(progressAnim, {
toValue: progress,
duration: 500,
useNativeDriver: false,
}).start();
}, [progress]);
// 使用
<Animated.View style={[styles.progressFill, {
width: progressAnim.interpolate({
inputRange: [0, 100],
outputRange: ['0%', '100%'],
}),
backgroundColor: theme.accent
}]} />
动画的原理
用 Animated.Value 存储动画值,当 progress 变化时,用 Animated.timing 让动画值平滑过渡到新值。interpolate 把 0-100 的数值映射到 '0%'-'100%' 的宽度。
useNativeDriver 的限制
useNativeDriver: false 是必须的,因为宽度动画不能用原生驱动。这意味着动画性能可能不如原生驱动的动画,但对于简单的进度条来说完全够用。
进度条与筛选的关系
进度条显示的是所有任务的完成情况,不受筛选影响:
tsx
const totalTasks = tasks.length;
const completedTasks = tasks.filter(t => t.completed).length;
基于原始数据
计算用的是 tasks 而不是 filteredTasks。即使用户筛选了"待办"任务,进度条仍然显示整体的完成率。
为什么这样设计
如果进度条跟着筛选变化,用户筛选"已完成"后会看到 100% 的进度,这没有意义。进度条应该反映整体情况,给用户一个全局视角。
进度条的可访问性
为了让进度条对所有用户友好:
tsx
<View
style={styles.progressContainer}
accessibilityRole="progressbar"
accessibilityValue={{min: 0, max: 100, now: Math.round(progress)}}
accessibilityLabel={`任务完成进度 ${Math.round(progress)}%`}
>
accessibilityRole
accessibilityRole="progressbar" 告诉屏幕阅读器这是一个进度条。
accessibilityValue
accessibilityValue 提供进度条的数值信息:最小值、最大值、当前值。
accessibilityLabel
accessibilityLabel 是屏幕阅读器会朗读的文字,比如"任务完成进度 60%"。
进度条的变体
除了水平进度条,还有其他形式:
环形进度条
用 SVG 或 react-native-svg 画一个圆环,用 strokeDasharray 和 strokeDashoffset 控制进度。环形进度条更紧凑,适合在小空间里显示。
分段进度条
把进度条分成几段,每完成一个任务点亮一段。这种形式更有"打怪升级"的感觉。
数字进度
不用进度条,直接显示"3/5"或"60%"。最简单,但视觉冲击力不如进度条。
进度条的心理学
进度条不只是显示数据,它还影响用户的心理。
成就感
看到进度条增长,用户会有成就感。这种正向反馈能激励用户继续完成任务。
目标感
进度条暗示了一个目标:100%。用户会不自觉地想要填满它。
压力感
如果进度条长期停在很低的位置,用户可能会感到压力。设计时要注意,不要让进度条成为焦虑的来源。
进度条的颜色变化
可以根据进度改变颜色:
tsx
const getProgressColor = () => {
if (progress < 30) return '#ff6b6b'; // 红色,进度低
if (progress < 70) return '#ffd93d'; // 黄色,进度中等
return '#6bcb77'; // 绿色,进度高
};
颜色的语义
红色表示"还有很多要做",黄色表示"进行中",绿色表示"快完成了"。这种颜色变化能给用户更直观的反馈。
是否需要
这取决于产品定位。如果想强调"完成任务"的成就感,颜色变化是好的。如果想保持界面简洁,单一颜色也可以。
小结
进度条是一个简单但有效的 UI 元素。它用视觉的方式告诉用户"完成了多少",比单纯的数字更直观。
实现要点:
- 进度计算要处理除以零的边界情况
- 进度条是三层结构:容器、轨道、填充
- 填充部分的宽度用百分比动态设置
- 百分比数字用
Math.round四舍五入 - 进度条基于原始数据计算,不受筛选影响
好的进度条应该是"一眼就懂、看了想填满"。它不只是数据的可视化,更是一种激励机制,推动用户完成更多任务。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
