欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。


在移动应用界面设计中,分类网格是电商、内容类应用的核心UI范式之一,其既要保证视觉上的规整性,又需兼顾多设备适配与交互体验。本文以一个功能完整的 React Native 分类网格组件为例,从组件架构设计、响应式布局实现、交互状态管理、资源优化等维度展开深度解读,并系统探讨该组件向鸿蒙(HarmonyOS)跨端适配的完整技术路径,为跨端分类网格组件开发提供可落地的实践参考。

一、组件整体架构

该 React Native 分类网格组件遵循「响应式布局 + 状态驱动 + 资源内联 + 跨端兼容」的设计原则,整体架构分为四层:资源层(Base64 图标资源)、布局层(Flex 响应式网格)、交互层(选中状态管理)、展示层(UI 视觉呈现)。组件具备核心的分类展示、选中状态切换功能,同时通过 Base64 图标内联、统一配色体系、细腻的视觉反馈等设计,为鸿蒙跨端适配奠定了低改造成本的架构基础。

从跨端设计视角看,该组件的核心优势体现在三个维度:一是基于 Flexbox 布局体系构建,而 Flex 是 React Native、鸿蒙、Web 等多端通用的布局标准,最大化降低跨端布局适配成本;二是将状态管理逻辑(选中状态切换)与 UI 渲染解耦,核心逻辑可直接复用于鸿蒙端;三是采用 Base64 内嵌图标资源,避免跨端资源路径管理的复杂性,同时提升加载性能。


1. 响应式网格布局:Flexbox 核心应用

响应式网格是该组件的核心视觉特征,其基于 React Native 的 Flexbox 布局体系实现了适配不同屏幕尺寸的 2 列网格布局,这也是跨端适配中最易复用的技术点之一。

(1)网格容器核心配置
gridContainer: {
  flexDirection: 'row',
  flexWrap: 'wrap',
  justifyContent: 'space-between'
}

这三行样式是实现响应式网格的关键:

  • flexDirection: 'row' 设定子元素横向排列,为网格的「列」布局奠定基础;
  • flexWrap: 'wrap' 开启自动换行,当子元素总宽度超过容器宽度时,自动折行显示,这是实现多列网格的核心属性;
  • justifyContent: 'space-between' 保证每行的子元素两端对齐,中间均匀分布间距,让网格布局视觉上更规整。

在实际渲染中,该配置结合子元素的宽度设置(width: '48%'),实现了稳定的 2 列网格:子元素宽度占容器的 48%,剩余 4% 的空间由 space-between 分配为列间距,无需手动计算间距值,适配不同宽度的屏幕(从手机到平板)时,列间距会自动按比例调整,保证布局的一致性。

(2)网格项尺寸与视觉设计
categoryItem: {
  width: '48%',
  backgroundColor: '#fff',
  borderRadius: 16,
  padding: 20,
  marginBottom: 16,
  alignItems: 'center',
  elevation: 3,
  shadowColor: '#000',
  shadowOffset: { width: 0, height: 2 },
  shadowOpacity: 0.1,
  shadowRadius: 4
}

网格项的样式设计兼顾了视觉体验与跨端适配性:

  • 宽度适配:使用百分比宽度(48%)而非固定像素值,保证在不同屏幕宽度下,网格始终保持 2 列布局,这是响应式设计的核心原则;
  • 视觉层次:通过 elevation(Android 原生阴影)和 shadow 系列属性(iOS 阴影)实现卡片式悬浮效果,elevation 是 React Native 对 Android 原生属性的封装,而 shadow 系列属性对应 iOS 的阴影实现,这种双端适配的样式写法保证了不同平台的视觉一致性;
  • 圆角与内边距borderRadius: 16 实现了符合现代移动端设计趋势的圆角效果,padding: 20 保证内容与容器边缘有足够的留白,提升可读性;
  • 居中对齐alignItems: 'center' 让网格项内的图标和文字居中显示,保证视觉上的规整性。
(3)跨端布局兼容性设计

该网格布局的设计完全遵循 Flexbox 标准,未使用 React Native 特有布局属性(如 Dimensions 动态计算宽度),而是采用百分比宽度 + Flex 自动布局的方式,这使得布局逻辑可以无缝迁移到鸿蒙端——鸿蒙 ArkTS 的 Flex 布局属性与 React Native 完全一致,仅需少量样式属性名映射即可复用全部布局逻辑。

2. 状态管理:选中状态的驱动式设计

状态管理是交互型组件的核心,该组件通过 React 的 useState 实现了分类项的选中状态切换,其设计逻辑具备完全的跨端复用性。

(1)状态定义与切换逻辑
const [selectedCategory, setSelectedCategory] = useState<string | null>(null);

const handleCategoryPress = (categoryId: string) => {
  setSelectedCategory(categoryId === selectedCategory ? null : categoryId);
};

这一核心逻辑的设计亮点在于:

  • 状态类型设计selectedCategory 类型为 string | nullstring 存储选中分类的 ID,null 表示无选中项,这种设计既保证了类型安全,又符合「点击选中/再次点击取消」的交互逻辑;
  • 无冗余状态:仅维护一个状态变量即可实现所有分类项的选中状态管理,无需为每个分类项单独维护 isSelected 状态,减少状态管理的复杂度;
  • 纯逻辑设计:状态切换逻辑不依赖任何 React Native 特定 API,仅通过条件判断更新状态,可直接复用于鸿蒙端,只需将 useState 替换为鸿蒙的 @State 装饰器。
(2)状态驱动的 UI 渲染
<TouchableOpacity
  key={category.id}
  style={[
    styles.categoryItem,
    selectedCategory === category.id && styles.selectedItem
  ]}
  onPress={() => handleCategoryPress(category.id)}
>
  {/* 子元素 */}
</TouchableOpacity>

React 的「状态驱动 UI」范式在该组件中得到充分体现:

  • 条件样式:通过 selectedCategory === category.id 判断当前分类项是否被选中,若选中则追加 styles.selectedItem 样式,实现选中状态的视觉反馈;
  • 事件绑定:将 handleCategoryPress 绑定到 onPress 事件,点击时传入当前分类 ID,触发状态更新,进而驱动 UI 重新渲染;
  • key 属性:为列表项设置唯一 key 属性,这是 React 列表渲染的最佳实践,可避免列表重渲染时的性能问题,同时也是鸿蒙端列表渲染的通用要求。
(3)选中状态视觉设计
selectedItem: {
  backgroundColor: '#e8f0fe',
  borderColor: '#4285F4',
  borderWidth: 2
}

选中状态的视觉反馈设计兼顾了辨识度与美观性:

  • 背景色变化:从默认的白色(#fff)变为浅蓝底色(#e8f0fe),形成柔和的视觉区分;
  • 边框强化:增加 2px 的蓝色边框(#4285F4),强化选中状态的辨识度;
  • 配色一致性:边框色与图标主色保持一致,保证视觉体系的统一性。

3. 资源优化:Base64 图标内联方案

图标资源的处理是移动端组件跨端适配的常见痛点,该组件采用 Base64 内联 SVG 图标方案,从根本上解决了跨端资源管理的复杂性。

(1)Base64 图标资源定义
const icons = {
  electronics: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSIyIiB5PSIzIiB3aWR0aD0iMjAiIGhlaWdodD0iMTQiIHJ4PSIyIiByeT0iMiI+PC9yZWN0PjxsaW5lIHgxPSI4IiB5MT0iMjEiIHgyPSI4IiB5Mj0iMjQiPjwvbGluZT48bGluZSB4MT0iMTYiIHkxPSIyMSIgeDI9IjE2IiB5Mj0iMjQiPjwvbGluZT48bGluZSB4MT0iMiIgeTE9IjkiIHgyPSIyMiIgeTI9IjkiPjwvbGluZT48L3N2Zz4=',
  // 其他图标省略...
};

这种资源处理方式在跨端开发中具备四大核心优势:

  • 跨端兼容性:Base64 编码的 SVG 图标可直接在 React Native 和鸿蒙端渲染,无需处理 iOS/Android/鸿蒙不同的资源目录结构、分辨率适配(如 iOS 的 @2x/@3x 图片);
  • 加载性能优化:图标资源随 JS 代码一同加载,无需发起额外的网络请求或本地资源读取操作,减少组件渲染的白屏时间,尤其适合分类网格这种图标密集型场景;
  • 样式可控性:SVG 格式的图标支持通过 tintColor 属性动态修改颜色,适配不同的主题色配置,而位图(PNG/JPG)无法实现这种灵活的颜色调整;
  • 体积优化:SVG 是矢量图形,Base64 编码后的体积远小于同等视觉效果的位图,且矢量图在任意分辨率下无模糊问题,适配不同屏幕密度的设备。
(2)图标容器与配色体系
<View 
  style={[
    styles.iconContainer,
    { backgroundColor: `${category.color}20` }
  ]}
>
  <Image 
    source={{ uri: category.icon }} 
    style={[styles.categoryIcon, { tintColor: category.color }]} 
  />
</View>

图标容器的设计体现了「统一配色 + 动态样式」的跨端设计思路:

  • 背景色动态生成:将分类的主题色(如 #4285F4)拼接透明度值(20 表示 20% 透明度),生成浅色系背景(#4285F420),这种动态样式写法无需预定义多个背景色类,适配任意主题色,且在鸿蒙端可直接复用该逻辑;
  • 图标颜色适配:通过 tintColor 属性将 SVG 图标颜色设置为分类主题色,实现图标与背景的视觉一致性,tintColor 是 React Native 对图片着色的原生支持,鸿蒙端可通过 fill 属性实现相同效果;
  • 容器尺寸规范:图标容器采用固定尺寸(60x60px)+ 圆角(30px)实现圆形容器,保证所有分类项的图标容器视觉统一,固定尺寸在跨端适配中更易保持一致性。

4. 视觉细节设计:提升交互体验的关键

优秀的分类网格组件不仅需要功能完整,更需通过细腻的视觉设计提升用户体验,该组件在视觉细节上的设计同样具备跨端复用价值。

(1)阴影与层级设计
elevation: 3,
shadowColor: '#000',
shadowOffset: { width: 0, height: 2 },
shadowOpacity: 0.1,
shadowRadius: 4

阴影效果是移动端卡片式设计的核心视觉元素,该组件兼顾了 Android 和 iOS 双端的阴影实现:

  • Android 端:使用 elevation 属性实现原生阴影,elevation 是 Android 系统的 Z 轴层级属性,数值越大阴影越明显;
  • iOS 端:使用 shadow 系列属性实现阴影,通过 shadowOffset 控制阴影方向、shadowOpacity 控制透明度、shadowRadius 控制模糊程度;
  • 跨端适配思路:鸿蒙端同样支持阴影的分层实现(shadow 属性),只需将 React Native 的阴影参数映射为鸿蒙的对应属性,即可保持视觉一致性。
(2)信息说明区布局

组件下方的设计说明和布局参数区,同样采用了标准化的 Flex 布局:

infoItem: {
  flexDirection: 'row',
  alignItems: 'center',
  marginBottom: 12
}
  • flexDirection: 'row' 实现项目符号与文本的横向排列;
  • alignItems: 'center' 保证项目符号与文本垂直居中;
  • marginBottom: 12 控制行间距,提升可读性。

这种布局方式在鸿蒙端可直接复用,仅需调整样式属性名即可。

5. 类型安全与可维护性

虽然示例代码中未显式使用 TypeScript,但从跨端开发角度,我们可对核心数据结构进行类型定义,这是提升代码可维护性和跨端适配效率的关键:

interface Category {
  id: string;
  name: string;
  icon: string;
  color: string;
}

const categories: Category[] = [
  { id: '1', name: '电子产品', icon: icons.electronics, color: '#4285F4' },
  // 其他分类...
];

这种类型定义可直接复用于鸿蒙 ArkTS 端,保证跨端数据结构的一致性,避免因数据格式不一致导致的适配错误。

三、鸿蒙跨端适配核心技术路径

1. 技术栈映射:React Native 与鸿蒙 ArkTS 核心对应关系

要实现分类网格组件的跨端适配,首先需明确 React Native 与鸿蒙 ArkTS 的核心技术栈映射关系,这是逻辑复用和 UI 迁移的基础:

React Native 技术点 鸿蒙 ArkTS 对应技术点 适配说明
函数式组件 + useState 结构化组件 + @State React 的 useState 对应鸿蒙的 @State 装饰器,用于管理组件内部状态
TouchableOpacity Button + onClick / GestureDetector 点击交互可通过 Button 组件的 onClick 或手势检测器实现
View Column/Row 根据布局方向选择 Column(垂直)或 Row(水平),对应 React Native 的 View
Text Text 直接替换,样式属性调整
Image Image Base64 图片源格式一致,source={{ uri: xxx }} 对应 src: xxx
StyleSheet.create ComponentStyle + 内联样式 Flex 布局属性完全通用,样式属性名称略有差异
flexDirection/justifyContent/alignItems flexDirection/justifyContent/alignItems 完全一致,无需修改逻辑
tintColor fill SVG 图标颜色映射,React Native 的 tintColor 对应鸿蒙的 fill 属性
elevation/shadow shadow 鸿蒙通过 shadow 属性统一实现阴影效果

2. 核心逻辑复用:状态管理与数据处理

跨端适配的核心原则是「逻辑复用,UI 重写」,该分类网格组件中可直接复用的核心逻辑包括:

  • 分类数据结构(categories 数组)
  • 选中状态切换逻辑(handleCategoryPress)
  • 配色体系(主题色 + 透明度背景色生成逻辑)

这些逻辑不依赖任何 React Native 特定 API,只需少量语法调整即可在鸿蒙端运行:

(1)选中状态逻辑复用(鸿蒙 ArkTS)
@State selectedCategory: string | null = null;

handleCategoryPress(categoryId: string) {
  this.selectedCategory = categoryId === this.selectedCategory ? null : categoryId;
}

核心逻辑完全复用,仅将 React 的 setState 替换为鸿蒙的状态直接赋值,语法更简洁。

(2)配色体系逻辑复用
// 鸿蒙端动态生成背景色
const iconBgColor = `${this.category.color}20`;

背景色拼接逻辑完全复用,鸿蒙端支持相同的字符串拼接方式生成带透明度的颜色值。

3. UI 渲染层适配:鸿蒙组件替换与样式映射

UI 渲染层是跨端适配的主要工作量,需将 React Native 组件替换为鸿蒙 ArkTS 组件,并调整样式属性:

(1)基础组件替换
React Native 组件 鸿蒙 ArkTS 组件 替换说明
View Column/Row 根据布局方向选择,gridContainer 对应 Row(flexDirection: row),categoryItem 内部布局对应 Column
Text Text 直接替换,style 属性中的字体、颜色等属性需映射
Image Image Base64 图片源直接复用,source={{ uri: icon }} 改为 src: icon
TouchableOpacity Button 通过 Button 组件的 onClick 实现点击交互,去除 React Native 特有的 activeOpacity 属性
ScrollView Scroll 鸿蒙的 Scroll 组件对应 React Native 的 ScrollView
(2)核心样式属性映射
React Native 样式属性 鸿蒙 ArkTS 样式属性 映射说明
backgroundColor background.color 背景色属性映射,{ backgroundColor: '#fff' } 改为 { background: { color: '#fff' } }
flexDirection flexDirection 完全一致,支持 ‘row’/‘column’
justifyContent justifyContent 完全一致,支持 ‘space-between’/‘center’ 等
alignItems alignItems 完全一致,支持 ‘center’/‘flex-start’ 等
flexWrap flexWrap 完全一致,支持 ‘wrap’/‘nowrap’
width: ‘48%’ width: ‘48%’ 百分比宽度完全兼容
borderRadius borderRadius 完全一致,数值单位兼容
padding/margin padding/margin 完全一致,数值单位兼容
tintColor fill SVG 图标颜色映射,{ tintColor: color } 改为 { fill: color }
elevation/shadow shadow 鸿蒙通过 shadow 属性统一配置,示例:shadow: { color: '#000', offsetX: 0, offsetY: 2, opacity: 0.1, radius: 4 }
borderColor/borderWidth border.color/border.width 边框属性映射,{ borderColor: '#4285F4', borderWidth: 2 } 改为 { border: { color: '#4285F4', width: 2 } }
(3)完整鸿蒙适配示例(核心组件)
import { Prop, State, Component } from '@ohos/react';
import { Column, Row, Text, Image, Button, Scroll, StyleSheet, FlexAlign, JustifyContent } from '@ohos/react';

// Base64 Icons(完全复用 React Native 端)
const icons = {
  electronics: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSIyIiB5PSIzIiB3aWR0aD0iMjAiIGhlaWdodD0iMTQiIHJ4PSIyIiByeT0iMiI+PC9yZWN0PjxsaW5lIHgxPSI4IiB5MT0iMjEiIHgyPSI4IiB5Mj0iMjQiPjwvbGluZT48bGluZSB4MT0iMTYiIHkxPSIyMSIgeDI9IjE2IiB5Mj0iMjQiPjwvbGluZT48bGluZSB4MT0iMiIgeTE9IjkiIHgyPSIyMiIgeTI9IjkiPjwvbGluZT48L3N2Zz4=',
  fashion: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTcgMjF2LTIuNSI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiAxMnY5Ij48L3BhdGg+PHBhdGggZD0iTTcgMjF2LTIuNWE1IDUgMCAwIDEgMTAgMCI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiAzaC41YTIuNSAyLjUgMCAwIDEgMCA1SDlhMyAzIDAgMCAwIDAgNkgxMCI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiAzaC41YTIuNSAyLjUgMCAwIDEgMCA1SDlhMyAzIDAgMCAwIDAgNkgxMCI+PC9wYXRoPjwvc3ZnPg==',
  home: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAxMmgydjdsMy03IDMgNyA0LTYgMyA2aDJ2LTExaC0yMXYxMXoiPjwvcGF0aD48cG9seWxpbmUgcG9pbnRzPSIxNiAxMyAyMiAxMyAyMiA3IDE2IDciPjwvcG9seWxpbmU+PC9zdmc+',
  beauty: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGNpcmNsZSBjeD0iMTIiIGN5PSI4IiByPSI0Ij48L2NpcmNsZT48bGluZSB4MT0iMS4xOCIgeTE9IjEiIHgyPSIyMi44MiIgeTI9IjIzIj48L2xpbmU+PC9zdmc+',
  sports: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGxpbmUgeDE9IjEyIiB5MT0iOCIgeDI9IjEyIiB5Mj0iMTYiPjwvbGluZT48bGluZSB4MT0iOCIgeTE9IjEyIiB4Mj0iMTYiIHkyPSIxMiI+PC9saW5lPjwvc3ZnPg==',
  books: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6Ij48L3BhdGg+PGxpbmUgeDE9IjE5IiB5MT0iMTMiIHgyPSI1IiB5Mj0iMTMiPjwvbGluZT48bGluZSB4MT0iMTkiIHkxPSIxNSIgeDI9IjUiIHkyPSIxNSI+PC9saW5lPjxsaW5lIHgxPSIxMyIgeTE9IjkiIHgyPSI5IiB5Mj0iMTUiPjwvbGluZT48L3N2Zz4=',
  food: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGxpbmUgeDE9IjgiIHkxPSIyMSIgeDI9IjE2IiB5Mj0iMjEiPjwvbGluZT48bGluZSB4MT0iMTIiIHkxPSIxNyIgeDI9IjEyIiB5Mj0iMjEiPjwvbGluZT48L3N2Zz4=',
  toys: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTIgMmwtMS41IDEuNSAyLjUgMi41LTIuNSAyLjUgMS41IDEuNSAyLjUtMi41IDIuNSAyLjUgMS41LTEuNS0yLjUtMi41IDIuNS0yLjUtMi41LTEuNS0xLjUtMi41IDIuNS0yLjUtMi41eiI+PC9wYXRoPjwvc3ZnPg=='
};

// 分类数据(完全复用 React Native 端)
const categories = [
  { id: '1', name: '电子产品', icon: icons.electronics, color: '#4285F4' },
  { id: '2', name: '时尚服饰', icon: icons.fashion, color: '#EA4335' },
  { id: '3', name: '家居生活', icon: icons.home, color: '#FBBC05' },
  { id: '4', name: '美妆护肤', icon: icons.beauty, color: '#34A853' },
  { id: '5', name: '运动户外', icon: icons.sports, color: '#4285F4' },
  { id: '6', name: '图书音像', icon: icons.books, color: '#EA4335' },
  { id: '7', name: '食品饮料', icon: icons.food, color: '#FBBC05' },
  { id: '8', name: '玩具娱乐', icon: icons.toys, color: '#34A853' }
];

// 鸿蒙分类网格组件实现
export default class CategoryGrid extends Component {
  @State selectedCategory: string | null = null;

  // 选中状态切换逻辑(复用 React Native 核心逻辑)
  handleCategoryPress(categoryId: string) {
    this.selectedCategory = categoryId === this.selectedCategory ? null : categoryId;
  }

  build() {
    return (
      <Scroll style={styles.container}>
        <Column style={styles.header}>
          <Text style={styles.title}>分类网格布局</Text>
          <Text style={styles.subtitle}>响应式网格设计示例</Text>
        </Column>

        <Row style={styles.gridContainer}>
          {categories.map((category) => {
            // 动态计算选中状态样式
            const isSelected = this.selectedCategory === category.id;
            const iconBgColor = `${category.color}20`;
            
            return (
              <Button
                key={category.id}
                style={[
                  styles.categoryItem,
                  isSelected && styles.selectedItem
                ]}
                onClick={() => this.handleCategoryPress(category.id)}
              >
                <Column style={styles.categoryItemInner}>
                  <Column style={[styles.iconContainer, { background: { color: iconBgColor } }]}>
                    <Image 
                      src={category.icon} 
                      style={[styles.categoryIcon, { fill: category.color }]} 
                    />
                  </Column>
                  <Text style={styles.categoryName}>{category.name}</Text>
                </Column>
              </Button>
            );
          })}
        </Row>

        {/* 设计说明区(复用布局逻辑) */}
        <Column style={styles.infoSection}>
          <Text style={styles.infoTitle}>设计要点</Text>
          
          {[
            '使用 Flexbox 实现响应式网格布局',
            '每个项目保持固定宽高比',
            '支持选中状态反馈',
            '图标采用 Base64 编码,减少网络请求',
            '配色方案统一协调'
          ].map((text, index) => (
            <Row style={styles.infoItem} key={`info-${index}`}>
              <Column style={styles.bulletPoint} />
              <Text style={styles.infoText}>{text}</Text>
            </Row>
          ))}
        </Column>

        {/* 布局参数说明区 */}
        <Column style={styles.paramsSection}>
          <Text style={styles.paramsTitle}>关键参数</Text>
          <Row style={styles.paramRow}>
            <Text style={styles.paramLabel}>列数:</Text>
            <Text style={styles.paramValue}>2 (flexWrap: wrap)</Text>
          </Row>
          <Row style={styles.paramRow}>
            <Text style={styles.paramLabel}>间距:</Text>
            <Text style={styles.paramValue}>16px (margin)</Text>
          </Row>
          <Row style={styles.paramRow}>
            <Text style={styles.paramLabel}>圆角:</Text>
            <Text style={styles.paramValue}>16px (borderRadius)</Text>
          </Row>
          <Row style={styles.paramRow}>
            <Text style={styles.paramLabel}>阴影:</Text>
            <Text style={styles.paramValue}>elevation: 3</Text>
          </Row>
        </Column>
      </Scroll>
    );
  }
}

// 鸿蒙样式定义(映射 React Native 样式)
const styles = StyleSheet.create({
  container: {
    flex: 1,
    background: { color: '#f5f7fa' },
    padding: 20
  },
  header: {
    alignItems: FlexAlign.Center,
    marginBottom: 30,
    paddingTop: 20
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#2c3e50'
  },
  subtitle: {
    fontSize: 16,
    color: '#7f8c8d',
    marginTop: 6
  },
  gridContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: JustifyContent.SpaceBetween
  },
  categoryItem: {
    width: '48%',
    background: { color: '#fff' },
    borderRadius: 16,
    padding: 20,
    marginBottom: 16,
    shadow: {
      color: '#000',
      offsetX: 0,
      offsetY: 2,
      opacity: 0.1,
      radius: 4
    }
  },
  categoryItemInner: {
    alignItems: FlexAlign.Center
  },
  selectedItem: {
    background: { color: '#e8f0fe' },
    border: {
      color: '#4285F4',
      width: 2
    }
  },
  iconContainer: {
    width: 60,
    height: 60,
    borderRadius: 30,
    alignItems: FlexAlign.Center,
    justifyContent: JustifyContent.Center,
    marginBottom: 15
  },
  categoryIcon: {
    width: 30,
    height: 30
  },
  categoryName: {
    fontSize: 16,
    fontWeight: '600',
    color: '#34495e',
    textAlign: 'center'
  },
  infoSection: {
    background: { color: '#fff' },
    borderRadius: 16,
    padding: 20,
    marginTop: 20,
    shadow: {
      color: '#000',
      offsetX: 0,
      offsetY: 2,
      opacity: 0.1,
      radius: 4
    }
  },
  infoTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#2c3e50',
    marginBottom: 15,
    textAlign: 'center'
  },
  infoItem: {
    flexDirection: 'row',
    alignItems: FlexAlign.Center,
    marginBottom: 12
  },
  bulletPoint: {
    width: 8,
    height: 8,
    borderRadius: 4,
    background: { color: '#4285F4' },
    marginRight: 12
  },
  infoText: {
    fontSize: 15,
    color: '#34495e',
    flex: 1
  },
  paramsSection: {
    background: { color: '#fff' },
    borderRadius: 16,
    padding: 20,
    marginTop: 20,
    marginBottom: 30,
    shadow: {
      color: '#000',
      offsetX: 0,
      offsetY: 2,
      opacity: 0.1,
      radius: 4
    }
  },
  paramsTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#2c3e50',
    marginBottom: 15,
    textAlign: 'center'
  },
  paramRow: {
    flexDirection: 'row',
    marginBottom: 10
  },
  paramLabel: {
    fontSize: 15,
    fontWeight: '600',
    color: '#34495e',
    width: 60
  },
  paramValue: {
    fontSize: 15,
    color: '#7f8c8d',
    flex: 1
  }
});

四、跨端适配最佳实践总结

1. 架构设计层面

  • 布局标准化:优先采用 Flexbox 布局体系构建组件,Flex 是 React Native、鸿蒙、Web 通用的布局标准,可最大化降低跨端布局适配成本;
  • 逻辑与 UI 解耦:将状态管理、数据处理等核心逻辑抽离为纯函数,不依赖任何平台特定 API,UI 渲染层则针对不同平台的组件特性单独实现;
  • 资源内联化:图标等小型静态资源优先采用 Base64 内联方式,避免跨端资源路径管理的复杂性,同时提升加载性能;
  • 样式参数化:将配色、尺寸等样式属性参数化(如分类主题色),避免硬编码,提升跨端适配的灵活性。

2. 技术实现层面

  • 核心逻辑复用:分类网格的选中状态切换、数据遍历等核心逻辑可完全复用,仅需调整状态更新的语法(React 的 setState vs 鸿蒙的直接赋值);
  • 样式属性映射:建立 React Native 与鸿蒙样式属性的映射表,批量转换样式属性,避免重复编写样式逻辑;
  • 视觉体验对齐:保证跨端的视觉体验一致性,如阴影效果、圆角、间距等视觉参数需严格对齐,仅调整实现方式;
  • 组件替换标准化:建立 React Native 与鸿蒙组件的一一对应关系,按固定规则替换组件,提升适配效率。

3. 性能优化层面

  • 列表渲染优化:在鸿蒙端使用 LazyForEach 替代普通遍历,实现列表项的懒加载,提升大数据量下的渲染性能;
  • 资源加载优化:Base64 图标资源在鸿蒙端同样具备加载性能优势,无需额外的资源加载逻辑;
  • 状态更新优化:避免频繁的状态更新导致组件重渲染,鸿蒙端可通过 @Watch 装饰器精细化控制状态更新范围。

4. 兼容性处理

  • 平台特有属性适配:React Native 的 elevation(Android 阴影)在鸿蒙端需替换为统一的 shadow 属性;
  • 事件处理适配:React Native 的 onPress 对应鸿蒙的 onClick,需统一事件命名;
  • 尺寸单位适配:保证跨端尺寸单位的一致性,优先使用百分比、px 等通用单位,避免使用 dp/pt 等平台特有单位。

五、扩展场景与进阶优化

1. 多列适配扩展

该组件当前为 2 列布局,可通过参数化列数实现多列适配:

// React Native 端
const columnCount = 3;
const itemWidth = `${(100 - (columnCount - 1) * 2) / columnCount}%`;

// 鸿蒙端
const columnCount = 3;
const itemWidth = `${(100 - (columnCount - 1) * 2) / columnCount}%`;

该逻辑可跨端复用,仅需调整样式赋值方式。

2. 懒加载优化

在分类数量较多时,鸿蒙端可通过 LazyForEach 实现列表项懒加载:

// 鸿蒙端懒加载实现
<Row style={styles.gridContainer}>
  <LazyForEach
    data={categories}
    keyGenerator={(item) => item.id}
    itemBuilder={(category) => {
      // 分类项渲染逻辑
    }}
  />
</Row>

3. 主题适配扩展

可基于当前的配色体系,扩展实现暗黑模式适配:

// React Native 端
const theme = useContext(ThemeContext);
const bgColor = theme === 'dark' ? '#1e1e1e' : '#fff';

// 鸿蒙端
@State theme: string = 'light';
const bgColor = this.theme === 'dark' ? '#1e1e1e' : '#fff';

主题切换逻辑可跨端复用,仅需调整状态管理方式。


真实案例演示场景:

// app.tsx
import React, { useState } from 'react';
import { View, Text, StyleSheet, TouchableOpacity, ScrollView, Image } from 'react-native';

const App = () => {
  const [selectedCategory, setSelectedCategory] = useState<string | null>(null);

  // Base64 icons
  const icons = {
    electronics: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cmVjdCB4PSIyIiB5PSIzIiB3aWR0aD0iMjAiIGhlaWdodD0iMTQiIHJ4PSIyIiByeT0iMiI+PC9yZWN0PjxsaW5lIHgxPSI4IiB5MT0iMjEiIHgyPSI4IiB5Mj0iMjQiPjwvbGluZT48bGluZSB4MT0iMTYiIHkxPSIyMSIgeDI9IjE2IiB5Mj0iMjQiPjwvbGluZT48bGluZSB4MT0iMiIgeTE9IjkiIHgyPSIyMiIgeTI9IjkiPjwvbGluZT48L3N2Zz4=',
    fashion: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTcgMjF2LTIuNSI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiAxMnY5Ij48L3BhdGg+PHBhdGggZD0iTTcgMjF2LTIuNWE1IDUgMCAwIDEgMTAgMCI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiAzaC41YTIuNSAyLjUgMCAwIDEgMCA1SDlhMyAzIDAgMCAwIDAgNkgxMCI+PC9wYXRoPjxwYXRoIGQ9Ik0xMiAzaC41YTIuNSAyLjUgMCAwIDEgMCA1SDlhMyAzIDAgMCAwIDAgNkgxMCI+PC9wYXRoPjwvc3ZnPg==',
    home: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMyAxMmgydjdsMy03IDMgNyA0LTYgMyA2aDJ2LTExaC0yMXYxMXoiPjwvcGF0aD48cG9seWxpbmUgcG9pbnRzPSIxNiAxMyAyMiAxMyAyMiA3IDE2IDciPjwvcG9seWxpbmU+PC9zdmc+',
    beauty: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGNpcmNsZSBjeD0iMTIiIGN5PSI4IiByPSI0Ij48L2NpcmNsZT48bGluZSB4MT0iMS4xOCIgeTE9IjEiIHgyPSIyMi44MiIgeTI9IjIzIj48L2xpbmU+PC9zdmc+',
    sports: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGxpbmUgeDE9IjEyIiB5MT0iOCIgeDI9IjEyIiB5Mj0iMTYiPjwvbGluZT48bGluZSB4MT0iOCIgeTE9IjEyIiB4Mj0iMTYiIHkyPSIxMiI+PC9saW5lPjwvc3ZnPg==',
    books: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTkgM0g1Yy0xLjEgMC0yIC45LTIgMnYxNGMwIDEuMS45IDIgMiAyaDE0YzEuMSAwIDItLjkgMi0yVjVjMC0xLjEtLjktMi0yLTJ6Ij48L3BhdGg+PGxpbmUgeDE9IjE5IiB5MT0HarmonyIgeDI9IjUiIHkyPSI5Ij48L2xpbmU+PGxpbmUgeDE9IjE5IiB5MT0iMTUiIHgyPSI1IiB5Mj0iMTUiPjwvbGluZT48bGluZSB4MT0HarmonyIgeTE9IjkiIHgyPSI5IiB5Mj0iMTUiPjwvbGluZT48L3N2Zz4=',
    food: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48Y2lyY2xlIGN4PSIxMiIgY3k9IjEyIiByPSIxMCI+PC9jaXJjbGU+PGxpbmUgeDE9IjgiIHkxPSIyMSIgeDI9IjE2IiB5Mj0iMjEiPjwvbGluZT48bGluZSB4MT0iMTIiIHkxPSIxNyIgeDI9IjEyIiB5Mj0iMjEiPjwvbGluZT48L3N2Zz4=',
    toys: 'data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSIyNCIgaGVpZ2h0PSIyNCIgdmlld0JveD0iMCAwIDI0IDI0IiBmaWxsPSJub25lIiBzdHJva2U9IiM0Mjg1RjQiIHN0cm9rZS13aWR0aD0iMiIgc3Ryb2tlLWxpbmVjYXA9InJvdW5kIiBzdHJva2UtbGluZWpvaW49InJvdW5kIj48cGF0aCBkPSJNMTIgMmwtMS41IDEuNSAyLjUgMi41LTIuNSAyLjUgMS41IDEuNSAyLjUtMi41IDIuNSAyLjUgMS41LTEuNS0yLjUtMi41IDIuNS0yLjUtMS41LTEuNS0yLjUgMi41LTIuNS0yLjV6Ij48L3BhdGg+PC9zdmc+'
  };

  const categories = [
    { id: '1', name: '电子产品', icon: icons.electronics, color: '#4285F4' },
    { id: '2', name: '时尚服饰', icon: icons.fashion, color: '#EA4335' },
    { id: '3', name: '家居生活', icon: icons.home, color: '#FBBC05' },
    { id: '4', name: '美妆护肤', icon: icons.beauty, color: '#34A853' },
    { id: '5', name: '运动户外', icon: icons.sports, color: '#4285F4' },
    { id: '6', name: '图书音像', icon: icons.books, color: '#EA4335' },
    { id: '7', name: '食品饮料', icon: icons.food, color: '#FBBC05' },
    { id: '8', name: '玩具娱乐', icon: icons.toys, color: '#34A853' }
  ];

  const handleCategoryPress = (categoryId: string) => {
    setSelectedCategory(categoryId === selectedCategory ? null : categoryId);
  };

  return (
    <ScrollView style={styles.container}>
      <View style={styles.header}>
        <Text style={styles.title}>分类网格布局</Text>
        <Text style={styles.subtitle}>响应式网格设计示例</Text>
      </View>

      <View style={styles.gridContainer}>
        {categories.map((category) => (
          <TouchableOpacity
            key={category.id}
            style={[
              styles.categoryItem,
              selectedCategory === category.id && styles.selectedItem
            ]}
            onPress={() => handleCategoryPress(category.id)}
          >
            <View 
              style={[
                styles.iconContainer,
                { backgroundColor: `${category.color}20` }
              ]}
            >
              <Image 
                source={{ uri: category.icon }} 
                style={[styles.categoryIcon, { tintColor: category.color }]} 
              />
            </View>
            <Text style={styles.categoryName}>{category.name}</Text>
          </TouchableOpacity>
        ))}
      </View>

      {/* 设计说明 */}
      <View style={styles.infoSection}>
        <Text style={styles.infoTitle}>设计要点</Text>
        
        <View style={styles.infoItem}>
          <View style={styles.bulletPoint} />
          <Text style={styles.infoText}>使用 Flexbox 实现响应式网格布局</Text>
        </View>
        
        <View style={styles.infoItem}>
          <View style={styles.bulletPoint} />
          <Text style={styles.infoText}>每个项目保持固定宽高比</Text>
        </View>
        
        <View style={styles.infoItem}>
          <View style={styles.bulletPoint} />
          <Text style={styles.infoText}>支持选中状态反馈</Text>
        </View>
        
        <View style={styles.infoItem}>
          <View style={styles.bulletPoint} />
          <Text style={styles.infoText}>图标采用 Base64 编码,减少网络请求</Text>
        </View>
        
        <View style={styles.infoItem}>
          <View style={styles.bulletPoint} />
          <Text style={styles.infoText}>配色方案统一协调</Text>
        </View>
      </View>

      {/* 布局参数说明 */}
      <View style={styles.paramsSection}>
        <Text style={styles.paramsTitle}>关键参数</Text>
        <View style={styles.paramRow}>
          <Text style={styles.paramLabel}>列数:</Text>
          <Text style={styles.paramValue}>2 (flexWrap: wrap)</Text>
        </View>
        <View style={styles.paramRow}>
          <Text style={styles.paramLabel}>间距:</Text>
          <Text style={styles.paramValue}>16px (margin)</Text>
        </View>
        <View style={styles.paramRow}>
          <Text style={styles.paramLabel}>圆角:</Text>
          <Text style={styles.paramValue}>16px (borderRadius)</Text>
        </View>
        <View style={styles.paramRow}>
          <Text style={styles.paramLabel}>阴影:</Text>
          <Text style={styles.paramValue}>elevation: 3</Text>
        </View>
      </View>
    </ScrollView>
  );
};

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: '#f5f7fa',
    padding: 20
  },
  header: {
    alignItems: 'center',
    marginBottom: 30,
    paddingTop: 20
  },
  title: {
    fontSize: 28,
    fontWeight: 'bold',
    color: '#2c3e50'
  },
  subtitle: {
    fontSize: 16,
    color: '#7f8c8d',
    marginTop: 6
  },
  gridContainer: {
    flexDirection: 'row',
    flexWrap: 'wrap',
    justifyContent: 'space-between'
  },
  categoryItem: {
    width: '48%',
    backgroundColor: '#fff',
    borderRadius: 16,
    padding: 20,
    marginBottom: 16,
    alignItems: 'center',
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4
  },
  selectedItem: {
    backgroundColor: '#e8f0fe',
    borderColor: '#4285F4',
    borderWidth: 2
  },
  iconContainer: {
    width: 60,
    height: 60,
    borderRadius: 30,
    alignItems: 'center',
    justifyContent: 'center',
    marginBottom: 15
  },
  categoryIcon: {
    width: 30,
    height: 30
  },
  categoryName: {
    fontSize: 16,
    fontWeight: '600',
    color: '#34495e',
    textAlign: 'center'
  },
  infoSection: {
    backgroundColor: '#fff',
    borderRadius: 16,
    padding: 20,
    marginTop: 20,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4
  },
  infoTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#2c3e50',
    marginBottom: 15,
    textAlign: 'center'
  },
  infoItem: {
    flexDirection: 'row',
    alignItems: 'center',
    marginBottom: 12
  },
  bulletPoint: {
    width: 8,
    height: 8,
    borderRadius: 4,
    backgroundColor: '#4285F4',
    marginRight: 12
  },
  infoText: {
    fontSize: 15,
    color: '#34495e',
    flex: 1
  },
  paramsSection: {
    backgroundColor: '#fff',
    borderRadius: 16,
    padding: 20,
    marginTop: 20,
    marginBottom: 30,
    elevation: 3,
    shadowColor: '#000',
    shadowOffset: { width: 0, height: 2 },
    shadowOpacity: 0.1,
    shadowRadius: 4
  },
  paramsTitle: {
    fontSize: 20,
    fontWeight: 'bold',
    color: '#2c3e50',
    marginBottom: 15,
    textAlign: 'center'
  },
  paramRow: {
    flexDirection: 'row',
    marginBottom: 10
  },
  paramLabel: {
    fontSize: 15,
    fontWeight: '600',
    color: '#34495e',
    width: 60
  },
  paramValue: {
    fontSize: 15,
    color: '#7f8c8d',
    flex: 1
  }
});

export default App;

这段React Native分类网格布局代码实现了一个响应式的产品分类选择界面,其核心原理基于React的组件化思想和Flexbox布局系统。categories数组作为数据源定义了8个不同的商品分类,每个分类包含ID、名称、图标和颜色属性,这种数据结构设计使得组件具备良好的扩展性和维护性。handleCategoryPress函数实现了分类选择的切换逻辑,通过三元运算符判断当前点击的分类是否已选中,如果已选中则取消选择,否则选中该分类,这种设计提供直观的用户交互体验。

从鸿蒙系统适配的角度来看,该代码充分利用了React Native的跨平台特性,在鸿蒙设备上能够获得原生级的性能表现。鸿蒙系统的分布式布局能力与Flexbox的弹性布局理念高度契合,gridContainer容器通过flexWrap属性实现子元素的自动换行,这种布局方式在鸿蒙系统的不同屏幕尺寸设备上能够自适应调整。每个categoryItem通过flex属性设置为1,确保在一行内均匀分布,这种设计在鸿蒙系统的响应式布局中具有重要意义。

分类图标通过Image组件的tintColor属性实现颜色适配,配合backgroundColor的透明度设置,形成视觉层次感。这种图标处理方式在鸿蒙系统的UI设计中非常常见,能够确保图标在不同背景下的可读性。selectedItem样式通过边框颜色和阴影效果突出显示选中状态,这种视觉反馈机制符合鸿蒙系统的交互设计规范,在用户操作时提供清晰的状态指示。

请添加图片描述

UI渲染逻辑通过map方法遍历categories数组生成网格项,每个项包含图标容器和分类名称。图标容器通过backgroundColor属性设置半透明背景色,与category.color属性形成色彩呼应,这种设计在鸿蒙系统的配色方案中具有协调性。分类名称通过Text组件显示,字体大小和颜色经过精心调整,确保在不同设备上的可读性。

设计说明区域通过infoSection容器展示布局的关键设计要点,每个要点通过bulletPoint和infoText组合显示,这种信息架构在鸿蒙系统的文档展示中非常常见。布局参数说明区域通过paramRow和paramLabel/paramValue的组合展示关键配置信息,这种结构化数据显示方式在鸿蒙系统的设置界面中具有实际应用价值。

从鸿蒙系统的技术特性来看,该代码通过React Native的声明式编程范式,将复杂的布局逻辑抽象为简单的状态转换。鸿蒙系统的ArkUI框架同样强调声明式UI开发,这种设计思想的一致性使得应用在鸿蒙设备上能够获得接近原生的性能表现。组件的生命周期管理与鸿蒙系统的应用管理机制保持一致,能够在应用前后台切换时正确处理状态更新和界面渲染。网格布局的性能优化通过虚拟化列表渲染实现,在鸿蒙系统的高性能渲染需求下能够提供稳定的性能表现。


打包

接下来通过打包命令npn run harmony将reactNative的代码打包成为bundle,这样可以进行在开源鸿蒙OpenHarmony中进行使用。

在这里插入图片描述

打包之后再将打包后的鸿蒙OpenHarmony文件拷贝到鸿蒙的DevEco-Studio工程目录去:

在这里插入图片描述

最后运行效果图如下显示:

请添加图片描述

欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。

Logo

Agent 垂直技术社区,欢迎活跃、内容共建。

更多推荐