【精通篇】打造React Native鸿蒙跨平台开发高级复合组件库开发系列:IndexBar 索引栏(点击索引栏时,会自动跳转到对应的 IndexAnchor 锚点位置)
本文介绍了基于React Native开发的鸿蒙跨平台宫格组件实现方案。该组件采用数据驱动架构,通过动态列宽计算和Flex布局实现响应式设计,适配手机、平板、智慧屏等多种鸿蒙设备。核心特点包括:1) 全维度配置化属性体系,支持列数、样式等灵活调整;2) 原生交互组件确保多终端操作一致性;3) 轻量化资源管理,采用图标染色复用等技术降低适配成本。文章详细解析了组件在布局计算、交互逻辑、视觉适配等方面
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
在鸿蒙(HarmonyOS)全场景分布式应用生态下,宫格(Grid)组件作为功能导航与内容展示的核心UI元素,其跨端开发的核心挑战在于实现灵活的响应式布局、保证多终端交互一致性,同时兼顾不同设备的视觉适配特性。本文将从组件架构、布局设计、交互逻辑、跨端适配等维度,深度解读这套 React Native 宫格组件的技术实现,展现如何构建一套适配手机、平板、智慧屏等鸿蒙全场景设备的标准化宫格组件。
驱动
这套宫格组件采用“数据驱动 + 动态布局计算”的核心架构,是 React Native 适配鸿蒙跨端开发的典型实践。组件通过 TypeScript 接口定义完整的属性配置体系,将布局逻辑与渲染逻辑解耦,既保证了代码的可维护性,又便于针对不同鸿蒙终端进行精细化的布局适配。
interface GridItem {
id: string;
title: string;
icon: keyof typeof GRID_ICONS;
color: string;
badge?: number;
}
interface GridProps {
items: GridItem[];
columns?: number;
onPress?: (item: GridItem) => void;
showBadges?: boolean;
itemStyle?: object;
titleStyle?: object;
}
属性设计覆盖了宫格组件的全维度配置:columns 定义列数,items 承载核心数据,showBadges 控制角标显示,itemStyle/titleStyle 支持样式自定义,onPress 处理点击交互。这种全维度的配置化设计,使得组件无需修改核心逻辑,即可通过属性调整适配鸿蒙不同终端的展示需求——比如智慧屏端可默认使用 3 列布局保证触控区域足够大,手机端默认使用 4 列实现紧凑布局,平板端适配 5 列平衡展示密度与操作体验。
布局
宫格组件的布局系统是跨端适配的核心,其通过动态计算列宽的方式实现响应式布局,保证在鸿蒙多终端下的布局一致性。
列宽动态计算:
const itemWidth = `${100 / columns}%`;
return (
<View style={styles.gridContainer}>
{items.map((item) => (
<TouchableOpacity
key={item.id}
style={[styles.gridItem, { width: itemWidth }, itemStyle]}
onPress={() => onPress && onPress(item)}
>
{/* 宫格项内容 */}
</TouchableOpacity>
))}
</View>
);
列宽计算采用百分比布局方案,是 React Native 适配鸿蒙多终端的关键技术选型:
- 设备无关性:百分比布局基于容器宽度而非固定像素,在鸿蒙手机(窄屏)、平板(宽屏)、智慧屏(超大屏)上均能保持设计预期的列数比例,避免了固定像素布局在不同设备上的列数错乱;
- 动态适配性:通过
100 / columns动态计算每列宽度,支持任意列数配置,无需为不同列数编写单独的布局逻辑,极大提升了组件的灵活性; - 样式组合性:将基础样式
styles.gridItem、动态宽度{ width: itemWidth }、自定义样式itemStyle进行组合,既保证了基础布局的一致性,又支持业务层的个性化定制,适配鸿蒙不同应用的UI设计规范。
容器布局设计
宫格容器采用 Flex 布局(React Native 默认布局方式),这是适配鸿蒙全场景设备的核心布局选择:
- Flex 布局在鸿蒙系统中会被转换为原生的弹性布局逻辑,相比传统的绝对定位布局,具有更高的性能和兼容性;
- 弹性布局自动处理宫格项的换行与对齐,避免了手动计算行高、间距带来的适配问题,尤其适配智慧屏等异形屏设备;
- 结合百分比列宽,Flex 布局能实现“列数固定、宽度自适应”的响应式效果,完美适配鸿蒙多终端的屏幕尺寸差异。
宫格组件的交互逻辑设计兼顾了触控与非触控终端的使用场景,是适配鸿蒙全场景交互的关键。
角标(Badge)实现:
const renderBadge = (badgeCount: number) => {
if (!showBadges || !badgeCount) return null;
return (
<View style={styles.badgeContainer}>
<View style={styles.badge}>
<Text style={styles.badgeText}>
{badgeCount > 99 ? '99+' : badgeCount}
</Text>
</View>
</View>
);
};
角标功能的实现体现了跨端交互的核心设计原则:
- 状态控制:
!showBadges || !badgeCount的条件判断保证无数据时不渲染角标,减少不必要的DOM节点,提升鸿蒙低性能设备的渲染效率; - 数据格式化:
badgeCount > 99 ? '99+' : badgeCount的格式化逻辑,避免了角标数字过长导致的UI变形,适配智慧屏等大屏设备的视觉展示; - 视觉层级:角标容器
badgeContainer通过绝对定位叠加在图标之上,保证在不同尺寸的宫格项中都能保持正确的位置,适配鸿蒙多终端的布局差异。
图标
<View style={[styles.iconContainer, { backgroundColor: `${item.color}20` }]}>
<Image
source={{ uri: GRID_ICONS[item.icon] }}
style={[styles.gridIcon, { tintColor: item.color }]}
/>
{renderBadge(item.badge || 0)}
</View>
图标与色彩的处理逻辑保证了跨端视觉的统一性:
- 色彩透明度处理:
${item.color}20动态生成半透明背景色,既保持了主题色的一致性,又避免了纯色背景导致的视觉压抑,适配鸿蒙系统的设计规范; - 图标染色机制:通过
tintColor属性为图标统一染色,避免了为不同主题色准备多套图标资源,降低了跨端资源适配成本; - 资源轻量化:使用 URI 形式加载图标(
GRID_ICONS[item.icon]),便于对接鸿蒙的分布式资源管理体系,实现多设备间的资源共享。
点击交互:
<TouchableOpacity
key={item.id}
style={[styles.gridItem, { width: itemWidth }, itemStyle]}
onPress={() => onPress && onPress(item)}
>
{/* 宫格项内容 */}
</TouchableOpacity>
点击交互基于 React Native 原生 TouchableOpacity 组件实现,是适配鸿蒙全场景交互的最佳实践:
- 原生交互反馈:
TouchableOpacity在鸿蒙系统中会被转换为对应的原生可点击组件,手机端的触控反馈、平板端的按压反馈、智慧屏端的焦点反馈均能原生适配,无需自定义交互反馈逻辑; - 安全的回调处理:
onPress && onPress(item)的条件调用,避免了未传入回调函数时的运行时错误,保证了组件在鸿蒙不同集成模式下的稳定性; - 触控区域适配:宫格项的宽度基于百分比计算,保证在不同终端的触控区域大小合理——智慧屏端的宫格项足够大,避免遥控器操作的误触;手机端的宫格项紧凑,保证屏幕空间的高效利用。
在 GridComponentApp 主组件中,展示了宫格组件与业务逻辑的集成方式,体现了 React Native 鸿蒙跨端开发的最佳实践:
多场景布局:
组件展示了4列、3列、5列、自定义样式等多场景的使用方式,覆盖了鸿蒙手机、平板、智慧屏的典型使用场景:
- 手机端:展示4列紧凑布局,适配小屏设备的空间限制;
- 平板端:展示5列布局,平衡展示密度与操作体验;
- 智慧屏端:展示3列布局,增大触控区域,适配遥控器操作。
样式自定义:
<Grid
items={gridItems.slice(0, 8)}
columns={4}
onPress={handleGridPress}
itemStyle={{ margin: 8 }}
titleStyle={{ fontSize: 12, fontWeight: '600' }}
/>
通过 itemStyle 和 titleStyle 支持样式自定义,是组件适配鸿蒙不同应用UI规范的关键:
- 业务层可根据鸿蒙不同终端的设计要求,调整宫格项的间距、字号、字重等样式;
- 样式自定义基于 React Native 的样式组合机制,保证了基础样式与自定义样式的兼容性,避免样式冲突;
- 字号使用 DP 单位,保证在不同屏幕密度的鸿蒙设备上视觉大小一致。
这套宫格组件在 React Native 适配鸿蒙的过程中,贯穿了以下核心优化思路:
嵌套
宫格组件的DOM结构保持极简,每一个宫格项仅包含图标容器、标题文本、角标(可选)三层结构,避免了过度嵌套导致的渲染性能问题,尤其适配鸿蒙低性能设备的渲染能力。
轻量化
- 图标资源通过
tintColor染色复用,避免多套图标资源的冗余; - 角标采用纯 View 实现,避免图片资源的加载开销,提升鸿蒙设备的启动速度。
交互
- 宫格项的点击区域足够大,适配智慧屏遥控器的操作精度;
- 点击反馈基于原生组件,保证不同终端的交互体验一致。
状态
通过 showBadges、columns 等属性控制组件状态,保证在鸿蒙分布式场景下,多设备间的组件状态可通过属性同步,实现一致的展示效果。
总结
这套 React Native 宫格组件不仅实现了多列布局、角标提示、样式自定义等核心功能,更构建了一套完整的鸿蒙跨端适配体系。核心要点可总结为:
- 架构层面:全维度配置化属性设计,布局与渲染解耦,适配鸿蒙多终端的展示需求;
- 布局层面:百分比动态列宽 + Flex 布局,保证多终端布局的响应式适配;
- 交互层面:原生交互组件 + 场景化视觉提示,适配鸿蒙触控/非触控终端的交互习惯;
- 资源层面:图标染色复用 + 纯代码角标实现,降低跨端资源适配成本。
在实际的鸿蒙跨端项目中,可基于该组件进一步扩展,比如支持鸿蒙系统的深色模式适配、对接分布式数据管理实现多设备宫格数据同步、适配智慧屏的焦点导航逻辑,充分发挥 React Native “一次开发,多端部署”的技术优势,构建真正适配鸿蒙全场景生态的宫格组件。
本文将深入分析一个功能丰富的 React Native 宫格组件实现,该组件采用了现代化的函数式组件架构,支持自定义列数、徽章显示、样式定制等多种功能,同时兼顾了 React Native 与 HarmonyOS 的跨端兼容性。
接口设计
组件首先通过 TypeScript 接口定义了核心数据结构和配置选项:
interface GridItem {
id: string;
title: string;
icon: keyof typeof GRID_ICONS;
color: string;
badge?: number;
}
interface GridProps {
items: GridItem[];
columns?: number;
onPress?: (item: GridItem) => void;
showBadges?: boolean;
itemStyle?: object;
titleStyle?: object;
}
这种类型定义方式体现了良好的 TypeScript 实践,通过可选属性和字面量类型,提供了清晰的配置选项和类型约束,确保了组件使用时的类型安全。特别值得注意的是,icon 属性使用了 keyof typeof GRID_ICONS 类型,确保了只能使用预定义的图标键值。
核心实现
宫格组件的核心实现逻辑主要体现在以下几个方面:
-
动态列数计算:
const itemWidth = `${100 / columns}%`;通过简单的数学计算,根据列数动态确定每个宫格项的宽度,实现了灵活的列数配置。
-
徽章显示逻辑:
const renderBadge = (badgeCount: number) => { if (!showBadges || !badgeCount) return null; return ( <View style={styles.badgeContainer}> <View style={styles.badge}> <Text style={styles.badgeText}> {badgeCount > 99 ? '99+' : badgeCount} </Text> </View> </View> ); };徽章显示逻辑考虑了多种情况:
- 当
showBadges为 false 时不显示徽章 - 当
badgeCount为 0 时不显示徽章 - 当
badgeCount大于 99 时显示 “99+”,避免徽章过大
- 当
-
组件渲染逻辑:
return ( <View style={styles.gridContainer}> {items.map((item) => ( <TouchableOpacity key={item.id} style={[styles.gridItem, { width: itemWidth }, itemStyle]} onPress={() => onPress && onPress(item)} > <View style={[styles.iconContainer, { backgroundColor: `${item.color}20` }]}> <Image source={{ uri: GRID_ICONS[item.icon] }} style={[styles.gridIcon, { tintColor: item.color }]} /> {renderBadge(item.badge || 0)} </View> <Text style={[styles.gridTitle, titleStyle]}>{item.title}</Text> </TouchableOpacity> ))} </View> );渲染逻辑的技术要点:
- 使用
map遍历items数组,为每个 item 生成一个宫格项 - 动态计算宫格项的宽度,实现响应式布局
- 支持自定义样式,通过
itemStyle和titleStyle覆盖默认样式 - 图标容器的背景色使用了
item.color的半透明版本,通过${item.color}20实现 - 图标颜色使用了
item.color,通过tintColor属性实现 - 支持徽章显示,通过
renderBadge函数实现 - 支持点击事件,通过
onPress回调实现
- 使用
主应用
主应用组件展示了宫格组件的使用示例:
const GridComponentApp = () => {
const [gridItems] = useState<GridItem[]>([
{ id: '1', title: '首页', icon: 'home', color: '#3b82f6', badge: 0 },
{ id: '2', title: '搜索', icon: 'search', color: '#10b981', badge: 0 },
{ id: '3', title: '个人中心', icon: 'user', color: '#8b5cf6', badge: 3 },
// 更多宫格项...
]);
const handleGridPress = (item: GridItem) => {
console.log(`点击了: ${item.title}`);
};
// 渲染逻辑
};
主应用组件的技术要点:
- 状态管理:使用 useState 管理宫格项的数据
- 示例展示:展示了宫格组件的各种使用场景,包括不同列数、是否显示徽章、自定义样式等
- 事件处理:实现了宫格项的点击事件处理
- 布局结构:使用 ScrollView 确保在小屏幕设备上的良好显示效果
宫格组件的样式系统设计灵活,支持多种样式定制:
- 基础样式:定义宫格的基本结构和布局
- 响应式布局:通过动态计算宽度实现响应式布局
- 徽章样式:实现了徽章的显示效果,包括徽章容器、徽章背景和文本样式
- 图标样式:定义了图标容器和图标的样式
- 文本样式:定义了宫格标题的样式
- 自定义样式:支持通过 props 传入自定义样式,覆盖默认样式
样式计算
宫格组件的一个技术亮点是动态计算宫格项的宽度:
const itemWidth = `${100 / columns}%`;
这种实现方式的优点:
- 灵活性:可以通过
columns属性轻松调整宫格的列数 - 响应式:宫格项的宽度会根据容器宽度自动调整
- 简洁性:一行代码实现了复杂的宽度计算逻辑
颜色系统
宫格组件的颜色系统设计巧妙:
- 主色调:每个宫格项可以通过
color属性指定主色调 - 背景色:图标容器的背景色使用了主色调的半透明版本,通过
${item.color}20实现 - 图标颜色:图标颜色使用了主色调,通过
tintColor属性实现 - 徽章颜色:徽章使用了统一的红色系,确保视觉一致性
这种颜色设计的优点:
- 视觉统一:每个宫格项有自己的主题色,同时保持整体视觉的一致性
- 层次感:通过半透明背景和实色图标,创造出视觉层次感
- 可定制:用户可以通过
color属性轻松定制每个宫格项的颜色
功能特性
宫格组件实现了丰富的功能特性:
- 自定义列数:支持通过
columns属性自定义宫格的列数,默认为 4 列 - 徽章显示:支持通过
badge属性显示徽章,当数量大于 99 时显示 “99+” - 样式定制:支持通过
itemStyle和titleStyle属性自定义样式 - 点击事件:支持通过
onPress属性处理点击事件 - 响应式布局:宫格项的宽度会根据容器宽度和列数自动调整
- 图标支持:支持通过
icon属性指定图标,使用 Base64 编码的图标 - 颜色定制:支持通过
color属性自定义宫格项的颜色
应用场景
宫格组件适用于多种应用场景:
- 功能菜单:作为应用的主要功能入口,如首页的功能菜单
- 快捷入口:提供常用功能的快捷访问,如设置、个人中心等
- 分类展示:展示商品分类、内容分类等
- 导航菜单:作为应用内的导航菜单,如底部导航栏的替代品
- 数据展示:展示统计数据、状态信息等
- 个性化推荐:展示个性化推荐内容或功能
宫格组件在设计时充分考虑了跨端兼容性,主要体现在以下几个方面:
- 组件兼容性:使用的
View、Text、TouchableOpacity、Image等组件在 React Native 和 HarmonyOS 中都有对应实现 - API 兼容性:使用的
useState等 Hooks 在两个平台上都可用 - 样式兼容性:使用的
StyleSheetAPI 在两个平台上的使用方式基本一致,flexbox 布局在两个平台上都得到了良好支持 - 事件处理:使用的触摸事件在两个平台上都有对应的实现
- 图片加载:使用的
Image组件的source属性在两个平台上都支持 URI 形式的图片
在 React Native 和 HarmonyOS 跨端开发中,宫格组件需要注意以下实现细节:
- 样式差异:不同平台的默认样式可能存在差异,需要确保样式的一致性
- 触摸反馈:不同平台的触摸反馈机制可能存在差异,需要确保交互体验一致
- 性能优化:不同平台的性能特性可能存在差异,需要进行性能测试和优化
- 图片加载:不同平台的图片加载机制可能存在差异,需要确保图片的正确加载和显示
- 响应式布局:不同平台的屏幕尺寸和分辨率可能存在差异,需要确保布局的响应式适配
组件设计模式
- 可配置性:宫格组件提供了丰富的配置选项,如列数、徽章显示、样式定制等,满足不同场景的需求
- 模块化设计:组件结构清晰,逻辑分明,易于理解和维护
- 组合式设计:通过属性的灵活组合,实现了多样化的宫格效果
- 类型安全:充分利用 TypeScript 的类型系统,确保组件使用时的类型安全
- 可扩展性:组件设计考虑了未来的扩展,如添加新的配置选项或功能
状态管理策略
- 无状态设计:宫格组件本身是无状态的,所有数据和配置都通过 props 传入,提高了组件的可复用性
- 单一数据源:主应用组件使用 useState 管理宫格项的数据,确保了数据的一致性
- 状态隔离:组件只关注自己的渲染逻辑,不管理外部状态,避免了状态的混乱和冲突
性能优化策略
- 渲染优化:通过
key属性和条件渲染,优化了组件的渲染性能 - 样式优化:使用
StyleSheet.create定义样式,避免在每次渲染时重新创建样式对象 - 计算优化:将宽度计算等逻辑放在组件渲染函数的顶部,避免在每次渲染时重复计算
- 内存管理:组件结构清晰,没有不必要的内存占用
宫格组件的徽章显示功能实现巧妙:
const renderBadge = (badgeCount: number) => {
if (!showBadges || !badgeCount) return null;
return (
<View style={styles.badgeContainer}>
<View style={styles.badge}>
<Text style={styles.badgeText}>
{badgeCount > 99 ? '99+' : badgeCount}
</Text>
</View>
</View>
);
};
这种实现方式的优点:
- 条件渲染:只有当
showBadges为 true 且badgeCount大于 0 时才渲染徽章 - 边界处理:当
badgeCount大于 99 时显示 “99+”,避免徽章过大 - 模块化:将徽章渲染逻辑封装在独立的函数中,提高了代码的可读性和可维护性
宽度计算实现
宫格组件的宽度计算是其核心技术之一:
const itemWidth = `${100 / columns}%`;
这种实现方式的优点:
- 灵活性:可以通过
columns属性轻松调整宫格的列数 - 响应式:宫格项的宽度会根据容器宽度自动调整
- 简洁性:一行代码实现了复杂的宽度计算逻辑
- 兼容性:使用百分比宽度,在不同平台和设备上都能良好显示
颜色处理实现
宫格组件的颜色处理设计巧妙:
<View style={[styles.iconContainer, { backgroundColor: `${item.color}20` }]}>
<Image
source={{ uri: GRID_ICONS[item.icon] }}
style={[styles.gridIcon, { tintColor: item.color }]}
/>
</View>
这种实现方式的优点:
- 视觉层次:通过半透明背景和实色图标,创造出视觉层次感
- 一致性:图标颜色与背景色使用相同的主色调,确保视觉一致性
- 可定制:用户可以通过
color属性轻松定制每个宫格项的颜色 - 简洁性:一行代码实现了复杂的颜色处理逻辑
实际应用示例
基础宫格示例
<Grid
items={gridItems.slice(0, 8)}
columns={4}
onPress={handleGridPress}
/>
不同列数示例
// 三列宫格
<Grid
items={gridItems.slice(0, 6)}
columns={3}
onPress={handleGridPress}
/>
// 五列宫格
<Grid
items={gridItems.slice(0, 10)}
columns={5}
onPress={handleGridPress}
showBadges={false}
/>
自定义样式示例
<Grid
items={gridItems.slice(0, 8)}
columns={4}
onPress={handleGridPress}
itemStyle={{ margin: 8 }}
titleStyle={{ fontSize: 12, fontWeight: '600' }}
/>
跨端开发最佳实践
组件抽象
在 React Native 和 HarmonyOS 跨端开发中,宫格组件的设计体现了以下最佳实践:
- 平台无关:组件的实现不依赖于特定平台的 API 或特性
- 接口统一:使用统一的接口和配置选项,确保在不同平台上的使用方式一致
- 样式兼容:使用平台无关的样式语法,确保在不同平台上的显示效果一致
- 功能一致:确保在不同平台上的功能和交互体验一致
组件的技术亮点包括:
- 灵活的列数配置:通过
columns属性轻松调整宫格的列数 - 丰富的样式定制:支持通过 props 传入自定义样式,覆盖默认样式
- 智能的徽章显示:支持徽章显示,当数量大于 99 时显示 “99+”
- 巧妙的颜色处理:通过半透明背景和实色图标,创造出视觉层次感
- 跨端兼容性:在设计时充分考虑了 React Native 和 HarmonyOS 的跨端兼容性
该组件可以广泛应用于功能菜单、快捷入口、分类展示等场景,为应用提供了直观、美观的导航和内容展示方式。通过本文的技术解读,希望能够为 React Native 跨端开发提供有益的参考,帮助开发者构建更高质量、更具用户友好性的移动应用。
真实演示案例代码:
import React, { useState } from 'react';
import { View, Text, StyleSheet, ScrollView, Dimensions, TouchableOpacity, Image } from 'react-native';
// Base64 Icons for Grid component
const GRID_ICONS = {
home: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJMMiA5TDQgOWMxLjEgMCAyIC45IDIgMnY3YzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJ2LTdjMC0xLjEuOS0yIDItMmgxbC0xMC03WiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE2IDE1SDgiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik0xNiAxMVg4IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
search: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE1IDE1bDYgNm0tNi02YzMuODcgMCA3LTMuMTMgNy03cy0zLjEzLTctNy03LTcgMy4xMy03IDdjMCAzLjg3IDMuMTMgNyA3IDdaIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
user: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJDNy41OCAyIDQgNS41OCA0IDEwdjRjMCA0LjQyIDMuNTggOCA4IDhzOCAzLjU4IDggOHYtNGMwLTQuNDItMy41OC04LTgtOFYxMFoiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+CjxwYXRoIGQ9Ik04IDEwYzAtMi4yIDEuOC00IDQtNCAyLjIgMCA0IDEuOCA0IDQgMCAyLjItMS44IDQtNCA0cy00LTEuOC00LTRaIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
settings: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDJDNi40OCAyIDIgNi40OCAyIDEyczQuNDggMTAgMTAgMTAgMTAtNC40OCAxMC0xMFMxNy41MiAyIDEyIDJaIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8cGF0aCBkPSJNMTIgNHYxNCIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8cGF0aCBkPSJNNyAxMmgxMCIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiIHN0cm9rZS1saW5lY2FwPSJyb3VuZCIvPgo8L3N2Zz4K',
heart: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTEyIDIxLjVMMSAxMS4yQzEuNjIgOS41MiAzLjQgOC4yNSA1LjQyIDcuNzFDNy40NSA3LjE3IDkuNjMgNy40IDExLjQ1IDguM0wxMiA4LjU4TDEyLjU1IDguM0MxNC4zNyA3LjQgMTYuNTUgNy4xNyAxOC41OCA3LjcxdDIuODEgMi40M0wxMjIuNSAyMS41WiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg==',
star: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0xMiAyLjVMMTQuMDkgNy41MkwxOS41IDEwLjQ4TDE1LjkxIDEzLjQ0TDE3LjE4IDE5LjA5TDEyIDE2LjIzTDYuODIgMTkuMDlMOC4wOSAxMy40NEwyLjY4IDEwLjQ4TDguMDkgNy41MkwxMiAyLjVaIiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
cart: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTcgMkg1Yy0xLjEgMC0xLjk5LjktMS45OSAyTDUgMjFINyIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE5IDIwSDhjLTEuMSAwLTIgLjktMiAydjFjMCAuMS45IDEgMiAxaDExYzEuMSAwIDItLjkgMi0ydi0xYzAtMS4xLS45LTItMi0yeiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE3IDE2YTEgMSAwIDAgMCAxLTF2LTE0YTItMiAwIDAgMC0yLTJoLTRhMiAyIDAgMCAwLTIgMnYxNGMwIC41LjQ0IDEgMSAxaDJ6IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
bell: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggIGQ9Ik0yMSAxMWE4IDggMCAwIDAtLTE2IDBjMCA0LjUgMy41IDguMDYgNy45NCA4LjJhMS4xIDEuMSAwIDAgMCAuMTIgMGMyLjI0LS4wNSA0LjAzLjkzIDUuMjktMi41NEMxOS4xNiAxOC4wOSAyMSAxNS4yNSAyMSAxMUE5IDkgMCAwIDAgMTIgMkExIDAgMCAwIDAgMTIgM1Y1QTUgNSAwIDAgMSAxMCAxMEE1IDUgMCAwIDEgMTIgM0gyMUMyMSA1LjI1IDIxIDggMjEgMTF6IiBzdHJva2U9IiNGRkZGRkYiIHN0cm9rZS13aWR0aD0iMiIvPgo8L3N2Zz4K',
message: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE0IDJIMTBjLTEuMSAwLTIgLjktMiAydjRoLTJjLTEuMSAwLTIgLjktMiAydjEwYzAgMS4xLjkgMiAyIDJoMTJjMS4xIDAgMi0uOSAyLTJWNmMwLTEuMS0uOS0yLTItMmgtMlYyem0wIDR2MmgydjJINHYtNFoiIHN0cm9rZT0iI0ZGRkZGRiIgc3Ryb2tlLXdpZHRoPSIyIi8+Cjwvc3ZnPgo=',
camera: 'data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iMjQiIGhlaWdodD0iMjQiIHZpZXdCb3g9IjAgMCAyNCAyNCIgZmlsbD0ibm9uZSIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj4KPHBhdGggZD0iTTE4IDEzYTIgMiAwIDAgMC0yLTJoLTJhMiAyIDAgMCAwLTIgMnYyaDJ2MmEyIDIgMCAwIDAgMiAyaDJhMiAyIDAgMCAwIDItMnYtMmgydi0yeiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPHBhdGggZD0iTTE4IDEzYTIgMiAwIDAgMC0yLTJoLTJhMiAyIDAgMCAwLTIgMnYyaDJ2MmEyIDIgMCAwIDAgMiAyaDJhMiAyIDAgMCAwIDItMnYtMmgydi0yeiIgc3Ryb2tlPSIjRkZGRkZGIiBzdHJva2Utd2lkdGg9IjIiLz4KPC9zdmc+Cg=='
};
// Grid Item Component
interface GridItem {
id: string;
title: string;
icon: keyof typeof GRID_ICONS;
color: string;
badge?: number;
}
interface GridProps {
items: GridItem[];
columns?: number;
onPress?: (item: GridItem) => void;
showBadges?: boolean;
itemStyle?: object;
titleStyle?: object;
}
const Grid: React.FC<GridProps> = ({
items,
columns = 4,
onPress,
showBadges = true,
itemStyle = {},
titleStyle = {}
}) => {
const itemWidth = `${100 / columns}%`;
const renderBadge = (badgeCount: number) => {
if (!showBadges || !badgeCount) return null;
return (
<View style={styles.badgeContainer}>
<View style={styles.badge}>
<Text style={styles.badgeText}>
{badgeCount > 99 ? '99+' : badgeCount}
</Text>
</View>
</View>
);
};
return (
<View style={styles.gridContainer}>
{items.map((item) => (
<TouchableOpacity
key={item.id}
style={[styles.gridItem, { width: itemWidth }, itemStyle]}
onPress={() => onPress && onPress(item)}
>
<View style={[styles.iconContainer, { backgroundColor: `${item.color}20` }]}>
<Image
source={{ uri: GRID_ICONS[item.icon] }}
style={[styles.gridIcon, { tintColor: item.color }]}
/>
{renderBadge(item.badge || 0)}
</View>
<Text style={[styles.gridTitle, titleStyle]}>{item.title}</Text>
</TouchableOpacity>
))}
</View>
);
};
// Main App Component
const GridComponentApp = () => {
const [gridItems] = useState<GridItem[]>([
{ id: '1', title: '首页', icon: 'home', color: '#3b82f6', badge: 0 },
{ id: '2', title: '搜索', icon: 'search', color: '#10b981', badge: 0 },
{ id: '3', title: '个人中心', icon: 'user', color: '#8b5cf6', badge: 3 },
{ id: '4', title: '设置', icon: 'settings', color: '#f59e0b', badge: 0 },
{ id: '5', title: '收藏', icon: 'heart', color: '#ef4444', badge: 12 },
{ id: '6', title: '星级', icon: 'star', color: '#f97316', badge: 0 },
{ id: '7', title: '购物车', icon: 'cart', color: '#06b6d4', badge: 5 },
{ id: '8', title: '通知', icon: 'bell', color: '#ec4899', badge: 8 },
{ id: '9', title: '消息', icon: 'message', color: '#6366f1', badge: 2 },
{ id: '10', title: '相机', icon: 'camera', color: '#14b8a6', badge: 0 },
{ id: '11', title: '更多', icon: 'settings', color: '#64748b', badge: 0 },
{ id: '12', title: '帮助', icon: 'search', color: '#94a3b8', badge: 0 }
]);
const handleGridPress = (item: GridItem) => {
console.log(`点击了: ${item.title}`);
};
return (
<View style={styles.container}>
<View style={styles.header}>
<Text style={styles.headerTitle}>宫格组件</Text>
<Text style={styles.headerSubtitle}>展示内容或进行页面导航</Text>
</View>
<ScrollView contentContainerStyle={styles.content}>
<View style={styles.gridSection}>
<Text style={styles.sectionTitle}>基础宫格 (4列)</Text>
<Grid
items={gridItems.slice(0, 8)}
columns={4}
onPress={handleGridPress}
/>
</View>
<View style={styles.gridSection}>
<Text style={styles.sectionTitle}>三列宫格</Text>
<Grid
items={gridItems.slice(0, 6)}
columns={3}
onPress={handleGridPress}
/>
</View>
<View style={styles.gridSection}>
<Text style={styles.sectionTitle}>五列宫格</Text>
<Grid
items={gridItems.slice(0, 10)}
columns={5}
onPress={handleGridPress}
showBadges={false}
/>
</View>
<View style={styles.gridSection}>
<Text style={styles.sectionTitle}>自定义样式</Text>
<Grid
items={gridItems.slice(0, 8)}
columns={4}
onPress={handleGridPress}
itemStyle={{ margin: 8 }}
titleStyle={{ fontSize: 12, fontWeight: '600' }}
/>
</View>
<View style={styles.featuresSection}>
<Text style={styles.featuresTitle}>功能特性</Text>
<View style={styles.featureList}>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>灵活的列数配置</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>徽章数字显示</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>丰富的Base64图标</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>自定义样式支持</Text>
</View>
<View style={styles.featureItem}>
<Text style={styles.featureBullet}>•</Text>
<Text style={styles.featureText}>响应式布局</Text>
</View>
</View>
</View>
<View style={styles.usageSection}>
<Text style={styles.usageTitle}>使用说明</Text>
<Text style={styles.usageText}>
宫格组件用于展示内容或进行页面导航,
适用于功能菜单、快捷入口、分类展示等场景。
</Text>
</View>
</ScrollView>
<View style={styles.footer}>
<Text style={styles.footerText}>© 2023 宫格组件 | 现代化UI组件库</Text>
</View>
</View>
);
};
const { width, height } = Dimensions.get('window');
const styles = StyleSheet.create({
container: {
flex: 1,
backgroundColor: '#f1f5f9',
},
header: {
backgroundColor: '#0f172a',
paddingTop: 30,
paddingBottom: 25,
paddingHorizontal: 20,
borderBottomWidth: 1,
borderBottomColor: '#1e293b',
},
headerTitle: {
fontSize: 28,
fontWeight: '700',
color: '#f8fafc',
textAlign: 'center',
marginBottom: 5,
},
headerSubtitle: {
fontSize: 16,
color: '#94a3b8',
textAlign: 'center',
},
content: {
padding: 20,
},
gridSection: {
marginBottom: 30,
},
sectionTitle: {
fontSize: 18,
fontWeight: '600',
color: '#0f172a',
marginBottom: 15,
},
gridContainer: {
flexDirection: 'row',
flexWrap: 'wrap',
},
gridItem: {
alignItems: 'center',
marginBottom: 20,
},
iconContainer: {
width: 60,
height: 60,
borderRadius: 12,
justifyContent: 'center',
alignItems: 'center',
marginBottom: 8,
position: 'relative',
},
gridIcon: {
width: 28,
height: 28,
},
badgeContainer: {
position: 'absolute',
top: -4,
right: -4,
},
badge: {
backgroundColor: '#ef4444',
borderRadius: 10,
minWidth: 20,
height: 20,
justifyContent: 'center',
alignItems: 'center',
paddingHorizontal: 4,
},
badgeText: {
color: '#ffffff',
fontSize: 10,
fontWeight: 'bold',
},
gridTitle: {
fontSize: 14,
color: '#334155',
textAlign: 'center',
fontWeight: '500',
},
featuresSection: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 20,
marginBottom: 25,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.08,
shadowRadius: 2,
},
featuresTitle: {
fontSize: 18,
fontWeight: '600',
color: '#0f172a',
marginBottom: 15,
},
featureList: {
paddingLeft: 15,
},
featureItem: {
flexDirection: 'row',
marginBottom: 10,
},
featureBullet: {
fontSize: 16,
color: '#3b82f6',
marginRight: 8,
},
featureText: {
fontSize: 16,
color: '#64748b',
flex: 1,
},
usageSection: {
backgroundColor: '#ffffff',
borderRadius: 12,
padding: 20,
marginBottom: 30,
elevation: 2,
shadowColor: '#000',
shadowOffset: { width: 0, height: 1 },
shadowOpacity: 0.08,
shadowRadius: 2,
},
usageTitle: {
fontSize: 18,
fontWeight: '600',
color: '#0f172a',
marginBottom: 10,
},
usageText: {
fontSize: 16,
color: '#64748b',
lineHeight: 24,
},
footer: {
paddingVertical: 20,
alignItems: 'center',
backgroundColor: '#0f172a',
},
footerText: {
color: '#94a3b8',
fontSize: 14,
},
});
export default GridComponentApp;

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

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

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

本文介绍了基于React Native开发的鸿蒙跨平台宫格组件实现方案。该组件采用数据驱动架构,通过动态列宽计算和Flex布局实现响应式设计,适配手机、平板、智慧屏等多种鸿蒙设备。核心特点包括:1) 全维度配置化属性体系,支持列数、样式等灵活调整;2) 原生交互组件确保多终端操作一致性;3) 轻量化资源管理,采用图标染色复用等技术降低适配成本。文章详细解析了组件在布局计算、交互逻辑、视觉适配等方面的技术实现,为构建鸿蒙全场景应用提供了标准化UI组件解决方案。
欢迎大家加入开源鸿蒙跨平台开发者社区,一起共建开源鸿蒙跨平台生态。
更多推荐
所有评论(0)