【HarmonyOS】用仓颉编程语言开发应用——首页实现
/ 导航卡片数据结构public var bgColor: Color // 背景颜色在仓颉编程语言语言中,struct是一种值类型的数据结构,用于组织相关的数据。值语义:赋值时会进行拷贝,而不是共享引用轻量级:适合表示简单的数据模型不可继承:struct 不支持继承,但可以实现接口public:访问修饰符,表示这些字段可以从外部访问var:表示可变变量(与let不可变变量相对)String:字符
从零开始构建HarmonyOS应用的首页导航界面
创建项目
由于前段时间没有申请到仓颉的鸿蒙6版本,所以目前本教程使用DevEco Studio 5.0.5 Release开发,对应HarmonyOS API17版本,搭配仓颉插件DevEco Studio-Cangjie Plugin 5.0.13.200 Canary。
新建项目的教程可以参考文章:从零开始创建你的第一个HarmonyOS6项目
只不过在新建项目的时候需要选择[Cangjie]Empty Ability:

概览
在本文中,我们将深入讲解应用的主页实现。这是用户打开应用后看到的第一个界面,也是本应用的导航中心。通过本文学习,你将掌握:
- 仓颉编程语言 struct 类型的定义和使用
- UI 组件的基本用法(Column、Grid、Text 等)
- 状态管理和组件装饰器
- 列表渲染和事件处理
- 无障碍设计的实践应用
完整代码展示
首先,让我们看一下完整的 index.cj 代码:
package ohos_app_cangjie_entry
import ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import std.collection.*
// 导航卡片数据结构
struct NavigationCard {
public var icon: String
public var title: String
public var bgColor: Color // 背景颜色
public init(icon: String, title: String, bgColor: Color) {
this.icon = icon
this.title = title
this.bgColor = bgColor
}
}
// 主页入口视图
@Entry
@Component
class EntryView {
// 8个功能卡片数据
let cards: Array<NavigationCard> = [
NavigationCard("药", "用药提醒", Color(0xFFB3BA)), // 深粉红色
NavigationCard("健", "健康日记", Color(0xBAE1B3)), // 深绿色
NavigationCard("急", "紧急联系", Color(0xFFCC99)), // 深橙色
NavigationCard("价", "价格记录", Color(0x99CCFF)), // 深蓝色
NavigationCard("乐", "娱乐", Color(0xFFB3E6)), // 深粉色
NavigationCard("电", "常用电话", Color(0x99E6E6)), // 深青色
NavigationCard("防", "防诈骗", Color(0xFFE699)), // 深黄色
NavigationCard("救", "应急指南", Color(0xD9B3FF)) // 深紫色
]
func build(): Unit {
Column() {
// 标题栏
Text("老年帮帮手")
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(0x333333)
.margin(top: 20, bottom: 20)
// 可滚动的卡片网格
Scroll() {
Grid() {
// 使用ForEach循环渲染卡片
ForEach(
this.cards,
itemGeneratorFunc: {card: NavigationCard, index: Int64 =>
GridItem() {
this.CardItem(card)
}
},
keyGeneratorFunc: {card: NavigationCard, index: Int64 =>
card.title
}
)
}
.columnsTemplate("1fr 1fr")
.rowsGap(16)
.columnsGap(16)
.width(100.percent)
.padding(16)
}
.width(100.percent)
.layoutWeight(1)
}
.width(100.percent)
.height(100.percent)
.backgroundColor(0xF5F5F5)
}
// 卡片组件 - 使用@Builder装饰器
@Builder
func CardItem(card: NavigationCard): Unit {
Column(12) {
Text(card.icon)
.fontSize(40)
.fontColor(0x333333)
.width(70)
.height(70)
.textAlign(TextAlign.Center)
.backgroundColor(0xF0FFF0)
.borderRadius(40)
// 标题
Text(card.title)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(0x333333)
}
.width(100.percent)
.height(150)
.padding(20)
.backgroundColor(card.bgColor)
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick {
evt => AppLog.info("点击了: ${card.title}")
}
}
}
代码逐行解析
第一部分:包声明和导入
package ohos_app_cangjie_entry
- 包声明定义了当前文件所属的命名空间
以下是导入语句:
import ohos.base.*
import ohos.component.*
import ohos.state_manage.*
import ohos.state_macro_manage.*
import std.collection.*
第二部分:数据模型定义
// 导航卡片数据结构
struct NavigationCard {
public var icon: String
public var title: String
public var bgColor: Color // 背景颜色
public init(icon: String, title: String, bgColor: Color) {
this.icon = icon
this.title = title
this.bgColor = bgColor
}
}
什么是 struct?
在仓颉编程语言语言中,struct 是一种值类型的数据结构,用于组织相关的数据。它具有以下特点:
- 值语义:赋值时会进行拷贝,而不是共享引用
- 轻量级:适合表示简单的数据模型
- 不可继承:struct 不支持继承,但可以实现接口
struct vs class 对比
| 特性 | struct | class |
|---|---|---|
| 类型 | 值类型 | 引用类型 |
| 赋值行为 | 拷贝 | 共享引用 |
| 继承 | 不支持 | 支持 |
为什么这里使用 struct?
NavigationCard 只是一个简单的数据容器,包含图标、标题和背景色三个字段。它不需要继承,也不需要复杂的行为,因此使用 struct 是最佳选择。
字段定义
public var icon: String
public var title: String
public var bgColor: Color
public:访问修饰符,表示这些字段可以从外部访问var:表示可变变量(与let不可变变量相对)String:字符串类型Color:UI 框架提供的颜色类型
构造函数
public init(icon: String, title: String, bgColor: Color) {
this.icon = icon
this.title = title
this.bgColor = bgColor
}
init 构造函数
init是仓颉中的构造函数关键字- 参数列表定义了创建对象时需要传入的值
this关键字指向当前对象实例- 构造函数中通常进行字段的初始化
使用示例:
let card = NavigationCard("药", "用药提醒", Color(0xFFB3BA))
第三部分:主视图组件
@Entry
@Component
public class EntryView {
// ...
}
装饰器
@Entry 装饰器
- 标记这是入口组件
- 应用启动时会自动加载并显示这个组件
@Component 装饰器
- 标记这是一个UI 组件
- 组件必须实现
build()方法来定义 UI 结构 - 组件可以包含状态、生命周期方法等
第四部分:数据初始化
let cards: Array<NavigationCard> = [
NavigationCard("药", "用药提醒", Color(0xFFB3BA)), // 深粉红色
NavigationCard("健", "健康日记", Color(0xBAE1B3)), // 深绿色
NavigationCard("急", "紧急联系", Color(0xFFCC99)), // 深橙色
NavigationCard("价", "价格记录", Color(0x99CCFF)), // 深蓝色
NavigationCard("乐", "娱乐", Color(0xFFB3E6)), // 深粉色
NavigationCard("电", "常用电话", Color(0x99E6E6)), // 深青色
NavigationCard("防", "防诈骗", Color(0xFFE699)), // 深黄色
NavigationCard("救", "应急指南", Color(0xD9B3FF)) // 深紫色
]
知识点:数组字面量
Array<NavigationCard>:数组类型声明,元素类型为 NavigationCard[...]:数组字面量语法,直接初始化数组内容let:不可变变量,数组引用不能改变(但数组内容可以修改)
第五部分:build() 方法 - UI 构建
func build(): Unit {
Column() {
// UI 内容
}
.width(100.percent)
.height(100.percent)
.backgroundColor(0xF5F5F5)
}
知识点:build() 方法
- 每个 @Component 必须实现
build()方法 - 返回类型是
Unit(相当于其他语言的 void) - 方法内部定义组件的 UI 结构
知识点:Column 组件
Column 是一个垂直布局容器,子元素从上到下排列。
┌─────────────────┐
│ 子元素 1 │
├─────────────────┤
│ 子元素 2 │
├─────────────────┤
│ 子元素 3 │
└─────────────────┘
属性链式调用:
.width(100.percent) // 宽度 100%
.height(100.percent) // 高度 100%
.backgroundColor(0xF5F5F5) // 背景色(浅灰色)
- UI 组件支持链式调用
- 每个属性方法返回组件自身,可以连续调用
100.percent是百分比单位,表示占满父容器
第六部分:标题栏
Text("老年帮帮手")
.fontSize(32)
.fontWeight(FontWeight.Bold)
.fontColor(0x333333)
.margin(top: 20, bottom: 20)
知识点:Text 组件
Text 是显示文本的基础组件。
属性详解:
| 属性 | 值 | 说明 |
|---|---|---|
fontSize |
32 | 字体大小 32sp |
fontWeight |
FontWeight.Bold | 字体粗细(加粗) |
fontColor |
0x333333 | 字体颜色(深灰色) |
margin |
top: 20, bottom: 20 | 上下外边距各 20dp |
第七部分:可滚动网格布局
Scroll() {
Grid() {
// 网格内容
}
.columnsTemplate("1fr 1fr")
.rowsGap(16)
.columnsGap(16)
.width(100.percent)
.padding(16)
}
.width(100.percent)
.layoutWeight(1)
知识点:Scroll 组件
Scroll 是一个可滚动容器,当内容超出屏幕时自动显示滚动条。
为什么需要 Scroll?
- 8 个卡片可能超出屏幕高度
- 提供良好的用户体验
知识点:Grid 组件
Grid 是一个网格布局容器,可以将子元素排列成行和列。
┌──────────┬──────────┐
│ 卡片1 │ 卡片2 │
├──────────┼──────────┤
│ 卡片3 │ 卡片4 │
├──────────┼──────────┤
│ 卡片5 │ 卡片6 │
└──────────┴──────────┘
Grid 属性详解:
1. columnsTemplate(“1fr 1fr”)
定义网格的列模板。
1fr 1fr:两列,每列占 1 份空间(平分)fr是 fraction(分数)单位- 相当于 CSS Grid 的
grid-template-columns
其他示例:
"1fr 2fr" // 两列,第二列是第一列的 2 倍宽
"1fr 1fr 1fr" // 三列,平分
2. rowsGap(16) 和 columnsGap(16)
rowsGap:行间距 16dpcolumnsGap:列间距 16dp
视觉效果:
┌────┐ 16dp ┌────┐
│卡片│<---->│卡片│
└────┘ └────┘
↕ 16dp
┌────┐ ┌────┐
│卡片│ │卡片│
└────┘ └────┘
3. layoutWeight(1)
- 设置组件在父容器中的权重
1表示占据剩余的所有空间- 确保网格区域填满标题栏下方的所有空间
布局结构:
┌─────────────────────┐
│ 标题栏(固定高度) │ ← 固定大小
├─────────────────────┤
│ │
│ 网格区域 │ ← layoutWeight(1),占满剩余空间
│ (可滚动) │
│ │
└─────────────────────┘
第八部分:ForEach 列表渲染
ForEach(
this.cards,
itemGeneratorFunc: {card: NavigationCard, index: Int64 =>
GridItem() {
this.CardItem(card)
}
},
keyGeneratorFunc: {card: NavigationCard, index: Int64 =>
card.title
}
)
知识点:ForEach 组件
ForEach 是一个列表渲染组件,用于根据数据数组动态生成 UI 元素。
参数详解:
1. 数据源:this.cards
- 要遍历的数组
this指向当前组件实例
2. itemGeneratorFunc(项生成函数)
{card: NavigationCard, index: Int64 =>
GridItem() {
this.CardItem(card)
}
}
这是一个 lambda 表达式(匿名函数):
card:当前遍历到的数组元素index:当前元素的索引(0, 1, 2, …)=>:lambda 箭头,分隔参数和函数体- 返回值:一个
GridItem组件
Lambda 语法回顾:
// 完整形式
{参数1: 类型1, 参数2: 类型2 => 函数体}
// 示例
{x: Int64, y: Int64 => x + y} // 加法函数
{name: String => println(name)} // 打印函数
3. keyGeneratorFunc(键生成函数)
{card: NavigationCard, index: Int64 =>
card.title
}
- 为每个列表项生成唯一的键(key)
- 这里使用
card.title作为键(因为每个卡片标题都是唯一的)
知识点:GridItem 组件
GridItem 是 Grid 的子项容器,每个 GridItem 占据网格中的一个单元格。
GridItem() {
this.CardItem(card)
}
- 包裹单个卡片组件
- 自动适应网格布局
第九部分:CardItem 卡片组件
@Builder
func CardItem(card: NavigationCard): Unit {
Column(12) {
// 卡片内容
}
.width(100.percent)
.height(160)
.padding(20)
.backgroundColor(card.bgColor)
.borderRadius(16)
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick {
evt => AppLog.info("点击了: ${card.title}")
}
}
知识点:@Builder 装饰器
- 标记这是一个可复用的 UI 构建函数
- 可以在
build()方法中多次调用 - 类似于 React 的函数组件
为什么使用 @Builder?
- 代码复用:避免重复编写相同的 UI 代码
- 逻辑分离:将卡片的渲染逻辑独立出来
- 可维护性:修改卡片样式只需改一处
Column(12) - 间距参数
Column(12) {
// 子元素
}
12是子元素之间的默认间距(12dp)- 相当于给每个子元素之间添加 margin
视觉效果:
┌─────────────┐
│ 图标 │
│ │ ← 12dp 间距
│ 标题 │
└─────────────┘
卡片样式属性
让我们逐个解析卡片的样式属性:
| 属性 | 值 | 说明 |
|---|---|---|
width |
100.percent | 宽度占满网格单元格 |
height |
160 | 固定高度 160 |
padding |
20 | 内边距 20 |
backgroundColor |
card.bgColor | 使用卡片数据中的背景色 |
borderRadius |
16 | 圆角半径 16(圆润的卡片) |
justifyContent |
FlexAlign.Center | 垂直方向居中对齐 |
alignItems |
HorizontalAlign.Center | 水平方向居中对齐 |
视觉效果:
┌──────────────────────┐
│ │ ← padding: 20
│ ┌────┐ │
│ │图标│ │ ← 居中对齐
│ └────┘ │
│ 标题 │
│ │
└──────────────────────┘
↑
borderRadius: 16
(圆角)
第十部分:图标显示
Text(card.icon)
.fontSize(40)
.fontColor(0x333333)
.width(80)
.height(80)
.textAlign(TextAlign.Center)
.backgroundColor(0xFFFFFF)
.borderRadius(40)
设计思路
这里使用文字作为图标,而不是图片:
- 简单直观:汉字图标对老年人更友好(本应用的面向人群是老年人)
- 无需资源:不需要准备图标图片
- 易于修改:直接改文字即可
圆形图标实现:
.width(80)
.height(80)
.borderRadius(40) // 半径 = 宽高的一半,形成正圆
属性详解:
fontSize(40):大字体,清晰可见fontColor(0x333333):深灰色,与背景形成对比textAlign(TextAlign.Center):文字居中backgroundColor(0xFFFFFF):白色背景borderRadius(40):圆形(半径 = 宽高的一半)
第十一部分:标题显示
Text(card.title)
.fontSize(22)
.fontWeight(FontWeight.Bold)
.fontColor(0x333333)
第十二部分:点击事件处理
.onClick {
evt => AppLog.info("点击了: ${card.title}")
}
知识点:事件处理
onClick 事件
onClick是点击事件处理器- 接受一个 lambda 函数作为参数
evt是事件对象(这里未使用)
AppLog.info() 日志输出
AppLog.info("点击了: ${card.title}")
info()输出信息级别的日志${card.title}是字符串插值语法
知识点:字符串插值
"点击了: ${card.title}"
${}内可以放置任何表达式- 表达式的值会被转换为字符串并插入
- 类似于 JavaScript 的模板字符串
示例:
let name = "张三"
let age = 65
let message = "姓名: ${name}, 年龄: ${age}"
// 结果: "姓名: 张三, 年龄: 65"
界面效果展示
最终界面布局

本章总结
通过本章学习,我们完成了老年帮帮手应用的主页实现。让我们回顾一下关键要点:
核心概念
- 数据模型:使用 struct 定义 NavigationCard
- 组件化:使用 @Component 和 @Builder 构建可复用组件
- 布局系统:掌握 Column、Grid、Scroll 的使用
- 列表渲染:使用 ForEach 动态生成 UI
- 事件处理:实现 onClick 点击事件
- 无障碍设计:大字体、高对比度、大触摸目标
如果大家有任何疑问,欢迎在下方评论区留言~
更多推荐



所有评论(0)