rn_for_openharmony狗狗之家app实战-品种对比实现
摘要 本文介绍了狗狗品种对比功能的实现方案。该功能帮助用户比较不同犬种的特性(体型、寿命、性格等),解决养犬选择难题。文章详细讲解了技术实现: 基础架构:使用React Native构建,包含占位页面和Empty组件 Empty组件设计:可配置图标、标题、描述和操作按钮 核心功能逻辑: 状态管理(已选品种、全部品种数据) 品种选择/移除/清空操作 三种状态渲染(未选择/选择中/已选择两个品种) 对
案例开源地址:https://atomgit.com/nutpi/rn_openharmony_dogimg
对比功能的价值
用户在选择养什么狗的时候,经常会纠结几个品种。金毛和拉布拉多哪个更适合家养?哈士奇和萨摩耶哪个更好打理?品种对比功能就是帮用户解决这个问题的。
把两个品种的信息放在一起,体型、寿命、性格一目了然,比来回切换详情页方便多了。
这篇文章会讲解品种对比页面的设计思路和实现方案,涉及到多选状态管理、数据对比展示、Empty 组件的灵活运用。
当前页面的基础结构
先看看现有的代码框架:
import React from 'react';
import {View, StyleSheet} from 'react-native';
import {Header, Empty} from '../../components';
export function BreedComparePage() {
return (
<View style={s.container}>
<Header title="品种对比" />
<Empty icon="⚖️" title="品种对比" desc="选择两个品种进行对比,功能开发中..." />
</View>
);
}
目前是个占位页面,用 Empty 组件提示用户功能开发中。
样式很简单:
const s = StyleSheet.create({
container: {flex: 1, backgroundColor: '#f5f5f5'}
});
接下来讲讲如何把它扩展成完整的对比功能。
Empty 组件的设计
先深入看看 Empty 组件,它在很多场景都能用到:
interface Props {
icon?: string;
title?: string;
desc?: string;
btnText?: string;
onPress?: () => void;
}
五个可选属性:
- icon:显示的图标,默认 📭
- title:主标题,默认"暂无数据"
- desc:描述文字
- btnText:按钮文字
- onPress:按钮点击回调
组件实现:
export function Empty({icon = '📭', title = '暂无数据', desc, btnText, onPress}: Props) {
return (
<View style={s.box}>
<Text style={s.icon}>{icon}</Text>
<Text style={s.title}>{title}</Text>
{desc && <Text style={s.desc}>{desc}</Text>}
{btnText && onPress && (
<TouchableOpacity style={s.btn} onPress={onPress}>
<Text style={s.btnText}>{btnText}</Text>
</TouchableOpacity>
)}
</View>
);
}
参数解构时给了默认值,调用时可以只传需要的属性。
desc 和按钮用条件渲染,有值才显示。按钮需要同时有 btnText 和 onPress 才显示。
Empty 组件的样式
const s = StyleSheet.create({
box: {flex: 1, alignItems: 'center', justifyContent: 'center', padding: 40},
icon: {fontSize: 50, marginBottom: 12},
title: {fontSize: 17, fontWeight: '600', color: '#333'},
desc: {fontSize: 14, color: '#999', marginTop: 6, textAlign: 'center'},
- 容器撑满父元素,内容居中
- 图标字号 50,足够醒目
- 标题加粗,描述用灰色
按钮样式:
btn: {marginTop: 20, paddingHorizontal: 24, paddingVertical: 10, backgroundColor: '#D2691E', borderRadius: 20},
btnText: {fontSize: 14, color: '#fff'},
});
主题色背景,白色文字,圆角胶囊形状。
对比功能的设计思路
完整的对比功能需要这几个部分:
品种选择:用户需要选择两个品种进行对比。可以从品种列表选,也可以搜索选择。
选中状态:需要记录用户选了哪两个品种,用数组存储。
对比展示:把两个品种的信息并排显示,方便对比。
清空重选:用户可以清空选择,重新选择其他品种。
状态设计
对比页面需要管理的状态:
const [selected, setSelected] = useState<Breed[]>([]);
const [breeds, setBreeds] = useState<Breed[]>([]);
const [loading, setLoading] = useState(true);
- selected:已选择的品种数组,最多两个
- breeds:所有品种数据,用于选择
- loading:加载状态
品种数据的类型
回顾一下 Breed 类型,对比时会用到这些字段:
export interface Breed {
id: number;
name: string;
bred_for?: string;
breed_group?: string;
life_span: string;
temperament?: string;
origin?: string;
weight: { metric: string };
height: { metric: string };
reference_image_id?: string;
image?: { url: string };
}
对比时主要展示:
- name:品种名称
- breed_group:品种分组
- life_span:寿命
- height/weight:体型
- temperament:性格特点
- image:图片
选择逻辑的实现
添加品种到对比列表:
const addToCompare = (breed: Breed) => {
if (selected.length >= 2) return;
if (selected.find(b => b.id === breed.id)) return;
setSelected([...selected, breed]);
};
三个检查:
- 已经选了两个,不能再加
- 已经选过这个品种,不重复添加
- 都通过了,添加到数组
从对比列表移除:
const removeFromCompare = (id: number) => {
setSelected(selected.filter(b => b.id !== id));
};
用 filter 过滤掉指定 ID 的品种。
清空选择:
const clearCompare = () => {
setSelected([]);
};
直接设为空数组。
对比展示的布局
两个品种并排显示,用 flexDirection: 'row':
<View style={s.compareRow}>
<View style={s.compareItem}>
{/* 品种A的信息 */}
</View>
<View style={s.compareItem}>
{/* 品种B的信息 */}
</View>
</View>
样式:
compareRow: {flexDirection: 'row'},
compareItem: {flex: 1, padding: 12},
两边各占一半宽度。
对比项的渲染
每个对比维度一行:
const renderCompareRow = (label: string, valueA: string, valueB: string) => (
<View style={s.row}>
<Text style={s.valueLeft}>{valueA}</Text>
<Text style={s.label}>{label}</Text>
<Text style={s.valueRight}>{valueB}</Text>
</View>
);
左边是品种A的值,中间是标签,右边是品种B的值。
使用:
{renderCompareRow('寿命', breedA.life_span, breedB.life_span)}
{renderCompareRow('身高', breedA.height.metric + ' cm', breedB.height.metric + ' cm')}
{renderCompareRow('体重', breedA.weight.metric + ' kg', breedB.weight.metric + ' kg')}
条件渲染的处理
页面有三种状态:
未选择任何品种:显示引导,让用户去选择。
{selected.length === 0 && (
<Empty
icon="⚖️"
title="开始对比"
desc="选择两个品种进行对比"
btnText="选择品种"
onPress={() => navigate('Breeds')}
/>
)}
选了一个品种:显示已选的,提示继续选择。
{selected.length === 1 && (
<View>
<Text>已选择: {selected[0].name}</Text>
<Text>请再选择一个品种</Text>
</View>
)}
选了两个品种:显示对比结果。
{selected.length === 2 && (
<View>
{/* 对比内容 */}
</View>
)}
性格特点的对比
性格是逗号分隔的字符串,对比时可以找出共同点和差异:
const getTemperaments = (breed: Breed) => {
return breed.temperament?.split(', ') || [];
};
const tempsA = getTemperaments(breedA);
const tempsB = getTemperaments(breedB);
const common = tempsA.filter(t => tempsB.includes(t));
const onlyA = tempsA.filter(t => !tempsB.includes(t));
const onlyB = tempsB.filter(t => !tempsA.includes(t));
- common:两个品种都有的性格
- onlyA:只有品种A有的
- onlyB:只有品种B有的
这样展示更有对比价值。
图片对比
顶部并排显示两个品种的图片:
<View style={s.imageRow}>
<Image source={{uri: imgA}} style={s.compareImg} />
<Image source={{uri: imgB}} style={s.compareImg} />
</View>
样式:
imageRow: {flexDirection: 'row', padding: 16},
compareImg: {flex: 1, height: 150, marginHorizontal: 4, borderRadius: 12, backgroundColor: '#eee'},
两张图片各占一半,中间留点间距。
数据兜底处理
对比展示时要处理缺失数据:
const getValue = (value: string | undefined, fallback = '未知') => {
return value || fallback;
};
// 使用
{renderCompareRow('产地', getValue(breedA.origin), getValue(breedB.origin))}
没有数据时显示"未知",避免空白。
从列表页跳转
用户可以从品种列表页选择品种加入对比。需要在 BreedCard 上加一个对比按钮:
<TouchableOpacity onPress={() => addToCompare(breed)}>
<Text>加入对比</Text>
</TouchableOpacity>
或者长按卡片触发:
<TouchableOpacity onLongPress={() => addToCompare(breed)}>
这需要把 addToCompare 方法通过全局状态或 Context 共享出去。
对比数据的持久化
如果用户选了品种后切换页面,选择状态会丢失。可以存到全局状态:
// store.ts
export interface AppState {
favoriteBreeds: number[];
favoriteImages: string[];
darkMode: boolean;
compareBreeds: Breed[]; // 新增
}
这样切换页面后选择还在。
小结
品种对比功能涉及的知识点:
- Empty 组件:灵活的空状态展示,支持图标、标题、描述、按钮
- 多选状态管理:数组存储选中项,添加/移除/清空操作
- 条件渲染:根据选择数量显示不同内容
- 并排布局:flex 布局实现左右对比
- 数据处理:性格特点的交集和差集计算
对比功能看起来简单,但要做好用户体验,细节还是挺多的。从选择到展示到清空,每个环节都要考虑周全。
下一篇讲品种测试页面,是个趣味性的功能,通过问答帮用户找到适合自己的品种。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net
更多推荐


所有评论(0)