【ArkTS开发实战】从零实现行李清单应用,掌握Grid与List核心组件

运行截图:
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

一、引言

在日常生活和旅行准备中,一份清晰、易用的行李清单能帮助我们避免遗漏重要物品。本文将带领读者使用 HarmonyOS 的 ArkTS 声明式 UI 开发框架,从零开始实现一款简洁美观的行李清单应用。通过这个项目,你将深入理解 ArkTS 中 Grid 网格布局组件和 List 列表组件的使用方法,掌握状态管理、数据驱动 UI 更新等核心概念。

本应用的最终效果如下:页面顶部使用 Grid 组件以四列网格的形式展示物品分类(衣物、证件、电子、洗护),点击某个分类后,下方的 List 列表会展示该分类下的所有物品,每个物品都带有一个可勾选的 Checkbox,勾选后物品名称会显示删除线效果,表示已装入行李。


二、项目环境准备

2.1 开发工具

在开始编码之前,请确保你已经安装了以下开发工具:

  • DevEco Studio:HarmonyOS 官方集成开发环境,支持 ArkTS 语言的开发、调试和预览。建议安装最新版本的 DevEco Studio,以获得最佳的开发体验和最新的 API 支持。
  • HarmonyOS SDK:DevEco Studio 通常会在安装过程中自动下载 SDK,请确保 SDK 版本与你的目标设备或模拟器兼容。

2.2 创建项目

打开 DevEco Studio,选择 Create Project,然后选择 Empty Ability 模板。填写项目名称(如 ArkTsPackinglist),包名等信息后,点击 Finish 即可创建一个基础的 HarmonyOS 项目。

项目创建完成后,主要的代码文件位于 entry/src/main/ets/pages/ 目录下,其中 Index.ets 是默认的首页文件,我们将在这个文件中实现行李清单的全部功能。

2.3 ArkTS 简介

ArkTS 是 HarmonyOS 的声明式 UI 开发语言,基于 TypeScript 扩展而来。它在 TypeScript 的基础上增加了声明式 UI 描述语法、状态管理装饰器(如 @State@Prop@Link 等)以及自定义组件的能力。使用 ArkTS,开发者可以通过简洁的声明式代码来描述 UI 的结构和样式,框架会自动根据状态的变化来更新 UI,无需手动操作 DOM。

ArkTS 的核心特性包括:

  • 声明式 UI:通过链式调用描述组件的属性和样式,代码结构清晰、易于维护。
  • 状态驱动:使用 @State 等装饰器标记状态变量,当状态发生变化时,UI 会自动重新渲染。
  • 丰富的组件库:提供了 Text、Image、Button、List、Grid、Column、Row 等丰富的内置组件,满足各种 UI 需求。
  • TypeScript 兼容:完全兼容 TypeScript 的类型系统,支持接口、枚举、泛型等特性。

三、数据模型设计

在编写 UI 之前,我们首先需要设计好应用的数据模型。良好的数据模型设计是实现功能的基础,它决定了我们如何组织和操作数据。

3.1 定义分类枚举

行李清单中的物品按照用途可以分为不同的类别。我们使用 TypeScript 的 enum 关键字来定义分类枚举,这样可以确保分类值的类型安全,避免使用字符串常量时可能出现的拼写错误。

enum CategoryType {
  Clothes,
  Documents,
  Electronics,
  Toiletries
}

在上述枚举中,我们定义了四个分类:

枚举值 含义 对应物品示例
Clothes 衣物 T恤、裤子、外套、内衣、袜子
Documents 证件 身份证、护照、机票、酒店预订单
Electronics 电子 手机、充电器、耳机、充电宝、相机
Toiletries 洗护 牙刷、牙膏、洗发水、沐浴露、毛巾

枚举的使用使得分类之间具有明确的区分,在后续的状态管理和条件判断中可以方便地进行比较。

3.2 定义物品数据接口

每个物品需要包含唯一标识、名称和勾选状态三个属性。我们使用 TypeScript 的 interface 来定义物品的数据结构:

interface PackingItem {
  id: number;
  name: string;
  checked: boolean;
}
  • id:物品的唯一标识符,使用数字类型,用于在勾选操作中精确定位到某个物品。
  • name:物品的名称,如"T恤"、"身份证"等,用于在界面上展示。
  • checked:物品的勾选状态,true 表示已勾选(已装入行李),false 表示未勾选。

3.3 定义分类数据接口

每个分类包含分类类型、名称、图标和该分类下的物品列表。我们同样使用 interface 来定义:

interface Category {
  type: CategoryType;
  name: string;
  icon: string;
  items: PackingItem[];
}
  • type:分类的类型,使用前面定义的 CategoryType 枚举。
  • name:分类的显示名称,如"衣物"、"证件"等。
  • icon:分类的图标,这里使用 Emoji 表情符号来简化实现,实际项目中可以使用图片资源。
  • items:该分类下的物品列表,是一个 PackingItem 数组。

通过这三个数据模型的定义,我们构建了一个清晰的层次结构:分类包含物品,每个分类和物品都有明确的属性。这种设计使得数据的存储、查询和修改都变得非常方便。


四、状态管理

4.1 @State 装饰器

在 ArkTS 中,@State 是最基础的状态管理装饰器。被 @State 标记的变量称为状态变量,当状态变量的值发生变化时,框架会自动检测这一变化,并重新渲染依赖该状态变量的 UI 组件。

在本应用中,我们定义了两个状态变量:

@State selectedCategory: CategoryType = CategoryType.Clothes;
@State categories: Category[] = [...];
  • selectedCategory:当前选中的分类类型,初始值为 CategoryType.Clothes(衣物分类)。当用户点击不同的分类时,这个值会更新,从而触发下方物品列表的重新渲染。
  • categories:所有分类及其物品的数据数组。当用户勾选某个物品时,我们会修改该数组中对应物品的 checked 属性,并重新赋值给 categories,以触发 UI 更新。

4.2 状态更新的注意事项

在 ArkTS 中,对于数组和对象类型的状态变量,直接修改其内部属性(如 item.checked = true)可能不会触发 UI 更新。这是因为框架的状态变化检测机制是基于引用比较的。为了确保 UI 能够正确更新,我们需要在修改数据后重新赋值状态变量:

toggleItem(itemId: number) {
  for (let category of this.categories) {
    for (let item of category.items) {
      if (item.id === itemId) {
        item.checked = !item.checked;
        // 重新赋值触发状态更新
        this.categories = [...this.categories];
        return;
      }
    }
  }
}

在上述代码中,我们首先遍历所有分类和物品,找到目标物品并切换其 checked 属性。然后通过扩展运算符 [...this.categories] 创建一个新的数组并赋值给 this.categories,这样框架就能检测到状态的变化并触发 UI 更新。

这种"修改数据 + 重新赋值"的模式是 ArkTS 状态管理中的一个重要技巧,读者在实际开发中需要特别注意。


五、UI 布局实现

5.1 整体布局结构

应用的整体布局采用垂直方向的 Column 容器,从上到下依次包含三个部分:

  1. 标题区域:使用 Text 组件显示"行李清单"标题。
  2. 分类网格区域:使用 Grid 组件以四列网格展示四个物品分类。
  3. 物品列表区域:使用 List 组件展示当前选中分类下的物品列表。
build() {
  Column() {
    // 标题
    Text('行李清单')
      ...

    // 分类Grid
    Grid() { ... }

    // 物品列表
    List() { ... }
  }
  .width('100%')
  .height('100%')
  .backgroundColor('#FAFAFA')
}

Column 是 ArkTS 中最常用的布局容器之一,它将子组件按照垂直方向从上到下依次排列。通过设置 width('100%')height('100%'),使整个布局占满屏幕。背景色设置为 #FAFAFA,这是一种浅灰色,比纯白色更加柔和,能提升视觉舒适度。

5.2 标题区域

标题区域使用 Text 组件实现,通过链式调用设置各项属性:

Text('行李清单')
  .fontSize(24)           // 字体大小 24fp
  .fontWeight(FontWeight.Bold)  // 粗体
  .width('100%')          // 宽度占满父容器
  .textAlign(TextAlign.Center)  // 文字居中
  .padding(16)            // 内边距 16vp
  • fontSize(24):设置字体大小为 24fp(font point),在 ArkTS 中字体大小的默认单位是 fp。
  • fontWeight(FontWeight.Bold):设置字体为粗体,使标题更加醒目。
  • textAlign(TextAlign.Center):设置文字水平居中对齐。
  • padding(16):设置四周内边距为 16vp(virtual pixel),使标题与周围元素保持适当的间距。

5.3 分类网格区域(Grid)

Grid 组件是 ArkTS 中用于实现网格布局的容器组件,它可以将子组件按照行列的方式排列。在本应用中,我们使用 Grid 来展示四个物品分类。

5.3.1 Grid 组件的基本用法
Grid() {
  ForEach(this.categories, (category: Category) => {
    GridItem() {
      Column() {
        Text(category.icon)
          .fontSize(32)
        Text(category.name)
          .fontSize(14)
          .margin({ top: 8 })
      }
      .width('100%')
      .height('100%')
      .justifyContent(FlexAlign.Center)
      .backgroundColor(this.selectedCategory === category.type ? '#E3F2FD' : '#F5F5F5')
      .borderRadius(8)
      .onClick(() => {
        this.selectedCategory = category.type;
      })
    }
  })
}
.columnsTemplate('1fr 1fr 1fr 1fr')
.rowsGap(12)
.columnsGap(12)
.height(120)
.padding({ left: 16, right: 16 })
5.3.2 columnsTemplate 列模板

columnsTemplate('1fr 1fr 1fr 1fr') 是 Grid 组件的核心属性之一,它定义了网格的列布局。1fr 表示一等份的空间,四个 1fr 表示将可用空间平均分成四列。这种写法类似于 CSS Grid 中的 grid-template-columns: 1fr 1fr 1fr 1fr

如果你需要不同的列宽比例,可以修改 fr 的数值。例如,columnsTemplate('1fr 2fr 1fr') 会创建三列,中间列的宽度是两侧列的两倍。

5.3.3 GridItem 内容

每个 GridItem 内部包含一个 Column 容器,垂直排列图标和名称文字:

  • 图标:使用 Text 组件渲染 Emoji 表情,字体大小设为 32fp,使其在网格中足够醒目。
  • 名称:使用 Text 组件显示分类名称,字体大小 14fp,上边距 8vp,与图标保持适当间距。
  • 居中对齐:通过 justifyContent(FlexAlign.Center) 使 Column 内的子组件在交叉轴(水平方向)上居中对齐。
5.3.4 选中状态的高亮效果

为了给用户清晰的视觉反馈,我们实现了分类选中状态的高亮效果:

.backgroundColor(this.selectedCategory === category.type ? '#E3F2FD' : '#F5F5F5')

这里使用了三元表达式进行条件判断:

  • 当分类被选中时(this.selectedCategory === category.type),背景色设为 #E3F2FD(浅蓝色)。
  • 当分类未被选中时,背景色设为 #F5F5F5(浅灰色)。

这种颜色搭配既保持了界面的简洁,又能让用户一眼看出当前选中的是哪个分类。

5.3.5 点击事件
.onClick(() => {
  this.selectedCategory = category.type;
})

为每个 GridItem 绑定了点击事件处理函数。当用户点击某个分类时,将 selectedCategory 状态变量更新为该分类的类型。由于 selectedCategory@State 装饰的状态变量,它的变化会自动触发依赖它的 UI 组件重新渲染,从而实现物品列表的切换。

5.4 物品列表区域(List)

List 组件是 ArkTS 中用于展示垂直列表的容器组件,支持高效的列表复用机制,适合展示大量数据。

5.4.1 获取当前分类的物品

在渲染列表之前,我们需要一个方法来获取当前选中分类下的物品列表:

getCurrentItems(): PackingItem[] {
  const category = this.categories.find(c => c.type === this.selectedCategory);
  return category ? category.items : [];
}

这个方法使用数组的 find 方法在 categories 数组中查找与 selectedCategory 匹配的分类,然后返回该分类的 items 数组。如果未找到匹配的分类(理论上不会发生),则返回空数组。

5.4.2 List 组件的实现
List() {
  ForEach(this.getCurrentItems(), (item: PackingItem) => {
    ListItem() {
      Row() {
        Checkbox()
          .select(item.checked)
          .selectedColor('#2196F3')
          .width(24)
          .height(24)
          .onChange((value: boolean) => {
            this.toggleItem(item.id);
          })

        Text(item.name)
          .fontSize(16)
          .margin({ left: 12 })
          .decoration({
            type: item.checked
              ? TextDecorationType.LineThrough
              : TextDecorationType.None
          })
          .fontColor(item.checked ? '#999999' : '#333333')
      }
      .width('100%')
      .padding(16)
      .backgroundColor('#FFFFFF')
      .borderRadius(8)
      .margin({ bottom: 8 })
    }
  })
}
.width('100%')
.layoutWeight(1)
.padding({ left: 16, right: 16, top: 16 })
5.4.3 ForEach 数据渲染

ForEach 是 ArkTS 中用于根据数据数组批量生成组件的容器。它接收两个参数:数据源数组和组件生成函数。

ForEach(this.getCurrentItems(), (item: PackingItem) => {
  // 为每个 item 生成一个 ListItem
})

getCurrentItems() 返回的数组发生变化时(例如切换了分类),ForEach 会自动更新列表内容,移除旧的列表项并添加新的列表项。

5.4.4 Checkbox 勾选框

每个物品列表项的左侧是一个 Checkbox 组件:

Checkbox()
  .select(item.checked)
  .selectedColor('#2196F3')
  .width(24)
  .height(24)
  .onChange((value: boolean) => {
    this.toggleItem(item.id);
  })
  • select(item.checked):设置 Checkbox 的选中状态,与物品的 checked 属性绑定。
  • selectedColor('#2196F3'):设置选中时的颜色为蓝色(Material Design 的主色调)。
  • width(24) / height(24):设置 Checkbox 的尺寸为 24x24 vp。
  • onChange:当用户点击 Checkbox 改变选中状态时触发。在回调函数中调用 toggleItem 方法更新物品的勾选状态。
5.4.5 物品名称的样式变化

物品名称的样式会根据勾选状态发生变化,这是提升用户体验的重要细节:

Text(item.name)
  .fontSize(16)
  .margin({ left: 12 })
  .decoration({
    type: item.checked
      ? TextDecorationType.LineThrough
      : TextDecorationType.None
  })
  .fontColor(item.checked ? '#999999' : '#333333')
  • 删除线效果:当物品被勾选时,通过 decoration 属性添加 LineThrough(删除线)效果,直观地表示该物品已准备好。注意 decoration 方法接收的是一个 DecorationStyleInterface 对象,需要传入 { type: TextDecorationType.LineThrough } 而非直接传入枚举值。
  • 颜色变化:勾选后文字颜色从深灰色(#333333)变为浅灰色(#999999),配合删除线效果,进一步强化"已完成"的视觉感受。
5.4.6 layoutWeight 布局权重
.layoutWeight(1)

layoutWeight 是 List 组件的一个关键属性,它使得 List 在 Column 布局中占据剩余的可用空间。由于标题和 Grid 区域的高度是固定的,List 通过 layoutWeight(1) 自动填充页面剩余的所有垂直空间,确保列表可以完整展示所有物品。


六、交互逻辑详解

6.1 分类切换流程

分类切换是应用中最核心的交互之一,其完整流程如下:

  1. 用户操作:用户点击某个分类的 GridItem(例如点击"电子"分类)。
  2. 事件触发:GridItem 的 onClick 事件处理函数被调用。
  3. 状态更新this.selectedCategory 被赋值为 CategoryType.Electronics
  4. UI 更新 - Grid:由于 selectedCategory 是状态变量,Grid 中每个 GridItem 的背景色条件表达式会重新计算,"电子"分类变为蓝色高亮,其他分类恢复灰色。
  5. UI 更新 - List:List 中的 ForEach 调用 getCurrentItems(),该方法根据新的 selectedCategory 返回电子分类的物品列表,List 自动更新显示手机、充电器、耳机、充电宝、相机等物品。

6.2 物品勾选流程

物品勾选的交互流程如下:

  1. 用户操作:用户点击某个物品旁边的 Checkbox。
  2. 事件触发:Checkbox 的 onChange 回调被调用,参数 value 为新的选中状态。
  3. 调用 toggleItemonChange 回调中调用 this.toggleItem(item.id),传入物品的唯一 ID。
  4. 查找并修改数据toggleItem 方法遍历所有分类和物品,找到 ID 匹配的物品,将其 checked 属性取反。
  5. 触发状态更新:通过 this.categories = [...this.categories] 重新赋值状态变量。
  6. UI 更新
    • Checkbox 的选中状态更新(勾选或取消勾选)。
    • 物品名称的文字装饰更新(添加或移除删除线)。
    • 物品名称的颜色更新(灰色或深色)。

6.3 数据流总结

整个应用的数据流可以概括为:

用户操作 → 事件处理 → 状态变量更新 → UI 自动重新渲染

这种单向数据流的设计模式使得应用的状态变化可预测、可追踪,是声明式 UI 框架的核心思想。


七、关键知识点总结

7.1 Grid 组件核心属性

属性 说明 示例
columnsTemplate 定义列宽比例 '1fr 1fr 1fr 1fr'
rowsTemplate 定义行高比例 '1fr 1fr'
columnsGap 列间距 12
rowsGap 行间距 12
GridItem Grid 的子组件容器 必须作为 Grid 的直接子组件

7.2 List 组件核心属性

属性 说明 示例
layoutWeight 在父布局中的权重 1
ListItem List 的子项容器 必须作为 List 的直接子组件
ForEach 数据驱动的子组件生成器 ForEach(dataArray, itemBuilder)

7.3 状态管理要点

装饰器 用途 特点
@State 组件自身的状态管理 状态变化触发 UI 更新
@Prop 父到子的单向数据传递 子组件无法修改父组件的状态
@Link 父子组件的双向数据同步 子组件的修改会同步到父组件

7.4 常用布局容器

容器 排列方向 适用场景
Column 垂直(从上到下) 页面整体布局
Row 水平(从左到右) 列表项内部布局
Grid 网格(行列) 分类展示、图片墙
List 垂直列表 可滚动的数据列表
Stack 层叠(后覆盖前) 叠加效果、悬浮按钮

八、扩展建议

在完成基础版本后,你可以尝试以下扩展功能来进一步提升应用的功能性和用户体验:

8.1 添加物品管理功能

  • 新增物品:在每个分类下添加输入框和按钮,允许用户自定义添加新的物品。
  • 删除物品:通过左滑列表项显示删除按钮,实现物品的删除功能。
  • 编辑物品:点击物品名称进入编辑模式,修改物品名称。

8.2 添加统计功能

  • 在页面底部或标题栏显示当前分类的勾选进度,例如"已准备 3/5 项"。
  • 显示所有分类的总体完成进度,帮助用户了解整体准备情况。

8.3 数据持久化

  • 使用 @StorageProp@StorageLink 将勾选状态保存到 AppStorage 中。
  • 使用文件存储或 Preferences(首选项)将数据持久化到本地,确保应用重启后数据不丢失。

8.4 新增分类功能

  • 允许用户自定义新的物品分类,而不仅限于预设的四个分类。
  • 支持为分类选择自定义图标和颜色。

8.5 多清单支持

  • 支持创建多个行李清单(如"出差清单"、“旅行清单”),每个清单独立管理。
  • 提供清单模板功能,预设常见场景的物品列表。

九、完整代码

以下是本应用的完整代码,供读者参考:

// 物品分类枚举
enum CategoryType {
  Clothes,
  Documents,
  Electronics,
  Toiletries
}

// 物品数据模型
interface PackingItem {
  id: number;
  name: string;
  checked: boolean;
}

// 分类数据模型
interface Category {
  type: CategoryType;
  name: string;
  icon: string;
  items: PackingItem[];
}

@Entry
@Component
struct Index {
  @State selectedCategory: CategoryType = CategoryType.Clothes;
  @State categories: Category[] = [
    {
      type: CategoryType.Clothes,
      name: '衣物',
      icon: '👕',
      items: [
        { id: 1, name: 'T恤', checked: false },
        { id: 2, name: '裤子', checked: false },
        { id: 3, name: '外套', checked: false },
        { id: 4, name: '内衣', checked: false },
        { id: 5, name: '袜子', checked: false }
      ]
    },
    {
      type: CategoryType.Documents,
      name: '证件',
      icon: '📄',
      items: [
        { id: 6, name: '身份证', checked: false },
        { id: 7, name: '护照', checked: false },
        { id: 8, name: '机票', checked: false },
        { id: 9, name: '酒店预订单', checked: false }
      ]
    },
    {
      type: CategoryType.Electronics,
      name: '电子',
      icon: '📱',
      items: [
        { id: 10, name: '手机', checked: false },
        { id: 11, name: '充电器', checked: false },
        { id: 12, name: '耳机', checked: false },
        { id: 13, name: '充电宝', checked: false },
        { id: 14, name: '相机', checked: false }
      ]
    },
    {
      type: CategoryType.Toiletries,
      name: '洗护',
      icon: '🧴',
      items: [
        { id: 15, name: '牙刷', checked: false },
        { id: 16, name: '牙膏', checked: false },
        { id: 17, name: '洗发水', checked: false },
        { id: 18, name: '沐浴露', checked: false },
        { id: 19, name: '毛巾', checked: false }
      ]
    }
  ];

  // 获取当前分类的物品列表
  getCurrentItems(): PackingItem[] {
    const category = this.categories.find(c => c.type === this.selectedCategory);
    return category ? category.items : [];
  }

  // 切换物品勾选状态
  toggleItem(itemId: number) {
    for (let category of this.categories) {
      for (let item of category.items) {
        if (item.id === itemId) {
          item.checked = !item.checked;
          // 触发状态更新
          this.categories = [...this.categories];
          return;
        }
      }
    }
  }

  build() {
    Column() {
      // 标题
      Text('行李清单')
        .fontSize(24)
        .fontWeight(FontWeight.Bold)
        .width('100%')
        .textAlign(TextAlign.Center)
        .padding(16)

      // 分类Grid
      Grid() {
        ForEach(this.categories, (category: Category) => {
          GridItem() {
            Column() {
              Text(category.icon)
                .fontSize(32)
              Text(category.name)
                .fontSize(14)
                .margin({ top: 8 })
            }
            .width('100%')
            .height('100%')
            .justifyContent(FlexAlign.Center)
            .backgroundColor(
              this.selectedCategory === category.type
                ? '#E3F2FD' : '#F5F5F5'
            )
            .borderRadius(8)
            .onClick(() => {
              this.selectedCategory = category.type;
            })
          }
        })
      }
      .columnsTemplate('1fr 1fr 1fr 1fr')
      .rowsGap(12)
      .columnsGap(12)
      .height(120)
      .padding({ left: 16, right: 16 })

      // 物品列表
      List() {
        ForEach(this.getCurrentItems(), (item: PackingItem) => {
          ListItem() {
            Row() {
              Checkbox()
                .select(item.checked)
                .selectedColor('#2196F3')
                .width(24)
                .height(24)
                .onChange((value: boolean) => {
                  this.toggleItem(item.id);
                })

              Text(item.name)
                .fontSize(16)
                .margin({ left: 12 })
                .decoration({
                  type: item.checked
                    ? TextDecorationType.LineThrough
                    : TextDecorationType.None
                })
                .fontColor(item.checked ? '#999999' : '#333333')
            }
            .width('100%')
            .padding(16)
            .backgroundColor('#FFFFFF')
            .borderRadius(8)
            .margin({ bottom: 8 })
          }
        })
      }
      .width('100%')
      .layoutWeight(1)
      .padding({ left: 16, right: 16, top: 16 })
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#FAFAFA')
  }
}

十、总结

通过本文的学习,我们使用 ArkTS 实现了一款功能完整的行李清单应用。在这个过程中,我们深入掌握了以下核心知识点:

  1. 数据模型设计:使用 enuminterface 构建了清晰的层次化数据结构。
  2. 状态管理:通过 @State 装饰器实现了数据驱动的 UI 更新机制,理解了状态变量重新赋值触发 UI 刷新的重要性。
  3. Grid 网格布局:使用 Grid + GridItem 实现了四列分类展示,掌握了 columnsTemplaterowsGapcolumnsGap 等核心属性。
  4. List 列表组件:使用 List + ListItem + ForEach 实现了可滚动的物品列表,掌握了 layoutWeight 布局权重属性。
  5. Checkbox 交互:实现了物品的勾选功能,包括删除线效果和颜色变化等视觉反馈。
  6. 条件样式:通过三元表达式实现了选中状态的高亮效果和勾选状态的样式变化。

这些知识点是 ArkTS 开发中最基础也是最常用的内容,掌握了它们,你就具备了开发更多 HarmonyOS 应用的能力。希望本文能对你学习 ArkTS 和 HarmonyOS 开发有所帮助,祝你开发愉快!

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐