鸿蒙原生 ArkTS Grid 布局实战:从零构建 Emoji Picker 表情面板


一、为什么选择 Grid 布局实现表情面板?

在即时通讯、社交评论、笔记编辑等应用中,表情选择器(Emoji Picker) 几乎是标配功能。用户期望它响应迅速、分类清晰、视觉舒适。而实现这样一个多行多列、支持分类切换和选中高亮的面板,恰好是 Grid 布局 的典型应用场景。

传统的线性布局(Column + Row 嵌套)虽然在简单场景下够用,但当表情数量增长到数百个时,会遇到以下问题:

  1. 代码冗余 —— 每个表情需手动排列,难以维护;
  2. 行列不均 —— 表情长度不同可能导致参差不齐;
  3. 性能损耗 —— 深层嵌套降低渲染效率;
  4. 扩展困难 —— 增删表情或调整列数需大改布局。

Grid 布局通过 行列模板(rowsTemplate / columnsTemplate) 以声明式方式描述网格结构,配合 GridItem 子组件自动填充,大幅降低布局复杂度。HarmonyOS NEXT 的 Grid 组件更是深度优化了滚动复用和懒加载,能够轻松承载数百个表情节点而不掉帧。


二、HarmonyOS NEXT(API 24)ArkUI 布局概览

在深入代码之前,有必要先了解 HarmonyOS NEXT 在 API 24 版本中 ArkUI 布局体系的一些关键变化:

2.1 全局 API 无需 import

与早期版本不同,API 24 的 ArkUI 将核心布局组件(ColumnRowGridGridItemScrollTextDivider 等)以及装饰器(@Entry@Component@State 等)全部设计为 全局内置 API。这意味着开发者无需在文件顶部写任何 import 语句即可直接使用这些组件与装饰器。这不仅简化了代码结构,也降低了新手的学习门槛。

2.2 声明式 UI 与状态响应

ArkTS 采用 声明式 + 响应式 编程范式,其核心思想是:UI 是状态的函数。开发者只需描述"数据长什么样,UI 就长什么样",框架自动追踪状态变化并最小化更新渲染树。

具体来说,通过 @State 装饰器标记的成员变量一旦发生变化,框架便会自动重新执行 build() 方法中依赖该变量的 UI 子树。这比命令式的手动刷新(如 setStatenotifyDataChange)更高效、更不易出错。

2.3 布局体系分层

HarmonyOS NEXT 的 ArkUI 布局体系可归纳为三层:

层级 典型组件 职责
容器层 Column, Row, Flex, Stack, Grid 定义子组件的排列方式
滚动层 Scroll, List, Grid(内置滚动) 处理内容溢出时的滚动交互
装饰层 Divider, Blank, Badge 提供视觉辅助与修饰

表情选择器恰好需要这三层的协同配合,是一个绝佳的综合性学习案例。


三、表情面板架构设计

下面是我们即将构建的 Emoji Picker 的整体架构设计,从数据结构到 UI 层级逐一拆解。

3.1 数据模型设计

首先定义表情分类的数据模型。每一个类别包含名称、图标和表情列表:

interface EmojiCategory {
  /** 类别名称(如"笑脸""动物""美食") */
  name: string;
  /** 类别图标(用一个代表性 emoji 字符) */
  icon: string;
  /** 该类别下所有表情的字符串数组 */
  emojis: string[];
}

这种模型极简且可扩展。如果未来需要添加"国旗"或"符号"分类,只需向数组中追加一个新的 EmojiCategory 对象即可,UI 层自动适配,无需修改任何布局代码。

3.2 分类数据概览

本案例内置了 6 个常用分类,覆盖日常生活中最常用的表情:

分类 图标 表情数量 典型表情
笑脸 😊 71 个 😀😂😍🤩😭
动物 🐱 56 个 🐶🐱🐼🐨🦊
美食 🍔 64 个 🍕🍔🍟🌮🍣
活动 64 个 ⚽🏀🎮🎸🏆
旅行 🌍 56 个 ✈️🏖️🗽🗼🌅
物品 💡 64 个 📱💻⌚📷💡

总计约 375 个表情,涵盖 Unicode Emoji 15.0 的大部分常用字符。每个类别的表情数量并不要求完全一致,Grid 会根据 columnsTemplate 自动换行对齐,这正是网格布局的优势所在。

3.3 UI 层级结构

整个页面的 UI 树以 Column 为根容器,从上到下依次排列五个区域:

Column(全屏根容器)
  ├── 标题栏         高度 48vp
  │   └── Text("😊 表情选择器")
  ├── 选中预览区     高度 64vp
  │   ├── Text(选中的表情)  fontSize: 56
  │   └── Text("点击上面的表情即可选中")
  ├── Divider 分割线
  ├── 分类 Tab 栏     高度 48vp(可横向滚动)
  │   └── Scroll → Row → ForEach → Column[icon + name]
  ├── Divider 分割线
  └── 表情网格面板    高度 400vp(可纵向滚动)
       └── Scroll → Grid → ForEach → GridItem[Text(emoji)]

每一层的职责清晰、解耦良好,便于单独测试和复用。


四、Grid 核心属性深度解析

Grid 是 ArkUI 中最强大的布局容器之一,理解其核心属性是高效使用它的关键。

4.1 columnsTemplate —— 列模板

columnsTemplate 定义网格的列数及各列的宽度分配策略,其值是一个以空格分隔的字符串:

"1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr"

这里的 fr 是"fraction"(份数)的单位,类似于 CSS Grid 中的 fr 单位。1fr 表示等分剩余空间的一个份额。上面的字符串表示:将 Grid 总宽度均分为 8 等份,每份宽度相等,即 8 列等宽布局

除了 fr,还支持以下单位:

单位 含义 示例
fr 弹性份数,按比例分配剩余空间 "1fr 2fr" → 第一列占 1/3
vp 虚拟像素(逻辑像素) "80vp 80vp 80vp"
px 物理像素 "100px auto"
% 百分比 "30% 30% 40%"
auto 根据内容自适应 "auto 1fr"

4.2 rowsTemplate —— 行模板

rowsTemplate 的语法与 columnsTemplate 完全一致,用于控制行高的分配。例如 "1fr 1fr 1fr" 表示三行等高。

需要注意的是,如果 Grid 不设置固定高度,行数会自动延伸;如果设置了固定高度且行模板使用 fr 单位,则行高会按比例撑满固定高度。在本例中,Grid 高度设为 400vp,因此行高会在此范围内按 1fr 均分。

4.3 rowsGap 与 columnsGap —— 行列间距

这两个属性控制网格项之间的间距:

.rowsGap(4)     // 行间距 4vp
.columnsGap(4)  // 列间距 4vp

适度的间距避免表情之间挤在一起,提升触控选择时的精准度和视觉舒适度。

4.4 GridItem —— 网格项

每个 GridItem 代表网格中的一个单元格。子组件(如 Text)的宽高默认充满 GridItem。你可以通过 .width().height() 链式调用精细控制每个格子的大小。

GridItem() {
  Text(emoji)
    .fontSize(32)        // 表情字号
    .width('100%')       // 撑满格子宽度
    .height('100%')      // 撑满格子高度
    .textAlign(TextAlign.Center)
}

4.5 Scroll + Grid 组合的滚动复用

当 Grid 的内容超出设定高度时,Grid 本身支持纵向滚动,但我们使用了 Scroll 包裹 Grid。这是因为:

  1. 外层的 Scroll 提供弹簧回弹效果(edgeEffect(EdgeEffect.Spring));
  2. Grid 负责布局和子项复用——当 GridItem 滚出视口时,框架会自动回收并复用,保持流畅滚动性能。

五、响应式状态管理

ArkTS 的响应式系统围绕装饰器展开。本案例使用了两个 @State 变量:

5.1 @State selectedEmoji

@State selectedEmoji: string = '😊';
  • 作用:记录当前用户选中的表情。
  • 关联 UI:预览区的 Text(大号展示)和网格中每个 GridItem 的背景色(高亮判断)。
  • 变化触发:当用户点击任一表情时更新,UI 自动刷新两处依赖点。

5.2 @State activeCategoryIndex

@State activeCategoryIndex: number = 0;
  • 作用:记录当前激活的分类 Tab 索引。
  • 关联 UI:Tab 的选中背景色(半透明蓝 ↔ 透明)和下方 Grid 显示的内容(通过 categories[activeCategoryIndex].emojis 切换)。
  • 变化触发:用户点击分类 Tab 时更新,Grid 内容整个切换为另一组表情。

5.3 响应式数据流图

用户点击 Tab
    ↓
activeCategoryIndex 变化
    ↓
Tab 栏高亮更新 → Grid 切换 emoji 源数组
                        ↓
用户点击表情
    ↓
selectedEmoji 变化
    ↓
预览区文字刷新 → 网格中对应项背景高亮

整个过程无需手动调用 any update()refresh() 方法,框架自动追踪依赖并增量更新,这正是声明式 UI 框架的核心价值。


六、完整代码逐段解读

下面逐段剖析 Index.ets 的核心逻辑。

6.1 文件头与数据模型

/**
 * EmojiPicker —— 基于 Grid 布局的表情选择器(表情面板)
 *
 * 【核心技术】Grid + Emoji
 * 【布局要点】
 *   1. 使用 Grid() 容器实现多行多列的宫格布局
 *   2. rowsTemplate / columnsTemplate 控制行列数量与比例
 *   3. 结合 Scroll 实现可滚动分类导航
 *   4. 通过 @State 响应式数据驱动界面刷新
 *   5. 支持按类别切换表情面板
 *
 * 【关于 import】
 *   在 HarmonyOS NEXT(API 24)中,ArkUI 内置组件以及装饰器
 *   都是全局 API,无需显式 import。因此本文件顶部无 import 语句。
 */

interface EmojiCategory {
  name: string;
  icon: string;
  emojis: string[];
}

这里没有 import 语句,因为所有 ArkUI 组件在 API 24 中都是全局可用的。EmojiCategory 接口定义了数据结构规范,类型安全地约束后续数据。

6.2 结构体与响应式状态

@Entry
@Component
struct Index {
  @State selectedEmoji: string = '😊';
  @State activeCategoryIndex: number = 0;

  private categories: EmojiCategory[] = [ /* 6 个分类,共约 375 个表情 */ ];

  private readonly GRID_COLUMNS: number = 8;
  private readonly SELECTED_EMOJI_SIZE: number = 56;
  private readonly EMOJI_SIZE: number = 32;
  private readonly TAB_HEIGHT: number = 48;
  private readonly PANEL_HEIGHT: number = 400;
  • @Entry 标记该组件为应用入口页面
  • @Component 声明为可复用自定义组件
  • @State 赋予变量状态驱动能力
  • private readonly 常量用于集中管理尺寸参数,方便后续视觉调整

6.3 build() —— UI 构建逻辑

标题栏、预览区、分割线的实现直截了当,这里重点看 Tab 栏Grid 表情面板

Tab 栏:水平可滚动分类导航
Scroll() {
  Row() {
    ForEach(this.categories, (category: EmojiCategory, index: number) => {
      Column() {
        Text(category.icon).fontSize(22)
        Text(category.name).fontSize(11).margin({ top: 2 })
      }
      .width(64)
      .height(this.TAB_HEIGHT)
      .justifyContent(FlexAlign.Center)
      .alignItems(HorizontalAlign.Center)
      .backgroundColor(
        this.activeCategoryIndex === index
          ? '#1A007AFF'      // 选中态:半透明蓝背景
          : Color.Transparent
      )
      .borderRadius(8)
      .onClick(() => {
        this.activeCategoryIndex = index;
      })
    })
  }
  .width('100%')
  .height(this.TAB_HEIGHT)
  .padding({ left: 8, right: 8 })
  .alignItems(VerticalAlign.Center)
}
.width('100%')
.height(this.TAB_HEIGHT)
.scrollBar(BarState.Off)

设计要点:

  • 外层 Scroll 包裹 Row,当分类增加(如添加"国旗""符号"等)时,用户可横向滑动查看更多 Tab;
  • 每个 Tab 是 Column(图标 + 文字垂直排列),宽度固定 64vp,确保触控 Hit 面积足够大;
  • backgroundColor 通过三元表达式根据 activeCategoryIndex 动态切换,实现选中高亮效果;
  • 隐藏 ScrollBar 让界面更整洁。
Grid 表情面板:多行多列可滚动网格
Scroll() {
  Grid() {
    ForEach(
      this.categories[this.activeCategoryIndex].emojis,
      (emoji: string) => {
        GridItem() {
          Text(emoji)
            .fontSize(this.EMOJI_SIZE)
            .width('100%')
            .height('100%')
            .textAlign(TextAlign.Center)
            .backgroundColor(
              this.selectedEmoji === emoji
                ? '#33007AFF'
                : Color.Transparent
            )
            .borderRadius(6)
            .onClick(() => {
              this.selectedEmoji = emoji;
            })
        }
      }
    )
  }
  .columnsTemplate("1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr")
  .rowsTemplate("1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr")
  .rowsGap(4)
  .columnsGap(4)
  .width('100%')
  .height(this.PANEL_HEIGHT)
  .padding({ left: 8, right: 8, top: 4, bottom: 4 })
}
.width('100%')
.height(this.PANEL_HEIGHT)
.scrollBar(BarState.Auto)
.edgeEffect(EdgeEffect.Spring)

设计要点:

  • ForEach 遍历当前分类的 emojis 数组,动态生成 GridItem,每一个 GridItem 内部是一个 Text 组件显示单个 emoji 字符;
  • columnsTemplate("1fr x 8") 声明 8 列等宽(实际代码中用 generateRepeatTemplate 方法动态拼接);
  • 每个表情点击后更新 selectedEmoji,同个表情的 background-color 立即变为浅蓝色高亮,实现了选中状态的即时视觉反馈;
  • edgeEffect(EdgeEffect.Spring) 在滚动到边界时产生弹性回弹效果,提升操作手感的细腻度。

6.4 辅助方法

private generateRepeatTemplate(count: number, unit: string): string {
  const parts: string[] = [];
  for (let i = 0; i < count; i++) {
    parts.push(unit);
  }
  return parts.join(' ');
}

这个辅助方法将 countunit 参数拼接成 Grid 所需的 template 字符串。例如 generateRepeatTemplate(8, '1fr') 返回 "1fr 1fr 1fr 1fr 1fr 1fr 1fr 1fr"。如果后续想从 8 列改为 10 列,只需修改 GRID_COLUMNS 常量即可。


七、API 24 的新特性应用

本案例代码也可以在 API 24(HarmonyOS NEXT 7.0.0)下编译运行。与 API 23 相比,API 24 带来了一些值得关注的改进:

7.1 全局组件注册

在 API 24 中,ArkUI 的所有布局组件和装饰器都预先注册在全局命名空间中,无需手动 import。这不仅减少了文件顶部的样板代码,还避免了因 import 遗漏导致的编译错误。

7.2 更强的类型推断

ArkTS 编译器在 API 24 中增强了类型推断能力。例如 ForEach 中回调参数的类型可以自动从数组类型推断:

ForEach(this.categories, (category, index) => {
  // category 自动推断为 EmojiCategory,无需手动标注
})

这使得代码更简洁且类型安全。

7.3 更完善的属性链式调用

API 24 对所有链式调用的返回类型进行了精细化处理,使得 IDE 的代码补全更准确,错误提示更精确。例如 Text().fontSize().fontColor() 每个步骤都能精确感知当前上下文。


八、最佳实践与性能优化

在实际工程中构建表情选择器时,以下实践经验可以帮助你写出更高质量的应用:

8.1 数据与视图分离

将表情数据定义在单独的文件(如 EmojiData.ts)中,与 Index.ets 的 UI 逻辑解耦。这样不仅方便多人协作,也便于未来从远程配置中心动态拉取表情包。

8.2 使用 LazyForEach 处理超长列表

如果单分类的表情数量超过 200 个(例如完整 Unicode Emoji 库超过 1,800 个),建议改用 LazyForEach 替代 ForEachLazyForEach 只在项目即将进入视口时才渲染对应的 GridItem,大幅降低首屏加载时间。

8.3 适当使用 cachedCount

对于 Grid 内频繁切换分类的场景,可以通过 cachedCount 属性保留当前分类在视口外的部分 GridItem 缓存,切换回来时无需重新创建:

Grid()
  .cachedCount(4)  // 缓存上下各 4 行的 GridItem

8.4 考虑滑动方向设置

由于表情面板以纵向滑动为主,可以显式设置 Grid 的滑动方向,帮助框架做进一步的性能优化:

Scroll() {
  Grid()
    .scrollBar(BarState.Auto)
}
.edgeEffect(EdgeEffect.Spring)

8.5 字体图标 vs emoji 字符

本例直接使用 Unicode emoji 字符(如 '😊'),优点是简单直接,无需额外资源文件。但在跨平台场景下,不同设备的 emoji 渲染样式可能不一致。如果追求品牌统一性,可以考虑使用 自定义字体图标 方式,将设计团队设计的表情以字体文件形式嵌入应用。


九、扩展与改进方向

一个完整的产品级表情选择器往往还需要以下增强:

9.1 搜索功能

增加顶部搜索栏,允许用户输入关键词(如"笑"“动物”)快速过滤表情。可以搭配自定义 Pipe(过滤器)或使用 Array.filter() 实现。

9.2 最近使用

记录用户频繁使用的表情,在分类栏的最左侧增加"最近使用"分类,将该数据持久化到本地 Preferences 或数据库。

9.3 表情肤色选择

对于支持多种肤色的表情(如挥手 👋、握手 🤝),长按弹出肤色选择面板(Fitzpatrick 肤色量表)。这需要额外维护一个"基础 emoji → 肤色变体"的映射数据结构。

9.4 与 TextInput 联动

在实际聊天应用中,点击表情后应将选中的 emoji 插入到 TextInputTextArea 的当前光标位置。这需要借助 TextInputControllerCaretOffset API 实现。

9.5 暗色模式适配

通过 @Styles 和自定义主题变量,为表情面板提供两种配色方案,响应系统的深色/浅色模式切换:

@Styles backgroundColor() {
  .backgroundColor(this.isDarkMode ? '#FF1C1C1E' : '#FFF5F5F5')
}

十、小结

通过本教程,我们以 Emoji Picker 表情选择器 为例,完整实践了 HarmonyOS NEXT(API 24)中 ArkTS Grid 布局的核心用法:

  1. Grid + GridItem 多行多列布局的声明式写法;
  2. Scroll + Grid 组合 实现可滚动的密集网格面板;
  3. @State 响应式变量 驱动 UI 状态变化;
  4. ForEach 动态遍历 生成网格列表项;
  5. Tab 分类切换 与表情选中高亮的交互设计;
  6. 合理的 常量管理辅助函数 提升代码可维护性。

Grid 布局不仅仅是"九宫格"那么简单,它在表情选择器、商品展示、图片画廊、图标面板等场景中都有着广泛的应用。掌握 Grid 的 columnsTemplaterowsTemplateGridItem 的配合使用,能够让你在面对各类宫格类 UI 需求时更得心应手。

希望这篇博客能帮助你在 HarmonyOS 原生开发的路上更进一步,用 Grid 布局创作出更多优秀的应用体验。


附录:完整源码

完整的项目源码位于 entry/src/main/ets/pages/Index.ets,总代码量 369 行,涵盖 6 个分类、约 375 个表情。可直接在 DevEco Studio 中打开并运行。

技术栈:HarmonyOS NEXT · ArkTS · ArkUI · Grid · Scroll
开发工具:DevEco Studio 5.0+
最低兼容:API 24(HarmonyOS NEXT 7.0.0)

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐