目录
[一、核心知识点:Grid 宫格组件 完整技术体系](#一、核心知识点:Grid 宫格组件 完整技术体系)
[1、核心内置 API / 组件 / Hook](#1、核心内置 API / 组件 / Hook)
[2、鸿蒙端 Grid 宫格组件 官方设计 & 样式规范](#2、鸿蒙端 Grid 宫格组件 官方设计 & 样式规范)
[✔️ 布局规范(手机端唯一标准,无其他选型)](#✔️ 布局规范(手机端唯一标准,无其他选型))
[✔️ 样式规范](#✔️ 样式规范)
[✔️ 交互规范](#✔️ 交互规范)
[3、✅ 核心重点:RN 中没有原生 Grid 组件!实现宫格的核心原理(极简必懂)](#3、✅ 核心重点:RN 中没有原生 Grid 组件!实现宫格的核心原理(极简必懂))
[✅ 1、Props 属性封装,所有功能解耦](#✅ 1、Props 属性封装,所有功能解耦)
[✅ 2、双层级布局结构,实现布局解耦](#✅ 2、双层级布局结构,实现布局解耦)
[四、实战: 多功能 Grid 宫格组件](#四、实战: 多功能 Grid 宫格组件)
[五、鸿蒙端 Grid 宫格组件 高频踩坑指南](#五、鸿蒙端 Grid 宫格组件 高频踩坑指南)
[六、扩展用法:多功能 Grid 宫格组件](#六、扩展用法:多功能 Grid 宫格组件)
[✅ 扩展 1:纯图标 / 纯文字宫格](#✅ 扩展 1:纯图标 / 纯文字宫格)
[✅ 扩展 2:横向滚动宫格](#✅ 扩展 2:横向滚动宫格)
[✅ 扩展 3:宫格点击动画](#✅ 扩展 3:宫格点击动画)
[✅ 扩展 4:宫格加载态](#✅ 扩展 4:宫格加载态)
[✅ 扩展 5:宫格排序功能](#✅ 扩展 5:宫格排序功能)
一、核心知识点:Grid 宫格组件 完整技术体系
1、核心内置 API / 组件 / Hook
本次 Grid 宫格组件的所有功能,均基于 React Native 原生能力开发,无任何 npm 依赖包 ,零兼容问题、零打包体积增加,也是鸿蒙端 RN 开发实现宫格的唯一最优技术选型,所有核心能力是开发宫格的必备原生技术,也是 RN 布局的核心基础能力:
| 核心 API / 组件 / Hook | 核心作用 | 鸿蒙手机端特性 |
|---|---|---|
View |
核心布局容器,实现宫格外层换行容器 + 内层子项容器,构建完整宫格结构 | 鸿蒙端flex弹性布局解析无兼容差异,flexWrap自动换行完美生效,无布局错乱问题 |
TouchableOpacity |
宫格子项点击交互载体,实现点击反馈 + 点击事件绑定,核心交互组件 | 鸿蒙原生按压透明反馈效果,设置activeOpacity即可实现丝滑点击反馈,符合鸿蒙交互规范 |
Image/Text |
宫格核心内容载体,实现「图标 + 文字」的标配布局,也是鸿蒙宫格的标准形态 | 鸿蒙端图片resizeMode="contain"无拉伸变形,文字居中无偏移,字号适配精准无模糊问题 |
Dimensions |
获取鸿蒙手机屏幕宽度,动态计算宫格子项宽度,实现完美等分自适应 | 自动适配鸿蒙所有机型屏幕尺寸(小屏 / 大屏 / 折叠屏),宫格永远等宽无错位,核心适配能力 |
useState |
管理宫格选中状态、禁用状态,驱动视图刷新,实现选中高亮的核心 Hook | 响应式更新无延迟,鸿蒙低端机型也能流畅刷新选中样式,无性能损耗 |
StyleSheet.create |
抽离多形态宫格样式:默认态、选中态、禁用态、角标样式、自定义圆角样式 | 鸿蒙端样式引擎原生解析,样式优先级清晰,无错乱 / 层级覆盖问题,全局修改一处生效 |
2、鸿蒙端 Grid 宫格组件 官方设计 & 样式规范
鸿蒙系统对「宫格 (Grid)」这类高频基础布局组件,有极其严格且统一的设计规范 ,这也是企业级鸿蒙 APP 必须遵循的标准,遵循后你的宫格组件和鸿蒙原生应用(如鸿蒙桌面、鸿蒙应用市场、鸿蒙办公 APP)的视觉 / 交互完全一致,是本次开发的核心准则,所有代码均严格贴合该规范开发,也是鸿蒙端宫格的最优视觉体验:
✔️ 布局规范(手机端唯一标准,无其他选型)
- 列数规范:主流业务场景只使用 3 列 / 4 列 两种,4 列适配「首页功能菜单」,3 列适配「分类筛选入口」,禁止自定义零散列数,避免视觉混乱;
- 间距规范:宫格子项的间距统一使用
15px(鸿蒙手机端最优间距),横向 + 纵向间距保持一致,不拥挤不松散,视觉协调; - 宽高规范:宫格子项为 正方形(宽 = 高),是鸿蒙宫格的标准形态,适配所有图标 + 文字的展示场景,无变形问题。
✔️ 样式规范
- 背景样式:默认态浅灰色背景
#F5F5F5,选中态鸿蒙主题蓝浅背景#007DFF10,禁用态浅灰 + 透明度opacity:0.6; - 圆角规范:默认圆角
8px(鸿蒙标准圆角),个性化需求可加大至12px,禁止使用直角,贴合鸿蒙柔化设计理念; - 图标规范:图标尺寸固定
28px,居中显示,默认色值#666666,选中态切换为鸿蒙主题蓝#007DFF; - 文字规范:文字字号
12px,单行显示不换行,默认色值#333333,选中态同步切换主题蓝,图标与文字间距8px。
✔️ 交互规范
- 点击反馈:所有宫格子项必须添加
activeOpacity={0.85},提供鸿蒙原生的按压透明反馈,无点击延迟、无失效问题; - 选中逻辑:点击宫格切换选中状态,选中态展示「高亮边框 + 主题色背景 + 主题色图标文字」,未选中态恢复默认样式;
- 响应逻辑:宫格点击事件无卡顿,选中状态切换即时刷新,禁用态宫格无点击反馈、无事件响应。
3、✅ 核心重点:RN 中没有原生 Grid 组件!实现宫格的核心原理(极简必懂)
这是所有 RN 新手开发宫格时第一个疑惑的核心问题 :React Native 官方没有提供 <Grid> 原生组件,鸿蒙端也没有对应的原生桥接组件。
✅ 结论:RN 实现宫格的唯一最优解 = 基于
View的 Flex 弹性布局纯原生实现,这也是鸿蒙 RN 项目中实现宫格的行业标准方案,无兼容问题、性能最优、适配所有机型,本次所有代码均基于此原理开发。
二、组件封装核心设计思想
本次多功能 Grid 宫格组件的封装,延续了之前头像组件的封装思路,基于 React 组件化封装思想 + 状态驱动样式变更 + props 解耦所有功能,核心设计思想简单清晰,无任何复杂逻辑,也是鸿蒙端封装通用布局组件的最优思路,核心 3 点,保证组件的复用性和扩展性:
✅ 1、Props 属性封装,所有功能解耦
声明统一的 GridItemProps 和 GridProps TS 接口,将「宫格数据源、列数、间距、圆角、选中色、默认选中项」等所有功能都作为 props 参数,组件内部根据 props 的值自动渲染对应样式和布局,外部调用时只需传参,无需关心内部实现,完美解耦。
✅ 2、双层级布局结构,实现布局解耦
宫格组件的 UI 结构是 双层嵌套布局,外层负责换行和等分,内层负责内容展示,所有元素互不干扰,样式修改无影响,也是 RN 封装布局组件的最优结构,永远不会出现布局错乱:
外层容器(控制横向排列、自动换行、间距)
└─ 宫格子项容器(控制宽度、高度、圆角、背景、选中态,循环渲染)
└─ 子项内部(图标+文字/自定义内容,纵向居中布局)
四、实战: 多功能 Grid 宫格组件
javascript
import React, { useState, useRef } from 'react';
import {
View, Text, Image, TouchableOpacity,
StyleSheet, Dimensions, SafeAreaView,
Animated
} from 'react-native';
export interface GridItem {
id: string | number;
icon: string;
label: string;
badge?: number | string;
disabled?: boolean;
}
export interface GridProps {
data: GridItem[];
column?: number;
spacing?: number;
borderRadius?: number;
activeColor?: string;
defaultSelected?: string | number;
onPress?: (item: GridItem) => void;
}
const AnimatedTouchableOpacity = Animated.createAnimatedComponent(TouchableOpacity);
const GridItemCell: React.FC<{
item: GridItem;
itemWH: number;
spacing: number;
borderRadius: number;
activeColor: string;
isSelected: boolean;
isDisabled: boolean;
onPress: () => void;
}> = ({
item,
itemWH,
spacing,
borderRadius,
activeColor,
isSelected,
isDisabled,
onPress
}) => {
const scaleAnim = useRef(new Animated.Value(1)).current;
const handlePressIn = () => {
if (!isDisabled) {
Animated.timing(scaleAnim, {
toValue: 0.95,
duration: 100,
useNativeDriver: true
}).start();
}
};
const handlePressOut = () => {
if (!isDisabled) {
Animated.timing(scaleAnim, {
toValue: 1,
duration: 100,
useNativeDriver: true
}).start();
}
};
return (
<AnimatedTouchableOpacity
style={{
width: itemWH,
height: itemWH,
marginBottom: spacing,
borderRadius,
backgroundColor: isDisabled ? '#EEEEEE' : (isSelected ? `${activeColor}10` : '#F5F5F5'),
borderWidth: isSelected && !isDisabled ? 2 : 0,
borderColor: activeColor,
opacity: isDisabled ? 0.6 : 1,
transform: [{ scale: scaleAnim }],
flexDirection: 'column',
justifyContent: 'center',
alignItems: 'center',
position: 'relative'
}}
activeOpacity={0.85}
onPressIn={handlePressIn}
onPressOut={handlePressOut}
onPress={onPress}
disabled={isDisabled}
>
<Image
source={{ uri: item.icon }}
style={{
width: 32,
height: 32,
marginBottom: 10,
tintColor: isDisabled ? '#999999' : (isSelected ? activeColor : '#666666')
}}
resizeMode="contain"
/>
<Text style={{
fontSize: 13,
color: isDisabled ? '#999999' : (isSelected ? activeColor : '#333333'),
textAlign: 'center'
}}>
{item.label}
</Text>
{item.badge && (
<View style={{
position: 'absolute',
top: 8,
right: 8,
backgroundColor: '#FF4D4F',
borderRadius: 9999,
paddingHorizontal: 6,
paddingVertical: 2,
}}>
<Text style={{ fontSize: 11, color: '#FFFFFF', fontWeight: '500' }}>
{item.badge}
</Text>
</View>
)}
</AnimatedTouchableOpacity>
);
};
const Grid: React.FC<GridProps> = ({
data,
column = 4,
spacing = 18,
borderRadius = 10,
activeColor = '#007DFF',
defaultSelected,
onPress
}) => {
const { width } = Dimensions.get('window');
const itemWH = (width - (column + 1) * spacing) / column;
const [selectedId, setSelectedId] = useState<string | number>(defaultSelected || '');
const handleGridClick = (item: GridItem) => {
if (item.disabled) return;
setSelectedId(item.id);
onPress && onPress(item);
};
const groupDataByColumn = () => {
const groups: GridItem[][] = [];
for (let i = 0; i < data.length; i += column) {
groups.push(data.slice(i, i + column));
}
return groups;
};
const groupedData = groupDataByColumn();
return (
<View style={{ paddingHorizontal: spacing }}>
{groupedData.map((group, groupIndex) => (
<View
key={groupIndex}
style={{
flexDirection: 'row',
justifyContent: 'space-between',
marginBottom: spacing,
}}
>
{group.map((item) => (
<GridItemCell
key={item.id}
item={item}
itemWH={itemWH}
spacing={spacing}
borderRadius={borderRadius}
activeColor={activeColor}
isSelected={selectedId === item.id}
isDisabled={item.disabled || false}
onPress={() => handleGridClick(item)}
/>
))}
</View>
))}
</View>
);
};
const App = () => {
const homeGridData = [
{ id: 1, icon: 'https://img.icons8.com/fluency/96/000000/home.png', label: '首页' },
{ id: 2, icon: 'https://img.icons8.com/fluency/96/000000/shopping-cart.png', label: '商城', badge: 5 },
{ id: 3, icon: 'https://img.icons8.com/fluency/96/000000/order.png', label: '订单', badge: 'NEW' },
{ id: 4, icon: 'https://img.icons8.com/fluency/96/000000/my-profile.png', label: '我的' },
{ id: 5, icon: 'https://img.icons8.com/fluency/96/000000/collect.png', label: '收藏' },
{ id: 6, icon: 'https://img.icons8.com/fluency/96/000000/coupon.png', label: '优惠券' },
{ id: 7, icon: 'https://img.icons8.com/fluency/96/000000/setting.png', label: '设置', disabled: true },
{ id: 8, icon: 'https://img.icons8.com/fluency/96/000000/help.png', label: '帮助' },
];
const categoryGridData = [
{ id: 1, icon: 'https://img.icons8.com/fluency/96/000000/food.png', label: '美食' },
{ id: 2, icon: 'https://img.icons8.com/fluency/96/000000/clothes.png', label: '服饰' },
{ id: 3, icon: 'https://img.icons8.com/fluency/96/000000/electronics.png', label: '数码' },
{ id: 4, icon: 'https://img.icons8.com/fluency/96/000000/book.png', label: '图书' },
];
const handleGridPress = (item: GridItem) => {
console.log('点击了宫格:', item.label);
};
return (
<SafeAreaView style={styles.pageContainer}>
<Text style={styles.pageTitle}>基础宫格 (4列 首页功能菜单)</Text>
<Grid data={homeGridData} column={4} onPress={handleGridPress} />
<Text style={styles.pageTitle}>带选中高亮宫格 (3列 分类筛选)</Text>
<Grid data={categoryGridData} column={3} defaultSelected={1} activeColor='#FF9500' onPress={handleGridPress} />
<Text style={styles.pageTitle}>自定义样式宫格 (圆角+间距定制)</Text>
<Grid data={homeGridData.slice(0,6)} column={3} spacing={22} borderRadius={14} activeColor='#FF4D4F' onPress={handleGridPress} />
</SafeAreaView>
);
};
const styles = StyleSheet.create({
pageContainer: {
flex: 1,
backgroundColor: '#F5F5F5',
paddingTop: 20,
},
pageTitle: {
fontSize: 18,
color: '#333333',
fontWeight: '600',
marginHorizontal: 18,
marginVertical: 20,
},
});
export default App;

五、鸿蒙端 Grid 宫格组件 高频踩坑指南
-
问题 :宫格子项宽度不一致、布局错位、漏行,最常见的宫格问题✅ 根源:没有动态计算宽度,直接写死固定宽度,适配不同屏幕时尺寸不一致✅ 解决方案:使用本次代码的核心公式
itemWH = (width - (column + 1) * spacing) / column动态计算宽度,完美等分,无任何错位(代码已实现)。 -
问题 :TS 报错
Property 'badge' does not exist on type 'GridItem'✅ 根源:未在 TS 接口中声明badge属性,TS 无法推断属性类型✅ 解决方案:在GridItem接口中补充badge?: number | string可选属性(代码已实现),杜绝类型报错。 -
问题 :宫格点击后无任何反馈,体验差,不符合鸿蒙交互规范✅ 根源:用
View封装宫格子项,没有使用TouchableOpacity组件✅ 解决方案:所有宫格子项用TouchableOpacity包裹,并设置activeOpacity={0.85}(代码已实现),鸿蒙原生点击反馈拉满。
六、扩展用法:多功能 Grid 宫格组件
本次封装的宫格组件是「无耦合设计」,所有功能解耦,基于基础代码可快速扩展鸿蒙端常用的进阶功能,无需修改核心组件代码,只需新增 props 和样式即可,全部是企业级开发刚需,无缝集成,一行代码扩展,满足更多业务场景,实用性拉满:
✅ 扩展 1:纯图标 / 纯文字宫格
只需删除宫格子项中的Image或Text组件,即可实现纯图标宫格(适用于导航栏)或纯文字宫格(适用于标签栏),无任何代码改动成本。
✅ 扩展 2:横向滚动宫格
给宫格外层容器添加overflow: 'hidden' + horizontal: true,即可实现横向滚动宫格,适用于分类标签、导航栏场景,鸿蒙 APP 标配功能。
✅ 扩展 3:宫格点击动画
结合 RN 的Animated组件,给宫格点击添加缩放动画,点击时宫格轻微缩小,松开恢复,贴合鸿蒙端的流畅交互体验,无性能损耗。
✅ 扩展 4:宫格加载态
添加loading属性,宫格加载时显示骨架屏 / 占位图,加载完成后显示内容,适用于异步加载数据的场景,提升用户体验。
✅ 扩展 5:宫格排序功能
修改数据源的排序逻辑,可实现宫格按热度、按名称排序,适用于分类筛选、应用列表场景,无缝扩展无兼容问题。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net