RN for OpenHarmony 实战 TodoList 项目:圆形勾选框样式
本文介绍了TodoList应用中勾选框的设计与实现。勾选框采用三层结构:外层TouchableOpacity处理点击事件,中层圆形View作为视觉容器,内层Text显示勾选符号。设计上选用圆形而非传统方形,尺寸为24x24像素,使用紫色主题色与白色勾选符号形成鲜明对比。状态切换通过不可变更新实现,点击时调用toggleTask函数更新任务状态。文章还探讨了圆形设计的优势、尺寸选择依据、颜色方案以及
案例开源地址:https://gitcode.com/lqjmac/rn_openharmony_todolist
勾选框的设计哲学
勾选框是 TodoList 应用的灵魂。
它不仅仅是一个功能组件,更是用户与任务交互的核心触点。每一次点击,都是一次"完成"的仪式感。一个设计精良的勾选框,能让这个简单的动作变得愉悦。
在我们的应用中,勾选框采用了圆形设计,而不是传统的方形。圆形更柔和、更现代,与整体的圆角设计风格保持一致。
勾选框的结构
先看勾选框的完整代码:
<TouchableOpacity onPress={() => toggleTask(item.id)} style={styles.checkbox}>
<View style={[styles.checkboxInner, {borderColor: theme.accent}, item.completed && {backgroundColor: theme.accent}]}>
{item.completed && <Text style={styles.checkmark}>✓</Text>}
</View>
</TouchableOpacity>
结构很简单,三层嵌套:
TouchableOpacity- 可点击的容器,处理触摸事件View(checkboxInner) - 圆形的视觉容器Text(checkmark) - 勾选符号,只在完成时显示
外层:触摸容器
<TouchableOpacity onPress={() => toggleTask(item.id)} style={styles.checkbox}>
TouchableOpacity 是 React Native 提供的触摸组件。当用户按下时,它会降低子元素的透明度,提供视觉反馈。
点击事件
onPress={() => toggleTask(item.id)}
点击时调用 toggleTask 函数,传入任务 ID。这个函数会切换任务的完成状态:
const toggleTask = (id: string) => {
setTasks(tasks.map(task => task.id === id ? {...task, completed: !task.completed} : task));
};
样式
checkbox: {marginRight: 12},
只设置了右边距,让勾选框与任务标题保持距离。
为什么不设置宽高?因为 TouchableOpacity 会自动包裹子元素的大小。实际的触摸区域由内部的 checkboxInner 决定。
中层:圆形容器
<View style={[styles.checkboxInner, {borderColor: theme.accent}, item.completed && {backgroundColor: theme.accent}]}>
这是勾选框的视觉主体。
基础样式
checkboxInner: {width: 24, height: 24, borderRadius: 12, borderWidth: 2, justifyContent: 'center', alignItems: 'center'},
width: 24, height: 24- 24x24 像素的正方形borderRadius: 12- 半径等于宽度的一半,形成圆形borderWidth: 2- 2 像素的边框justifyContent: 'center', alignItems: 'center'- 内容居中(为了让勾选符号居中)
动态边框颜色
{borderColor: theme.accent}
边框颜色使用主题的强调色 #6c5ce7(紫色)。无论任务是否完成,边框颜色都是一样的。
完成状态的背景色
item.completed && {backgroundColor: theme.accent}
当任务完成时,背景色变成强调色。这是一个条件样式——只有 item.completed 为 true 时才应用。
未完成状态:紫色边框 + 透明背景
完成状态:紫色边框 + 紫色背景
内层:勾选符号
{item.completed && <Text style={styles.checkmark}>✓</Text>}
勾选符号只在任务完成时显示。
条件渲染
{item.completed && <Text>...</Text>}
这是 React 的条件渲染语法。当 item.completed 为 false 时,整个表达式返回 false,不渲染任何内容。当 item.completed 为 true 时,渲染后面的 <Text> 组件。
勾选符号
✓
使用 Unicode 字符 ✓(U+2713)作为勾选符号。也可以用其他符号,比如 ✔(U+2714)或 ☑(U+2611)。
样式
checkmark: {color: '#fff', fontSize: 14, fontWeight: 'bold'},
color: '#fff'- 白色,与紫色背景形成对比fontSize: 14- 字体大小,比容器小一些,留出边距fontWeight: 'bold'- 粗体,让符号更醒目
两种状态的视觉对比
未完成状态
┌─────────┐
│ ○ │ 紫色圆环,透明内部
└─────────┘
- 边框:紫色(#6c5ce7)
- 背景:透明
- 内容:无
完成状态
┌─────────┐
│ ●✓ │ 紫色实心圆,白色勾选
└─────────┘
- 边框:紫色(#6c5ce7)
- 背景:紫色(#6c5ce7)
- 内容:白色勾选符号
这种设计的好处是状态变化非常明显。从空心到实心,从无到有,用户一眼就能看出任务是否完成。
为什么用圆形而不是方形?
传统的勾选框是方形的,但我们选择了圆形。原因有几个:
1. 与整体设计风格一致
应用中大量使用了圆角设计:卡片圆角、按钮圆角、标签圆角。圆形勾选框与这些元素更协调。
2. 更现代的视觉感受
圆形给人柔和、友好的感觉。方形则更正式、严肃。对于一个日常使用的待办应用,圆形更合适。
3. 触摸更自然
圆形的触摸区域更符合手指的形状。虽然实际的触摸区域是矩形的(TouchableOpacity 的边界),但视觉上的圆形让用户更愿意去点击。
4. 差异化
大多数待办应用使用方形勾选框。圆形是一个小小的差异化,让应用更有辨识度。
尺寸的选择
勾选框的尺寸是 24x24 像素。这个数字是怎么来的?
最小触摸目标
Apple 的人机界面指南建议,触摸目标至少应该是 44x44 点。Google 的 Material Design 建议至少 48x48 dp。
24 像素看起来小于这个标准,但实际上 TouchableOpacity 的触摸区域会比视觉区域大一些。而且勾选框旁边有足够的空白,用户不太容易误触。
视觉平衡
24 像素与 16 像素的任务标题字体大小比较协调。如果勾选框太大,会抢走标题的注意力;太小则不够醒目。
边框宽度
2 像素的边框在 24 像素的圆形上看起来刚好。1 像素太细,3 像素太粗。
颜色的选择
勾选框使用主题的强调色 #6c5ce7(紫色)。
为什么用强调色?
勾选框是用户交互的核心元素,应该足够醒目。使用强调色可以让它从其他元素中脱颖而出。
为什么不用绿色?
绿色通常表示"成功"或"完成",用在完成状态的勾选框上似乎很合理。但我们选择了紫色,原因是:
- 紫色是应用的主题色,保持一致性
- 绿色已经用于低优先级标签,避免颜色冲突
- 紫色更有个性,不那么"俗套"
白色勾选符号
白色与紫色背景有足够的对比度,确保可读性。如果背景是浅色,勾选符号应该用深色。
状态切换的实现
点击勾选框时,任务的完成状态会切换:
const toggleTask = (id: string) => {
setTasks(tasks.map(task => task.id === id ? {...task, completed: !task.completed} : task));
};
这行代码做了什么?
1. 遍历所有任务
tasks.map(task => ...)
map 方法遍历数组,对每个元素执行回调函数,返回一个新数组。
2. 找到目标任务
task.id === id ? ... : task
如果任务 ID 匹配,执行状态切换;否则返回原任务(不变)。
3. 切换完成状态
{...task, completed: !task.completed}
使用展开运算符创建任务的副本,然后覆盖 completed 属性。!task.completed 取反,true 变 false,false 变 true。
4. 更新状态
setTasks(...)
用新数组更新状态,触发重新渲染。
这种不可变更新的方式是 React 的最佳实践。不直接修改原数组,而是创建新数组,让 React 能正确检测到变化。
触摸反馈
TouchableOpacity 提供了默认的触摸反馈——按下时透明度降低。
可以通过 activeOpacity 属性调整:
<TouchableOpacity activeOpacity={0.7} onPress={...}>
默认值是 0.2,表示按下时透明度变为 20%。0.7 表示变为 70%,反馈更subtle。
也可以使用其他触摸组件:
TouchableHighlight- 按下时显示底色TouchableWithoutFeedback- 无视觉反馈Pressable- 更灵活的触摸组件(React Native 0.63+)
可访问性
为了让勾选框对所有用户都友好,可以添加可访问性属性:
<TouchableOpacity
onPress={() => toggleTask(item.id)}
style={styles.checkbox}
accessibilityRole="checkbox"
accessibilityState={{checked: item.completed}}
accessibilityLabel={`任务:${item.title}`}
accessibilityHint={item.completed ? '点击标记为未完成' : '点击标记为已完成'}
>
accessibilityRole="checkbox"- 告诉屏幕阅读器这是一个复选框accessibilityState={{checked: item.completed}}- 告诉屏幕阅读器当前状态accessibilityLabel- 屏幕阅读器会朗读的标签accessibilityHint- 额外的提示信息
动画增强
当前的实现没有状态切换动画。如果要添加,可以使用 Animated API:
const scaleAnim = useRef(new Animated.Value(1)).current;
const handlePress = () => {
Animated.sequence([
Animated.timing(scaleAnim, {toValue: 0.8, duration: 100, useNativeDriver: true}),
Animated.timing(scaleAnim, {toValue: 1, duration: 100, useNativeDriver: true}),
]).start();
toggleTask(item.id);
};
<Animated.View style={[styles.checkboxInner, {transform: [{scale: scaleAnim}]}]}>
这会在点击时产生一个"弹跳"效果,让交互更有趣。
其他勾选框设计方案
除了当前的设计,还有很多其他方案:
方形勾选框
checkboxInner: {width: 20, height: 20, borderRadius: 4, borderWidth: 2},
把 borderRadius 改小,就变成圆角方形。
图标勾选框
使用图标库(如 react-native-vector-icons)的勾选图标,而不是文字符号。
滑动开关
用 Switch 组件代替勾选框,适合某些场景。
自定义图形
使用 SVG 或自定义绘制,实现更复杂的勾选效果。
每种方案都有其适用场景,关键是要与整体设计风格保持一致。
小结
一个小小的勾选框,包含了很多设计和实现的细节:
- 三层嵌套结构:触摸容器 → 视觉容器 → 勾选符号
- 圆形设计,与整体风格一致
- 两种状态的明显视觉差异
- 合适的尺寸和颜色
- 不可变的状态更新
- 触摸反馈和可访问性
勾选框虽小,但它是用户与应用交互最频繁的元素。把它做好,能显著提升用户体验。
勾选框与任务状态的联动
勾选框不是孤立存在的,它与任务的其他视觉元素有联动:
标题的删除线
当任务完成时,标题会显示删除线:
<Text style={[styles.taskTitle, {color: theme.text}, item.completed && styles.completedText]}>{item.title}</Text>
completedText: {textDecorationLine: 'line-through', opacity: 0.5},
勾选框填充 + 标题删除线,双重视觉反馈,让完成状态更明显。
统计数据更新
勾选框的状态变化会影响统计数据:
const completedTasks = tasks.filter(t => t.completed).length;
const progress = totalTasks > 0 ? (completedTasks / totalTasks) * 100 : 0;
每次切换任务状态,已完成数量和进度百分比都会重新计算。
筛选结果变化
如果当前筛选条件是"待办"或"已完成",切换任务状态可能会让任务从列表中消失或出现。
这些联动让应用感觉是一个整体,而不是孤立的功能堆砌。
勾选框的边界情况
在实际使用中,需要考虑一些边界情况:
快速连续点击
用户可能快速连续点击勾选框。当前的实现没有防抖,每次点击都会触发状态更新。
如果需要防抖,可以这样:
const [isToggling, setIsToggling] = useState(false);
const handleToggle = async () => {
if (isToggling) return;
setIsToggling(true);
toggleTask(item.id);
setTimeout(() => setIsToggling(false), 300);
};
网络同步
如果任务数据需要同步到服务器,点击勾选框后应该:
- 立即更新本地状态(乐观更新)
- 发送请求到服务器
- 如果请求失败,回滚本地状态
离线支持
如果应用需要离线支持,状态变化应该先保存到本地存储,等网络恢复后再同步。
勾选框的测试
如果要为勾选框写测试,需要覆盖这些场景:
渲染测试
// 未完成任务应该显示空心勾选框
expect(screen.queryByText('✓')).toBeNull();
// 已完成任务应该显示实心勾选框和勾选符号
expect(screen.getByText('✓')).toBeTruthy();
交互测试
// 点击勾选框应该切换任务状态
fireEvent.press(checkbox);
expect(toggleTask).toHaveBeenCalledWith(taskId);
样式测试
// 已完成任务的勾选框应该有背景色
expect(checkboxInner).toHaveStyle({backgroundColor: theme.accent});
从勾选框看组件设计
勾选框是一个很好的组件设计案例。它展示了几个重要的设计原则:
单一职责
勾选框只负责显示和切换完成状态,不处理其他逻辑。
可组合性
勾选框可以放在任何需要的地方,不依赖特定的父组件。
可定制性
通过 props 可以定制颜色、大小等属性(虽然当前实现是硬编码的)。
状态外置
勾选框不管理自己的状态,状态由父组件传入。这让状态管理更清晰。
如果要把勾选框抽取为独立组件,可以这样设计:
interface CheckboxProps {
checked: boolean;
onToggle: () => void;
size?: number;
color?: string;
}
const Checkbox: React.FC<CheckboxProps> = ({
checked,
onToggle,
size = 24,
color = '#6c5ce7',
}) => {
return (
<TouchableOpacity onPress={onToggle}>
<View style={[
styles.checkboxInner,
{
width: size,
height: size,
borderRadius: size / 2,
borderColor: color,
backgroundColor: checked ? color : 'transparent',
},
]}>
{checked && <Text style={styles.checkmark}>✓</Text>}
</View>
</TouchableOpacity>
);
};
这样的组件更通用,可以在不同场景复用。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐



所有评论(0)