两个数字,一个对比
做了多少,还剩多少。这两个数字放在一起,形成一种对比。
绿色的已完成数字让人有成就感,红色的待办数字提醒你还有事没做。两个数字此消彼长,每完成一个任务,绿色加一,红色减一。这种变化本身就是一种激励。
我们把这两个数字放在统计区域,和今日任务、进度条并排显示。用户扫一眼就能知道整体情况。
数据怎么算
已完成数量很好算,筛选出 completed 为 true 的任务,数一数有多少个:
tsx
const completedTasks = tasks.filter(t => t.completed).length;
待办数量可以用同样的方法:
tsx
const pendingTasks = tasks.filter(t => !t.completed).length;
但其实没必要再遍历一次。总数减去已完成就是待办:
tsx
const totalTasks = tasks.length;
const completedTasks = tasks.filter(t => t.completed).length;
// 待办 = 总数 - 已完成
在显示的时候直接用 totalTasks - completedTasks,省一次遍历。虽然对于几十条任务来说这点性能差异可以忽略,但写代码还是要有这个意识。
统计区域的代码
任务列表页顶部的统计区域:
tsx
<View style={[styles.statsContainer, {backgroundColor: theme.card, borderColor: theme.border}]}>
<View style={styles.statItem}>
<Text style={[styles.statNumber, {color: theme.accent}]}>{todayTasks}</Text>
<Text style={[styles.statLabel, {color: theme.subText}]}>今日</Text>
</View>
<View style={styles.statItem}>
<Text style={[styles.statNumber, {color: '#6bcb77'}]}>{completedTasks}</Text>
<Text style={[styles.statLabel, {color: theme.subText}]}>已完成</Text>
</View>
<View style={styles.statItem}>
<Text style={[styles.statNumber, {color: '#ff6b6b'}]}>{totalTasks - completedTasks}</Text>
<Text style={[styles.statLabel, {color: theme.subText}]}>待办</Text>
</View>
<View style={styles.progressContainer}>
...
</View>
</View>
四个统计项并排,已完成和待办挨在一起,方便对比。
颜色的选择
已完成用绿色 #6bcb77,待办用红色 #ff6b6b。
为什么这么选?因为这是大家都懂的颜色语言。绿色代表"好"、"完成"、"通过",红色代表"注意"、"未完成"、"警告"。不用解释,用户一看就懂。
有人可能会说,红色会给用户压力。确实,如果待办数字很大,一片红色看着挺吓人的。但这也是我们想要的效果------提醒用户还有很多事没做。如果想要更温和的感觉,可以把红色换成橙色或者灰色。
我们选择保留红色,因为 TodoList 的核心功能就是帮用户记住要做的事。适度的压力是好的。
样式的细节
tsx
statItem: {alignItems: 'center', flex: 1},
statNumber: {fontSize: 24, fontWeight: 'bold'},
statLabel: {fontSize: 12, marginTop: 4},
数字和标签的关系
数字大,标签小。数字是主角,标签是配角。用户的视线会先被大数字吸引,然后看小标签确认这个数字是什么意思。
marginTop: 4 让标签和数字之间有一点间距,不会挤在一起。
flex: 1 的作用
flex: 1 让每个统计项平分可用空间。不管有几个统计项,它们的宽度都是相等的。这样看起来整齐。
统计页的展示
统计页有一个更大的卡片展示任务总览:
tsx
<View style={[styles.statsCard, {backgroundColor: theme.card, borderColor: theme.border}]}>
<Text style={[styles.statsCardTitle, {color: theme.text}]}>任务总览</Text>
<View style={styles.statsRow}>
<View style={styles.statsBox}>
<Text style={[styles.statsBoxNumber, {color: theme.accent}]}>{totalTasks}</Text>
<Text style={[styles.statsBoxLabel, {color: theme.subText}]}>总任务</Text>
</View>
<View style={styles.statsBox}>
<Text style={[styles.statsBoxNumber, {color: '#6bcb77'}]}>{completedTasks}</Text>
<Text style={[styles.statsBoxLabel, {color: theme.subText}]}>已完成</Text>
</View>
<View style={styles.statsBox}>
<Text style={[styles.statsBoxNumber, {color: '#ff6b6b'}]}>{totalTasks - completedTasks}</Text>
<Text style={[styles.statsBoxLabel, {color: theme.subText}]}>待完成</Text>
</View>
</View>
</View>
这里多了一个"总任务",用主题强调色显示。三个数字放在一起,总任务 = 已完成 + 待完成,用户可以验证数据的正确性。
统计页样式
tsx
statsCard: {borderRadius: 16, borderWidth: 1, padding: 20, marginBottom: 16},
statsCardTitle: {fontSize: 16, fontWeight: '600', marginBottom: 16},
statsRow: {flexDirection: 'row', justifyContent: 'space-around'},
statsBox: {alignItems: 'center'},
statsBoxNumber: {fontSize: 32, fontWeight: 'bold'},
statsBoxLabel: {fontSize: 12, marginTop: 4},
统计页的数字用 fontSize: 32,比任务列表页的 24 更大。因为统计页就是用来看数据的,数字应该更醒目。
justifyContent: 'space-around' 让三个统计项均匀分布,两边留有空白。
数字变化的时机
什么时候这两个数字会变?
完成任务
用户点击勾选框,任务从未完成变成已完成。已完成数字加一,待办数字减一。
tsx
const toggleTask = (id: string) => {
setTasks(tasks.map(task => task.id === id ? {...task, completed: !task.completed} : task));
};
toggleTask 函数切换任务的完成状态。状态变化触发重新渲染,统计数字自动更新。
添加任务
用户添加新任务,新任务默认是未完成的。总任务加一,待办加一,已完成不变。
删除任务
用户删除任务。如果删的是已完成任务,已完成减一。如果删的是未完成任务,待办减一。总任务都会减一。
这些变化都是自动的,因为统计数字是从 tasks 状态实时计算的。
全部完成的特殊情况
如果用户完成了所有任务,待办数字变成 0。这是一个值得庆祝的时刻。
可以加一些特殊处理:
tsx
{totalTasks - completedTasks === 0 && totalTasks > 0 ? (
<View style={styles.statItem}>
<Text style={[styles.statNumber, {color: '#6bcb77'}]}>✓</Text>
<Text style={[styles.statLabel, {color: theme.subText}]}>全部完成</Text>
</View>
) : (
<View style={styles.statItem}>
<Text style={[styles.statNumber, {color: '#ff6b6b'}]}>{totalTasks - completedTasks}</Text>
<Text style={[styles.statLabel, {color: theme.subText}]}>待办</Text>
</View>
)}
当待办为 0 且有任务时,显示一个绿色的勾和"全部完成"。这种小惊喜能让用户开心。
我们的演示项目没有做这个处理,直接显示 0。两种方式都可以。
没有任务的情况
如果任务列表是空的,totalTasks 是 0,completedTasks 也是 0,待办自然也是 0。
三个 0 放在一起,告诉用户"你还没有任何任务"。这时候空状态组件会显示,引导用户添加任务。
数据的一致性
已完成 + 待办 = 总任务,这个等式必须成立。
因为我们用 totalTasks - completedTasks 计算待办,而不是单独筛选,所以这个等式是天然成立的。不会出现数据不一致的情况。
如果用两次 filter 分别计算已完成和待办,理论上也应该一致。但如果代码写错了,比如筛选条件不对,就可能出现 2 + 3 = 6 这种尴尬情况。用减法更保险。
点击跳转筛选
用户看到已完成数字是 5,可能想看看具体是哪 5 个任务。可以让数字可点击:
tsx
<TouchableOpacity style={styles.statItem} onPress={() => setFilter('completed')}>
<Text style={[styles.statNumber, {color: '#6bcb77'}]}>{completedTasks}</Text>
<Text style={[styles.statLabel, {color: theme.subText}]}>已完成</Text>
</TouchableOpacity>
点击已完成数字,自动切换到"已完成"筛选,列表只显示已完成的任务。
同样,点击待办数字可以切换到"待办"筛选。这样统计数字不只是展示,还是一个快捷入口。
动画效果
数字变化时可以加一个动画,比如数字放大再缩小,或者颜色闪一下。这种微动画能让用户注意到变化。
tsx
const scaleAnim = useRef(new Animated.Value(1)).current;
const animateNumber = () => {
Animated.sequence([
Animated.timing(scaleAnim, {toValue: 1.2, duration: 100, useNativeDriver: true}),
Animated.timing(scaleAnim, {toValue: 1, duration: 100, useNativeDriver: true}),
]).start();
};
// 当 completedTasks 变化时触发动画
useEffect(() => {
animateNumber();
}, [completedTasks]);
不过要注意,动画不能太频繁。如果用户快速完成多个任务,数字一直在跳,反而会让人烦。
我们的演示项目没有加这个动画,保持简单。
颜色的可访问性
红绿色盲的用户可能分不清已完成和待办的颜色。除了颜色,我们还用了文字标签"已完成"和"待办"来区分。
如果想做得更好,可以在数字旁边加一个小图标。已完成用勾,待办用圆点。这样即使看不清颜色,也能通过图标区分。
小结
已完成和待办数量是 TodoList 最基本的统计。两个数字,两种颜色,形成对比。
计算很简单,已完成用 filter 筛选,待办用总数减已完成。显示上,绿色代表已完成,红色代表待办,大数字配小标签。
这两个数字的变化反映了用户的工作进度。每完成一个任务,绿色加一红色减一,这种即时反馈能激励用户继续完成任务。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
