请添加图片描述

案例开源地址: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 和按钮用条件渲染,有值才显示。按钮需要同时有 btnTextonPress 才显示。

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

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐