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


在鸿蒙(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 适配鸿蒙多终端的关键技术选型:

  1. 设备无关性:百分比布局基于容器宽度而非固定像素,在鸿蒙手机(窄屏)、平板(宽屏)、智慧屏(超大屏)上均能保持设计预期的列数比例,避免了固定像素布局在不同设备上的列数错乱;
  2. 动态适配性:通过 100 / columns 动态计算每列宽度,支持任意列数配置,无需为不同列数编写单独的布局逻辑,极大提升了组件的灵活性;
  3. 样式组合性:将基础样式 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>
  );
};

角标功能的实现体现了跨端交互的核心设计原则:

  1. 状态控制!showBadges || !badgeCount 的条件判断保证无数据时不渲染角标,减少不必要的DOM节点,提升鸿蒙低性能设备的渲染效率;
  2. 数据格式化badgeCount > 99 ? '99+' : badgeCount 的格式化逻辑,避免了角标数字过长导致的UI变形,适配智慧屏等大屏设备的视觉展示;
  3. 视觉层级:角标容器 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>

图标与色彩的处理逻辑保证了跨端视觉的统一性:

  1. 色彩透明度处理${item.color}20 动态生成半透明背景色,既保持了主题色的一致性,又避免了纯色背景导致的视觉压抑,适配鸿蒙系统的设计规范;
  2. 图标染色机制:通过 tintColor 属性为图标统一染色,避免了为不同主题色准备多套图标资源,降低了跨端资源适配成本;
  3. 资源轻量化:使用 URI 形式加载图标(GRID_ICONS[item.icon]),便于对接鸿蒙的分布式资源管理体系,实现多设备间的资源共享。

点击交互:

<TouchableOpacity
  key={item.id}
  style={[styles.gridItem, { width: itemWidth }, itemStyle]}
  onPress={() => onPress && onPress(item)}
>
  {/* 宫格项内容 */}
</TouchableOpacity>

点击交互基于 React Native 原生 TouchableOpacity 组件实现,是适配鸿蒙全场景交互的最佳实践:

  1. 原生交互反馈TouchableOpacity 在鸿蒙系统中会被转换为对应的原生可点击组件,手机端的触控反馈、平板端的按压反馈、智慧屏端的焦点反馈均能原生适配,无需自定义交互反馈逻辑;
  2. 安全的回调处理onPress && onPress(item) 的条件调用,避免了未传入回调函数时的运行时错误,保证了组件在鸿蒙不同集成模式下的稳定性;
  3. 触控区域适配:宫格项的宽度基于百分比计算,保证在不同终端的触控区域大小合理——智慧屏端的宫格项足够大,避免遥控器操作的误触;手机端的宫格项紧凑,保证屏幕空间的高效利用。

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' }}
/>

通过 itemStyletitleStyle 支持样式自定义,是组件适配鸿蒙不同应用UI规范的关键:

  • 业务层可根据鸿蒙不同终端的设计要求,调整宫格项的间距、字号、字重等样式;
  • 样式自定义基于 React Native 的样式组合机制,保证了基础样式与自定义样式的兼容性,避免样式冲突;
  • 字号使用 DP 单位,保证在不同屏幕密度的鸿蒙设备上视觉大小一致。

这套宫格组件在 React Native 适配鸿蒙的过程中,贯穿了以下核心优化思路:

嵌套

宫格组件的DOM结构保持极简,每一个宫格项仅包含图标容器、标题文本、角标(可选)三层结构,避免了过度嵌套导致的渲染性能问题,尤其适配鸿蒙低性能设备的渲染能力。

轻量化

  • 图标资源通过 tintColor 染色复用,避免多套图标资源的冗余;
  • 角标采用纯 View 实现,避免图片资源的加载开销,提升鸿蒙设备的启动速度。

交互

  • 宫格项的点击区域足够大,适配智慧屏遥控器的操作精度;
  • 点击反馈基于原生组件,保证不同终端的交互体验一致。

状态

通过 showBadgescolumns 等属性控制组件状态,保证在鸿蒙分布式场景下,多设备间的组件状态可通过属性同步,实现一致的展示效果。

总结

这套 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 类型,确保了只能使用预定义的图标键值。

核心实现

宫格组件的核心实现逻辑主要体现在以下几个方面:

  1. 动态列数计算

    const itemWidth = `${100 / columns}%`;
    

    通过简单的数学计算,根据列数动态确定每个宫格项的宽度,实现了灵活的列数配置。

  2. 徽章显示逻辑

    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+”,避免徽章过大
  3. 组件渲染逻辑

    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 生成一个宫格项
    • 动态计算宫格项的宽度,实现响应式布局
    • 支持自定义样式,通过 itemStyletitleStyle 覆盖默认样式
    • 图标容器的背景色使用了 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}`);
  };

  // 渲染逻辑
};

主应用组件的技术要点:

  1. 状态管理:使用 useState 管理宫格项的数据
  2. 示例展示:展示了宫格组件的各种使用场景,包括不同列数、是否显示徽章、自定义样式等
  3. 事件处理:实现了宫格项的点击事件处理
  4. 布局结构:使用 ScrollView 确保在小屏幕设备上的良好显示效果

宫格组件的样式系统设计灵活,支持多种样式定制:

  1. 基础样式:定义宫格的基本结构和布局
  2. 响应式布局:通过动态计算宽度实现响应式布局
  3. 徽章样式:实现了徽章的显示效果,包括徽章容器、徽章背景和文本样式
  4. 图标样式:定义了图标容器和图标的样式
  5. 文本样式:定义了宫格标题的样式
  6. 自定义样式:支持通过 props 传入自定义样式,覆盖默认样式

样式计算

宫格组件的一个技术亮点是动态计算宫格项的宽度:

const itemWidth = `${100 / columns}%`;

这种实现方式的优点:

  1. 灵活性:可以通过 columns 属性轻松调整宫格的列数
  2. 响应式:宫格项的宽度会根据容器宽度自动调整
  3. 简洁性:一行代码实现了复杂的宽度计算逻辑

颜色系统

宫格组件的颜色系统设计巧妙:

  1. 主色调:每个宫格项可以通过 color 属性指定主色调
  2. 背景色:图标容器的背景色使用了主色调的半透明版本,通过 ${item.color}20 实现
  3. 图标颜色:图标颜色使用了主色调,通过 tintColor 属性实现
  4. 徽章颜色:徽章使用了统一的红色系,确保视觉一致性

这种颜色设计的优点:

  1. 视觉统一:每个宫格项有自己的主题色,同时保持整体视觉的一致性
  2. 层次感:通过半透明背景和实色图标,创造出视觉层次感
  3. 可定制:用户可以通过 color 属性轻松定制每个宫格项的颜色

功能特性

宫格组件实现了丰富的功能特性:

  1. 自定义列数:支持通过 columns 属性自定义宫格的列数,默认为 4 列
  2. 徽章显示:支持通过 badge 属性显示徽章,当数量大于 99 时显示 “99+”
  3. 样式定制:支持通过 itemStyletitleStyle 属性自定义样式
  4. 点击事件:支持通过 onPress 属性处理点击事件
  5. 响应式布局:宫格项的宽度会根据容器宽度和列数自动调整
  6. 图标支持:支持通过 icon 属性指定图标,使用 Base64 编码的图标
  7. 颜色定制:支持通过 color 属性自定义宫格项的颜色

应用场景

宫格组件适用于多种应用场景:

  1. 功能菜单:作为应用的主要功能入口,如首页的功能菜单
  2. 快捷入口:提供常用功能的快捷访问,如设置、个人中心等
  3. 分类展示:展示商品分类、内容分类等
  4. 导航菜单:作为应用内的导航菜单,如底部导航栏的替代品
  5. 数据展示:展示统计数据、状态信息等
  6. 个性化推荐:展示个性化推荐内容或功能

宫格组件在设计时充分考虑了跨端兼容性,主要体现在以下几个方面:

  1. 组件兼容性:使用的 ViewTextTouchableOpacityImage 等组件在 React Native 和 HarmonyOS 中都有对应实现
  2. API 兼容性:使用的 useState 等 Hooks 在两个平台上都可用
  3. 样式兼容性:使用的 StyleSheet API 在两个平台上的使用方式基本一致,flexbox 布局在两个平台上都得到了良好支持
  4. 事件处理:使用的触摸事件在两个平台上都有对应的实现
  5. 图片加载:使用的 Image 组件的 source 属性在两个平台上都支持 URI 形式的图片

在 React Native 和 HarmonyOS 跨端开发中,宫格组件需要注意以下实现细节:

  1. 样式差异:不同平台的默认样式可能存在差异,需要确保样式的一致性
  2. 触摸反馈:不同平台的触摸反馈机制可能存在差异,需要确保交互体验一致
  3. 性能优化:不同平台的性能特性可能存在差异,需要进行性能测试和优化
  4. 图片加载:不同平台的图片加载机制可能存在差异,需要确保图片的正确加载和显示
  5. 响应式布局:不同平台的屏幕尺寸和分辨率可能存在差异,需要确保布局的响应式适配

组件设计模式

  1. 可配置性:宫格组件提供了丰富的配置选项,如列数、徽章显示、样式定制等,满足不同场景的需求
  2. 模块化设计:组件结构清晰,逻辑分明,易于理解和维护
  3. 组合式设计:通过属性的灵活组合,实现了多样化的宫格效果
  4. 类型安全:充分利用 TypeScript 的类型系统,确保组件使用时的类型安全
  5. 可扩展性:组件设计考虑了未来的扩展,如添加新的配置选项或功能

状态管理策略

  1. 无状态设计:宫格组件本身是无状态的,所有数据和配置都通过 props 传入,提高了组件的可复用性
  2. 单一数据源:主应用组件使用 useState 管理宫格项的数据,确保了数据的一致性
  3. 状态隔离:组件只关注自己的渲染逻辑,不管理外部状态,避免了状态的混乱和冲突

性能优化策略

  1. 渲染优化:通过 key 属性和条件渲染,优化了组件的渲染性能
  2. 样式优化:使用 StyleSheet.create 定义样式,避免在每次渲染时重新创建样式对象
  3. 计算优化:将宽度计算等逻辑放在组件渲染函数的顶部,避免在每次渲染时重复计算
  4. 内存管理:组件结构清晰,没有不必要的内存占用

宫格组件的徽章显示功能实现巧妙:

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>
  );
};

这种实现方式的优点:

  1. 条件渲染:只有当 showBadges 为 true 且 badgeCount 大于 0 时才渲染徽章
  2. 边界处理:当 badgeCount 大于 99 时显示 “99+”,避免徽章过大
  3. 模块化:将徽章渲染逻辑封装在独立的函数中,提高了代码的可读性和可维护性

宽度计算实现

宫格组件的宽度计算是其核心技术之一:

const itemWidth = `${100 / columns}%`;

这种实现方式的优点:

  1. 灵活性:可以通过 columns 属性轻松调整宫格的列数
  2. 响应式:宫格项的宽度会根据容器宽度自动调整
  3. 简洁性:一行代码实现了复杂的宽度计算逻辑
  4. 兼容性:使用百分比宽度,在不同平台和设备上都能良好显示

颜色处理实现

宫格组件的颜色处理设计巧妙:

<View style={[styles.iconContainer, { backgroundColor: `${item.color}20` }]}>
  <Image
    source={{ uri: GRID_ICONS[item.icon] }}
    style={[styles.gridIcon, { tintColor: item.color }]}
  />
</View>

这种实现方式的优点:

  1. 视觉层次:通过半透明背景和实色图标,创造出视觉层次感
  2. 一致性:图标颜色与背景色使用相同的主色调,确保视觉一致性
  3. 可定制:用户可以通过 color 属性轻松定制每个宫格项的颜色
  4. 简洁性:一行代码实现了复杂的颜色处理逻辑

实际应用示例

基础宫格示例

<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 跨端开发中,宫格组件的设计体现了以下最佳实践:

  1. 平台无关:组件的实现不依赖于特定平台的 API 或特性
  2. 接口统一:使用统一的接口和配置选项,确保在不同平台上的使用方式一致
  3. 样式兼容:使用平台无关的样式语法,确保在不同平台上的显示效果一致
  4. 功能一致:确保在不同平台上的功能和交互体验一致

组件的技术亮点包括:

  1. 灵活的列数配置:通过 columns 属性轻松调整宫格的列数
  2. 丰富的样式定制:支持通过 props 传入自定义样式,覆盖默认样式
  3. 智能的徽章显示:支持徽章显示,当数量大于 99 时显示 “99+”
  4. 巧妙的颜色处理:通过半透明背景和实色图标,创造出视觉层次感
  5. 跨端兼容性:在设计时充分考虑了 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组件解决方案。

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

Logo

腾讯云面向开发者汇聚海量精品云计算使用和开发经验,营造开放的云计算技术生态圈。

更多推荐