当前位置: 首页 > news >正文

【仓颉纪元】仓颉鸿蒙应用深度开发:待办事项 App 全流程实战

文章目录

  • 前言
  • 一、开发环境搭建
    • 1.1、DevEco Studio 环境配置
    • 1.2、鸿蒙项目结构解析
  • 二、ArkUI 组件开发
    • 2.1、ArkUI 基础组件应用
    • 2.2、响应式布局设计
    • 2.3、自定义组件封装
  • 三、状态管理
    • 3.1、@State 本地状态管理
    • 3.2、@Prop/@Link 父子组件通信
    • 3.3、@Observed/@ObjectLink 复杂对象状态
  • 四、页面导航
    • 4.1、Router 页面跳转与参数传递
    • 4.2、Tab 底部导航栏实现
  • 五、数据持久化
    • 5.1、Preferences 轻量级键值存储
    • 5.2、关系型数据库操作
  • 六、分布式能力
    • 6.1、分布式数据跨设备同步
    • 6.2、跨设备任务流转
  • 七、HTTP 网络请求封装
  • 八、应用生命周期
    • 8.1、Ability 应用生命周期
    • 8.2、页面组件生命周期
  • 九、系统权限申请与管理
  • 十、应用发布
    • 10.1、应用签名配置
    • 10.2、HAP 包构建与发布
  • 十一、调试技巧
    • 11.1、调试日志与错误追踪
    • 11.2、应用性能分析工具
  • 十二、关于作者与参考资料
    • 12.1、作者简介
    • 12.2、参考资料
  • 总结


前言

学习仓颉语言的最终目的是开发鸿蒙原生应用。作为一名移动应用开发工程师,我通过开发待办事项 App 完整实践了鸿蒙应用开发的全流程。从环境搭建到组件开发,从状态管理到数据持久化,从页面导航到分布式能力,每个环节都经过深入探索和实战验证。ArkUI 的声明式语法让 UI 开发简洁直观,响应式状态管理让数据流动清晰可控,分布式能力让跨设备协同变得简单。本文将以待办事项 App 为例,系统讲解鸿蒙应用开发的核心技术点,包括 ArkUI 组件体系、状态管理方案、数据持久化方法、分布式数据同步等内容,并分享开发过程中的实战经验和踩坑心得。通过本文,你将掌握使用仓颉开发功能完整的鸿蒙原生应用的能力。

在这里插入图片描述


声明:本文由作者“白鹿第一帅”于 CSDN 社区原创首发,未经作者本人授权,禁止转载!爬虫、复制至第三方平台属于严重违法行为,侵权必究。亲爱的读者,如果你在第三方平台看到本声明,说明本文内容已被窃取,内容可能残缺不全,强烈建议您移步“白鹿第一帅” CSDN 博客查看原文,并在 CSDN 平台私信联系作者对该第三方违规平台举报反馈,感谢您对于原创和知识产权保护做出的贡献!

文章作者:白鹿第一帅,作者主页:https://blog.csdn.net/qq_22695001,未经授权,严禁转载,侵权必究!

一、开发环境搭建

1.1、DevEco Studio 环境配置

安装步骤详解

步骤操作时间注意事项
1下载 DevEco Studio10-20 分钟约 2GB,需要稳定网络
2安装 IDE5-10 分钟选择合适的安装路径
3安装仓颉插件3-5 分钟在插件市场搜索“Cangjie”
4配置 SDK15-30 分钟下载 API 10 及以上版本
5创建项目2-3 分钟选择“Empty Ability”模板
  1. 下载 DevEco Studio(https://developer.huawei.com/consumer/cn/deveco-studio/)
  2. 安装仓颉语言插件
  3. 配置 HarmonyOS SDK
  4. 创建仓颉项目

1.2、鸿蒙项目结构解析

目录结构说明

目录/文件用途重要性
pages/应用页面⭐⭐⭐⭐⭐
components/可复用组件⭐⭐⭐⭐
models/数据模型⭐⭐⭐⭐
services/业务服务⭐⭐⭐
utils/工具类⭐⭐⭐
resources/资源文件⭐⭐⭐⭐
module.json5模块配置⭐⭐⭐⭐⭐
cangjie.toml仓颉配置⭐⭐⭐⭐⭐
my-harmony-app/
├── src/
│   ├── main/
│   │   ├── cj/              # 仓颉源代码
│   │   │   ├── pages/       # 页面
│   │   │   ├── components/  # 组件
│   │   │   ├── models/      # 数据模型
│   │   │   ├── services/    # 业务服务
│   │   │   └── utils/       # 工具类
│   │   ├── resources/       # 资源文件
│   │   │   ├── base/
│   │   │   │   ├── element/
│   │   │   │   ├── media/
│   │   │   │   └── profile/
│   │   └── module.json5     # 模块配置
├── cangjie.toml            # 仓颉配置
└── build-profile.json5     # 构建配置

二、ArkUI 组件开发

ArkUI 是鸿蒙的声明式 UI 框架,采用类似 Flutter 和 SwiftUI 的设计理念。

声明式 vs 命令式 UI

声明式UI
命令式UI
框架自动渲染
描述UI状态
状态变化
设置属性
创建元素
添加到DOM
监听事件
手动更新
对比项命令式 UI声明式 UI优势
代码量声明式减少 50%+
可读性更易理解
维护性更易维护
性能手动优化自动优化框架智能优化
学习曲线陡峭平缓更易上手

声明式 UI 的核心思想是描述“UI 是什么样子”而不是“如何更新 UI”,框架会自动处理 UI 的更新和渲染优化。这种编程范式让代码更简洁、更易维护,也更符合现代 UI 开发的趋势。

2.1、ArkUI 基础组件应用

基础组件
Text
Button
TextInput
Image
显示文本
样式设置
触发操作
点击事件
输入内容
实时监听
显示图标
加载图片

基础组件是构建 UI 的基石。在待办事项 App 中,最常用的是 Text(显示文本)、Button(触发操作)、TextInput(输入内容)、Image(显示图标)这几个组件。

基础组件对比

组件用途常用属性使用频率
Text显示文本fontSize, fontColor, fontWeight⭐⭐⭐⭐⭐
Button触发操作onClick, backgroundColor⭐⭐⭐⭐⭐
TextInput输入内容placeholder, onChange⭐⭐⭐⭐
Image显示图片src, width, height⭐⭐⭐

ArkUI 采用链式调用方式设置属性,代码简洁易读。

下面的示例展示了 Text 组件的多种样式设置,包括字体大小、颜色、粗细等常用属性。这些属性可以灵活组合,满足不同的 UI 需求:

// Text组件:显示不同样式的文本
@Component  // @Component装饰器标记这是一个可复用的UI组件
struct TextDemo {// build()方法是组件的核心,定义了UI的结构和样式func build() {// Column是垂直布局容器,子组件从上到下排列Column() {// Text组件用于显示文本,通过链式调用设置样式属性Text("普通文本").fontSize(16)              // 设置字体大小为16sp.fontColor(Color.Black)    // 设置字体颜色为黑色Text("粗体文本").fontSize(18)                    // 字体大小18sp.fontWeight(FontWeight.Bold)     // 设置字体粗细为粗体Text("彩色文本").fontSize(20)              // 字体大小20sp.fontColor(Color.Blue)     // 设置字体颜色为蓝色}}
}

Button 组件用于触发用户操作,支持点击事件回调。下面的计数器示例展示了如何使用 @State 管理按钮的状态,每次点击都会触发状态更新,UI 会自动重新渲染:

// Button组件:实现计数器功能
@Component
struct ButtonDemo {// @State装饰器标记响应式状态变量,当count变化时UI会自动更新@State private var count: Int32 = 0func build() {// Column的spacing参数设置子组件之间的垂直间距为10vpColumn(spacing: 10) {// 使用字符串插值${count}动态显示计数值Text("点击次数: ${count}").fontSize(20)// Button组件的onClick接收一个回调函数,点击时执行// 使用lambda表达式() => { ... }定义回调逻辑Button("点击我").onClick(() => { count += 1  // 每次点击计数加1,触发UI自动刷新})// 可以为Button设置背景色等样式属性Button("重置").backgroundColor(Color.Red)  // 设置按钮背景为红色.onClick(() => { count = 0  // 重置计数为0})}}
}

TextInput 组件接收用户输入,通过 onChange 回调实时监听输入变化。在待办事项 App 中,用户通过它输入新任务的标题和描述:

// TextInput组件:实时监听用户输入
@Component
struct InputDemo {// 使用@State管理输入框的文本内容@State private var text: String = ""func build() {Column(spacing: 10) {// TextInput是输入框组件,placeholder显示占位提示文本TextInput(placeholder: "请输入内容")// onChange回调在用户每次输入时触发// value参数包含输入框的最新内容.onChange((value: String) => { text = value  // 将输入内容同步到状态变量})// 实时显示用户输入的内容,展示响应式更新效果Text("输入的内容: ${text}").fontSize(16)}}
}

2.2、响应式布局设计

布局组件
Column
垂直布局
Row
水平布局
Stack
层叠布局
Grid
网格布局
List
列表布局
从上到下
从左到右
组件叠加
规则网格
虚拟滚动

布局是 UI 开发的核心,决定了界面的结构和视觉效果。

布局组件选择指南

垂直
水平
叠加
选择布局组件
排列方向?
Column
Row
Stack
是否规则排列?
Grid
是否长列表?
List
布局组件使用场景性能复杂度
Column垂直排列⭐⭐⭐⭐⭐
Row水平排列⭐⭐⭐⭐⭐
Stack组件叠加⭐⭐⭐⭐⭐⭐
Grid网格布局⭐⭐⭐⭐⭐⭐⭐
List长列表⭐⭐⭐⭐⭐⭐⭐

ArkUI 提供了多种布局组件:Column(垂直布局)用于从上到下排列子组件,Row(水平布局)用于从左到右排列,Stack(层叠布局)用于组件叠加显示,Grid(网格布局)用于规则的网格排列,List(列表布局)专门用于长列表的高性能渲染。

待办事项 App 布局结构

主界面
Column垂直布局
输入框区域
任务列表
TextInput
Button添加
List列表
任务项1
任务项2
任务项...
Row水平布局
Checkbox
Text任务
Button删除

在待办事项 App 中,主界面使用 Column 垂直排列输入框和任务列表,每个任务项使用 Row 横向排列复选框、文本和删除按钮,任务列表使用 List 组件实现虚拟滚动。下面的示例展示了 Column 和 Row 的基本用法,通过 spacing 设置子组件间距,通过 justifyContent 和 alignItems 控制对齐方式:

// Column垂直布局:子组件从上到下排列,支持居中对齐
@Component
struct ColumnDemo {func build() {// spacing: 20 设置子组件之间的垂直间距为20vpColumn(spacing: 20) {Text("第一行")Text("第二行")Text("第三行")}.width("100%")  // 设置宽度为父容器的100%// justifyContent控制主轴(垂直方向)的对齐方式// FlexAlign.Center表示子组件在垂直方向居中.justifyContent(FlexAlign.Center)// alignItems控制交叉轴(水平方向)的对齐方式// HorizontalAlign.Center表示子组件在水平方向居中.alignItems(HorizontalAlign.Center)}
}

Row 布局用于横向排列组件,常用于工具栏、按钮组等场景。通过 justifyContent 可以控制子组件的分布方式,SpaceBetween 表示两端对齐,中间平均分布:

// Row水平布局:子组件从左到右排列,支持空间分配
@Component
struct RowDemo {func build() {// spacing: 10 设置子组件之间的水平间距为10vpRow(spacing: 10) {Text("左")Text("中")Text("右")}.width("100%")  // 占满父容器宽度// FlexAlign.SpaceBetween表示两端对齐// 第一个子组件靠左,最后一个靠右,中间的平均分布剩余空间.justifyContent(FlexAlign.SpaceBetween)}
}

List 组件是长列表的最佳选择,支持虚拟滚动和懒加载,即使有上千条数据也能保持流畅。在待办事项 App 中,任务列表就是用 List 实现的,每个 ListItem 代表一个任务项:

// List列表布局:专门用于长列表,支持虚拟滚动优化性能
@Component
struct ListDemo {// 定义列表数据源private var items: Array<String> = ["项目1", "项目2", "项目3", "项目4", "项目5"]func build() {// List组件自动实现虚拟滚动,只渲染可见区域的列表项// 即使有成千上万条数据,也能保持流畅的滚动性能List() {// 使用for循环遍历数据源,为每个数据项创建ListItemfor (item in items) {// ListItem是List的子组件,代表列表中的一项ListItem() {// 每个列表项显示一个Text组件Text(item).width("100%")   // 占满列表项宽度.height(50)      // 设置固定高度50vp.padding(10)     // 内边距10vp,让文本不贴边}}}.width("100%")   // List占满父容器宽度.height("100%")  // List占满父容器高度,超出部分可滚动}
}

2.3、自定义组件封装

当 UI 元素需要重复使用时,自定义组件是最佳选择。在待办事项 App 中,任务项卡片在列表中多次出现,将其封装为自定义组件可以实现代码复用、简化维护、提高可读性。自定义组件通过构造函数接收参数,通过回调函数响应用户操作。

下面的 UserCard 组件展示了自定义组件的完整实现。组件接收用户信息作为参数,通过 Row 和 Column 组合实现卡片布局,通过 onClick 回调处理点击事件。使用时只需传入数据和回调函数即可:

// 自定义用户卡片组件:封装可复用的UI元素
@Component  // 标记为可复用组件
public struct UserCard {// 组件的属性,通过构造函数传入private var name: String      // 用户名private var avatar: String    // 头像URLprivate var bio: String       // 个人简介private var onTap: () -> Unit // 点击回调函数// 构造函数:创建组件实例时初始化属性public init(name: String, avatar: String, bio: String, onTap: () -> Unit) {this.name = namethis.avatar = avatarthis.bio = biothis.onTap = onTap}func build() {// 使用Row实现横向布局:头像在左,信息在右Row(spacing: 15) {  // 子组件间距15vp// Image组件显示圆形头像Image(avatar).width(60).height(60).borderRadius(30)  // 圆角半径为宽高的一半,形成圆形// Column垂直排列用户名和简介Column(spacing: 5) {// 用户名:粗体、较大字号Text(name).fontSize(18).fontWeight(FontWeight.Bold)// 个人简介:灰色、较小字号、最多显示2行Text(bio).fontSize(14).fontColor(Color.Gray).maxLines(2)  // 超过2行会被截断}.alignItems(HorizontalAlign.Start)  // 文本左对齐.layoutWeight(1)  // 占据Row中剩余的所有空间}.width("100%")              // 卡片占满父容器宽度.padding(15)                // 内边距15vp.backgroundColor(Color.White)  // 白色背景.borderRadius(10)           // 圆角10vp.onClick(onTap)             // 点击时触发回调函数}
}

使用自定义组件时,只需在 List 中遍历数据,为每个用户创建一个 UserCard 实例。这种方式让代码结构清晰,易于维护和扩展:

// 使用自定义组件构建用户列表
@Component
struct UserListPage {// 加载用户数据(假设loadUsers()从数据库或网络获取数据)private var users: Array<User> = loadUsers()func build() {List() {// 遍历用户数组,为每个用户创建一个卡片for (user in users) {ListItem() {// 使用自定义的UserCard组件// 通过命名参数传递数据,代码清晰易读UserCard(name: user.name,      // 传递用户名avatar: user.avatar,  // 传递头像URLbio: user.bio,        // 传递个人简介// onTap回调:点击卡片时跳转到用户详情页onTap: () => { navigateToUserDetail(user.id) })}}}.padding(10)  // 列表整体内边距10vp}
}

三、状态管理

状态是界面上会变化的数据,比如任务列表、任务完成状态、输入框内容等。

响应式编程模型

用户操作状态State框架UI界面修改状态通知变化计算差异更新UI自动刷新用户操作状态State框架UI界面

状态管理层次

装饰器用途数据流使用场景
@State组件内部状态单向计数器、开关
@Prop父传子(只读)单向配置、属性
@Link父子双向绑定双向表单、输入
@Observed对象可观察-复杂数据
@ObjectLink对象引用双向对象状态
AppStorage全局状态-用户信息

ArkUI 采用响应式编程模型,当状态改变时 UI 会自动更新,无需手动操作 DOM。状态管理分为三个层次:@State 管理组件内部状态,@Prop/@Link 用于父子组件间传递状态,@Observed/@ObjectLink 用于复杂对象的状态管理。

3.1、@State 本地状态管理

@State 用于管理组件内部状态,当状态变化时 ArkUI 会自动重新渲染相关 UI。在待办事项 App 中,用它管理新任务的输入内容、列表的展开状态、编辑模式开关等。下面的计数器示例展示了 @State 的基本用法,每次点击按钮都会触发状态更新,UI 自动响应变化:

// @State管理组件内部状态,实现响应式UI更新
@Component
struct CounterPage {// @State装饰器让变量成为响应式状态// 当状态变化时,ArkUI会自动重新渲染使用该状态的UI部分@State private var count: Int32 = 0        // 计数器的值@State private var isRunning: Bool = false // 运行状态开关func build() {Column(spacing: 20) {// 显示当前计数值,使用字符串插值${count}Text("计数: ${count}").fontSize(32).fontWeight(FontWeight.Bold)// 三个按钮横向排列Row(spacing: 10) {// 点击"增加"按钮,count自增1,UI自动更新Button("增加").onClick(() => { count += 1 })// 点击"减少"按钮,count自减1Button("减少").onClick(() => { count -= 1 })// 点击"重置"按钮,count归零Button("重置").onClick(() => { count = 0 })}// Toggle开关组件,使用$符号实现双向绑定// $isRunning表示将isRunning的引用传递给Toggle// Toggle的状态变化会自动同步到isRunning变量Toggle(isOn: $isRunning)// 根据isRunning的值显示不同文本// 使用三元运算符实现条件渲染Text(isRunning ? "运行中" : "已停止")}.width("100%").height("100%").justifyContent(FlexAlign.Center)  // 内容垂直居中}
}

3.2、@Prop/@Link 父子组件通信

父子组件间需要传递数据时,使用 @Prop@Link@Prop 实现单向传递,子组件只能读取不能修改父组件数据,适合纯展示场景。@Link 实现双向绑定,子组件可以修改父组件数据,适合输入框、开关等交互组件。在待办事项 App 中,任务列表项用 @Prop 显示任务信息,任务编辑页面用 @Link 修改任务数据。

下面的示例展示了两种传递方式的区别。ChildComponent1 使用 @Prop 接收数据,只能读取不能修改。ChildComponent2 使用 @Link 绑定数据,可以直接修改父组件的 message 变量,父组件会自动响应变化:

// 父子组件通信:@Prop单向传递,@Link双向绑定
@Component
struct ParentComponent {// 父组件持有状态数据@State private var message: String = "Hello"func build() {Column() {// 方式1:使用@Prop单向传递// 直接传递message的值,子组件只能读取不能修改ChildComponent1(text: message)// 方式2:使用@Link双向绑定// 使用$message传递引用,子组件可以修改父组件的数据ChildComponent2(text: $message)// 父组件修改message,两个子组件都会自动更新Button("修改消息").onClick(() => { message = "World" })}}
}// 子组件1:使用@Prop接收数据(只读)
@Component
struct ChildComponent1 {// @Prop表示这是从父组件传入的属性// 子组件只能读取text的值,不能修改@Prop private var text: Stringfunc build() { Text("接收到: ${text}")  // 显示父组件传入的值}
}// 子组件2:使用@Link绑定数据(可读写)
@Component
struct ChildComponent2 {// @Link表示这是与父组件双向绑定的引用// 子组件修改text时,父组件的message也会同步更新@Link private var text: Stringfunc build() {Column() {Text("当前值: ${text}")  // 显示当前值// 子组件可以直接修改text// 修改后父组件的message和ChildComponent1都会自动更新Button("修改").onClick(() => { text = "Modified" })}}
}

3.3、@Observed/@ObjectLink 复杂对象状态

当需要管理复杂对象时,使用 @Observed@ObjectLink@Observed 标记类为可观察对象,当对象属性变化时会通知 UI 更新。@ObjectLink 用于子组件中引用可观察对象,实现对象级别的响应式更新。在待办事项 App 中,TodoItem 和 TodoStore 都是可观察对象,任何属性变化都会自动触发 UI 刷新。

下面的代码展示了完整的待办事项管理。TodoStore 管理任务列表,提供添加、切换完成状态、删除等方法。TodoListPage 使用 @State 持有 store 实例,TodoItemView 使用 @ObjectLink 引用单个任务项,实现细粒度的 UI 更新:

// 可观察的数据模型:任务项和任务存储
// @Observed装饰器让类成为可观察对象
// 当对象的属性发生变化时,会自动通知UI进行更新
@Observed
class TodoItem {var id: Int64          // 任务的唯一标识符var title: String      // 任务标题var completed: Bool    // 任务完成状态// 构造函数:创建新任务时初始化属性// completed参数有默认值false,表示新任务默认未完成init(id: Int64, title: String, completed: Bool = false) {this.id = idthis.title = titlethis.completed = completed}
}// TodoStore是任务管理的核心类,负责所有任务的增删改查
@Observed
class TodoStore {var items: Array<TodoItem> = []  // 存储所有任务的数组var nextId: Int64 = 1            // 自增ID,确保每个任务有唯一标识// 添加新任务:创建TodoItem实例并添加到数组func addItem(title: String): Unit {// 使用当前nextId创建新任务items.append(TodoItem(nextId, title))// ID自增,为下一个任务准备nextId += 1}// 切换任务完成状态:根据ID查找任务并反转completed属性func toggleItem(id: Int64): Unit {// 遍历任务数组查找目标任务for (item in items) {if (item.id == id) {// 使用!运算符反转布尔值:true变false,false变trueitem.completed = !item.completedbreak  // 找到后立即退出循环,提高性能}}}// 删除任务:使用filter方法过滤掉指定ID的任务func removeItem(id: Int64): Unit {// filter返回一个新数组,只包含id不等于指定值的任务// lambda表达式{ item => item.id != id }定义过滤条件items = items.filter({ item => item.id != id })}
}

TodoListPage 是主界面,包含输入框和任务列表。用户在输入框中输入任务标题,点击添加按钮后调用 store.addItem() 添加任务。任务列表使用 List 组件渲染,每个任务项由 TodoItemView 组件展示:

// 待办事项列表页面:管理任务的添加和展示
@Component
struct TodoListPage {// @State管理TodoStore实例,当store中的数据变化时UI会自动更新@State private var store: TodoStore = TodoStore()// @State管理输入框的文本内容@State private var newTodoText: String = ""func build() {Column() {// 顶部输入区域:输入框和添加按钮横向排列Row(spacing: 10) {// TextInput占据Row中的剩余空间TextInput(placeholder: "添加待办事项").layoutWeight(1)  // 权重为1,自动填充剩余空间// onChange实时监听输入变化,将内容同步到newTodoText.onChange((value: String) => { newTodoText = value })// 添加按钮:点击时创建新任务Button("添加").onClick(() => {// 验证输入不为空if (newTodoText != "") {// 调用store的addItem方法添加任务store.addItem(newTodoText)// 清空输入框,准备下一次输入newTodoText = ""}})}.padding(10)  // 输入区域内边距// 任务列表:使用List组件展示所有任务List() {// 遍历store中的所有任务for (item in store.items) {ListItem() { // 为每个任务创建TodoItemView组件// 传入任务对象和store引用,以便操作任务TodoItemView(item: item, store: store) }}}}}
}

TodoItemView 展示单个任务项,包含复选框、标题和删除按钮。使用 @ObjectLink 引用任务项,当任务状态变化时只更新对应的UI部分,不会重新渲染整个列表,性能更优:

// 任务项视图:展示单个任务的详细信息
@Component
struct TodoItemView {// @ObjectLink用于引用可观察对象// 当item的属性变化时,只有这个TodoItemView会重新渲染// 不会影响列表中的其他任务项,实现细粒度的UI更新@ObjectLink private var item: TodoItem// 持有store引用,用于调用toggleItem和removeItem方法private var store: TodoStore// 构造函数:接收任务对象和store引用init(item: TodoItem, store: TodoStore) {this.item = itemthis.store = store}func build() {// Row横向布局:复选框、标题、删除按钮从左到右排列Row(spacing: 10) {// Checkbox复选框,显示任务的完成状态Checkbox(selected: item.completed)// onChange回调:用户点击复选框时触发.onChange((checked: Bool) => { // 调用store的toggleItem方法切换任务状态store.toggleItem(item.id) })// 显示任务标题Text(item.title).fontSize(16)// decoration根据完成状态动态设置文本装饰// 已完成的任务显示删除线,未完成的任务无装饰.decoration(item.completed ? TextDecorationType.LineThrough  // 删除线: TextDecorationType.None         // 无装饰).layoutWeight(1)  // 占据Row中的剩余空间// 删除按钮:点击时从列表中移除任务Button("删除").backgroundColor(Color.Red)  // 红色背景表示危险操作.onClick(() => { // 调用store的removeItem方法删除任务store.removeItem(item.id) })}.width("100%")  // 任务项占满列表宽度.padding(10)    // 内边距10vp}
}

四、页面导航

页面导航实现应用内的页面跳转和参数传递。router.pushUrl() 跳转到新页面并保留当前页面,router.replaceUrl() 跳转并替换当前页面,router.back() 返回上一页。跳转时可以通过 params 传递参数,目标页面通过 router.getParams() 获取参数。

4.1、Router 页面跳转与参数传递

下面的示例展示了页面跳转的完整流程。HomePage 通过 pushUrl 跳转到 DetailPage 并传递 id 和 name 参数,DetailPage 在 aboutToAppear 生命周期中获取参数并显示,用户点击返回按钮调用 router.back() 返回上一页:

// 主页:实现页面跳转和参数传递
@Entry      // @Entry标记这是应用的入口页面
@Component
struct HomePage {func build() {Column(spacing: 20) {Text("主页").fontSize(24)// 点击按钮跳转到详情页Button("跳转到详情页").onClick(() => {// router.pushUrl()实现页面跳转// pushUrl会保留当前页面在导航栈中,用户可以返回router.pushUrl({url: "pages/DetailPage",  // 目标页面路径// params对象传递参数到目标页面params: { id: 123,        // 传递ID参数name: "示例"    // 传递名称参数}})})}}
}// 详情页:接收路由参数并展示
@Entry      // @Entry标记这也是一个可独立访问的页面
@Component
struct DetailPage {// 使用@State管理从路由接收的参数@State private var id: Int64 = 0@State private var name: String = ""// aboutToAppear是页面生命周期方法// 在页面即将显示时调用,适合在这里获取路由参数func aboutToAppear() {// router.getParams()获取跳转时传递的参数对象let params = router.getParams()// 从params中提取参数,使用as进行类型转换id = params["id"] as Int64name = params["name"] as String}func build() {Column(spacing: 20) {Text("详情页").fontSize(24)// 显示接收到的参数Text("ID: ${id}")Text("名称: ${name}")// 返回按钮:调用router.back()返回上一页Button("返回").onClick(() => { router.back()  // 从导航栈中弹出当前页面,返回HomePage})}}
}

4.2、Tab 底部导航栏实现

Tab 导航用于实现底部导航栏,常见于首页、发现、消息、我的等多标签页应用。Tabs 组件管理多个 TabContent,每个 TabContent 对应一个页面,通过 @State 绑定 currentIndex 实现标签切换:

// Tab导航:实现底部导航栏的多标签页切换
@Entry
@Component
struct MainPage {// @State管理当前选中的标签页索引// 0表示第一个标签页,1表示第二个,以此类推@State private var currentIndex: Int32 = 0func build() {// Tabs组件管理多个标签页// 使用$currentIndex双向绑定当前索引Tabs(index: $currentIndex) {// 每个TabContent代表一个标签页// tabBar()方法设置标签页的标题TabContent() { HomePage()  // 首页内容}.tabBar("首页")TabContent() { DiscoverPage()  // 发现页内容}.tabBar("发现")TabContent() { MessagePage()  // 消息页内容}.tabBar("消息")TabContent() { ProfilePage()  // 我的页面内容}.tabBar("我的")}// barPosition设置标签栏位置// BarPosition.End表示在底部(移动端常见布局)// BarPosition.Start表示在顶部.barPosition(BarPosition.End)// onChange监听标签页切换事件// 当用户点击不同标签时,index参数包含新的索引值.onChange((index: Int32) => { currentIndex = index  // 更新当前索引})}
}

五、数据持久化

数据持久化让应用数据在关闭后仍然保留。鸿蒙提供三种方案:Preferences 适合轻量级键值对存储(如配置、设置),关系型数据库适合结构化数据和复杂查询(如任务、笔记),分布式数据库支持跨设备同步。在待办事项 App 中,用 Preferences 存储用户设置,用关系型数据库存储任务列表。

5.1、Preferences 轻量级键值存储

Preferences 是键值对存储,使用简单、读写快速、自动持久化,适合存储用户名、主题设置、排序方式等配置信息。下面的 PreferencesManager 封装了常用操作,提供 putString/putInt/putBool 保存数据,getString/getInt/getBool 读取数据,每次写入后调用 flush() 确保数据持久化:

// Preferences管理器:封装轻量级键值对存储
// 适合存储用户设置、配置信息等简单数据
class PreferencesManager {private var preferences: Preferences// 构造函数:初始化Preferences实例init() {// getInstance()获取或创建名为"app_settings"的Preferences实例// 同一个名称在整个应用中共享同一个实例this.preferences = Preferences.getInstance("app_settings")}// 保存字符串类型的键值对func putString(key: String, value: String): Unit {// putString()将数据写入内存preferences.putString(key, value)// flush()将内存中的数据持久化到磁盘// 必须调用flush()才能确保数据在应用关闭后仍然保留preferences.flush()}// 读取字符串类型的值// defaultValue参数提供默认值,当key不存在时返回func getString(key: String, defaultValue: String = ""): String {return preferences.getString(key, defaultValue)}// 保存布尔类型的键值对func putBool(key: String, value: Bool): Unit {preferences.putBool(key, value)preferences.flush()  // 持久化到磁盘}// 读取布尔类型的值func getBool(key: String, defaultValue: Bool = false): Bool {return preferences.getBool(key, defaultValue)}
}

SettingsPage 展示了 Preferences 的实际应用。在 aboutToAppear 生命周期中加载保存的设置,在用户修改时实时保存。这种方式让用户设置在应用重启后依然有效:

// 设置页面:使用Preferences保存和加载用户配置
@Component
struct SettingsPage {// 创建PreferencesManager实例用于数据持久化private var prefs: PreferencesManager = PreferencesManager()// @State管理用户名和深色模式开关状态@State private var username: String = ""@State private var darkMode: Bool = false// aboutToAppear生命周期方法:页面即将显示时调用// 在这里加载之前保存的设置func aboutToAppear() {// 从Preferences中读取用户名,如果不存在则返回空字符串username = prefs.getString("username")// 从Preferences中读取深色模式设置,如果不存在则返回falsedarkMode = prefs.getBool("dark_mode")}func build() {Column(spacing: 20) {// 用户名输入框TextInput(placeholder: "用户名", text: username)// onChange实时监听输入变化.onChange((value: String) => {// 更新状态变量username = value// 立即保存到Preferences,实现自动保存prefs.putString("username", value)})// 深色模式开关Row() {Text("深色模式")// Toggle开关组件,使用$darkMode双向绑定Toggle(isOn: $darkMode)// onChange监听开关状态变化.onChange((value: Bool) => { // 保存深色模式设置到Preferencesprefs.putBool("dark_mode", value) })}}}
}

5.2、关系型数据库操作

关系型数据库适合存储结构化数据,支持 SQL 查询、事务处理、索引优化。在待办事项 App 中,用它存储任务列表,支持按时间、优先级等条件查询和排序。DatabaseHelper 封装了数据库操作,在初始化时创建表结构,提供增删改查方法。

下面的代码展示了完整的数据库操作流程。createTables() 创建 users 表,insertUser() 插入新用户,queryUsers() 查询所有用户并转换为对象数组,updateUser()deleteUser() 实现更新和删除功能:

// 数据库助手:封装关系型数据库的增删改查操作
// 适合存储结构化数据,支持复杂查询和事务处理
class DatabaseHelper {private var db: RdbStore  // 关系型数据库实例// 构造函数:初始化数据库连接并创建表结构init() {// 配置数据库:指定数据库文件名和安全级别// SecurityLevel.S1表示最低安全级别,适合一般应用数据let config = RdbStoreConfig(name: "app.db", securityLevel: SecurityLevel.S1)// 获取或创建数据库实例this.db = RdbStore.getRdbStore(config)// 创建表结构createTables()}// 创建数据库表private func createTables(): Unit {// 使用三引号"""定义多行SQL语句let sql = """CREATE TABLE IF NOT EXISTS users (id INTEGER PRIMARY KEY AUTOINCREMENT,  -- 自增主键name TEXT NOT NULL,                    -- 用户名,不能为空email TEXT UNIQUE,                     -- 邮箱,唯一约束age INTEGER,                           -- 年龄created_at INTEGER                     -- 创建时间戳)"""// executeSql()执行SQL语句db.executeSql(sql)}// 插入新用户:返回新插入记录的IDfunc insertUser(name: String, email: String, age: Int32): Int64 {// ValuesBucket是键值对容器,用于构建插入数据let values = ValuesBucket()values.putString("name", name)      // 设置name字段values.putString("email", email)    // 设置email字段values.putInt("age", age)           // 设置age字段// 自动设置创建时间为当前时间戳values.putLong("created_at", Time.currentTimeMillis())// insert()方法插入数据,返回新记录的IDreturn db.insert("users", values)}// 查询所有用户:返回User对象数组func queryUsers(): Array<User> {// RdbPredicates用于构建查询条件,这里查询users表的所有记录let predicates = RdbPredicates("users")// query()执行查询,返回ResultSet结果集let resultSet = db.query(predicates)// 创建ArrayList存储查询结果var users = ArrayList<User>()// 遍历结果集,goToNextRow()移动到下一行while (resultSet.goToNextRow()) {// 从当前行提取数据,创建User对象let user = User(id: resultSet.getLong("id"),          // 获取id字段name: resultSet.getString("name"),    // 获取name字段email: resultSet.getString("email"),  // 获取email字段age: resultSet.getInt("age")          // 获取age字段)users.append(user)}// 关闭结果集,释放资源resultSet.close()// 将ArrayList转换为数组返回return users.toArray()}
}

六、分布式能力

分布式能力是鸿蒙的核心特色,让数据和任务在多设备间无缝流转。在待办事项 App 中,手机上添加的任务自动同步到平板和手表,在任何设备上标记完成都会实时更新到其他设备。鸿蒙提供了分布式数据同步、分布式任务调度、跨设备迁移等能力,让多设备协同变得简单。

6.1、分布式数据跨设备同步

分布式键值数据库(DistributedKVStore)自动处理数据同步,开发者只需创建实例、写入数据、监听变化,系统会自动将数据同步到同一账号下的其他设备。下面的 DistributedDataManager 封装了分布式数据操作,在初始化时监听数据变化事件,syncData() 方法写入数据并触发同步,handleDataChange() 处理来自其他设备的数据变化:

// 分布式数据管理器:实现跨设备数据同步
// 这是鸿蒙的核心特色功能,让数据在多设备间自动同步
class DistributedDataManager {private var kvStore: DistributedKVStore  // 分布式键值数据库实例// 构造函数:初始化分布式数据库并监听数据变化init() {// 配置分布式数据库let config = KVStoreConfig(bundleName: "com.example.app",  // 应用包名,用于标识应用dataDir: "distributed_data"     // 数据存储目录)// 获取分布式KV存储实例this.kvStore = DistributedKVStore.getInstance(config)// 监听数据变化事件// 当其他设备修改数据时,会触发dataChange事件// lambda表达式{ change => handleDataChange(change) }定义回调函数kvStore.on("dataChange", { change => handleDataChange(change) })}// 同步数据到其他设备func syncData(key: String, value: String): Unit {// put()将数据写入本地数据库kvStore.put(key, value)// sync()触发跨设备同步// 系统会自动将数据同步到同一账号下的其他设备// 开发者无需关心网络传输、冲突解决等细节kvStore.sync()}// 获取数据:从本地数据库读取// 返回String?表示可能返回null(当key不存在时)func getData(key: String): String? {return kvStore.get(key)}// 处理数据变化事件:当其他设备修改数据时调用private func handleDataChange(change: DataChange): Unit {// 打印日志,方便调试println("数据变化: ${change.key} = ${change.value}")// 通过事件总线通知UI层数据已更新// UI组件可以订阅DataChangedEvent来响应数据变化EventBus.post(DataChangedEvent(change))}
}

6.2、跨设备任务流转

class TaskMigrationManager {// 迁移当前任务到其他设备func migrateToDevice(deviceId: String): Bool {try {// 保存当前状态let state = captureCurrentState()// 发起迁移let result = DistributedAbilityKit.continueAbility(deviceId: deviceId,bundleName: "com.example.app",abilityName: "MainAbility",wantParams: state)return result.isSuccess()} catch (e: Exception) {println("迁移失败: ${e.message}")return false}}// 接收迁移的任务func onContinue(wantParams: HashMap<String, Any>): Unit {// 恢复状态restoreState(wantParams)}
}

七、HTTP 网络请求封装

class HttpClient {func get(url: String): Future<Response> {let request = HttpRequest()request.method = HttpMethod.GETrequest.url = urlreturn http.request(request)}func post(url: String, body: String): Future<Response> {let request = HttpRequest()request.method = HttpMethod.POSTrequest.url = urlrequest.header("Content-Type", "application/json")request.body = bodyreturn http.request(request)}
}// 使用示例
@Component
struct DataPage {@State private var data: Array<Item> = []@State private var isLoading: Bool = falsefunc build() {Column() {if (isLoading) {LoadingIndicator()} else {List() {for (item in data) {ListItem() {Text(item.title)}}}}}.onAppear({loadData()})}private async func loadData(): Unit {isLoading = truelet client = HttpClient()let response = await client.get("https://api.example.com/items")if (response.statusCode == 200) {let json = JsonParser.parse(response.body)data = json.as<Array<Item>>()}isLoading = false}
}

八、应用生命周期

8.1、Ability 应用生命周期

class MainAbility <: UIAbility {override func onCreate(want: Want, launchParam: AbilityStartSetting): Unit {println("Ability 创建")// 初始化应用}override func onWindowStageCreate(windowStage: WindowStage): Unit {println("窗口创建")// 加载主页面windowStage.loadContent("pages/Index")}override func onForeground(): Unit {println("应用进入前台")// 恢复数据}override func onBackground(): Unit {println("应用进入后台")// 保存数据}override func onDestroy(): Unit {println("Ability 销毁")// 清理资源}
}

8.2、页面组件生命周期

@Entry
@Component
struct LifecyclePage {@State private var message: String = ""func aboutToAppear() {println("页面即将显示")message = "页面已加载"}func onPageShow() {println("页面显示")}func onPageHide() {println("页面隐藏")}func aboutToDisappear() {println("页面即将销毁")}func build() {Column() {Text(message)}}
}

九、系统权限申请与管理

class PermissionManager {// 检查权限func checkPermission(permission: String): Bool {let result = abilityAccessCtrl.verifyAccessToken(tokenId: getTokenId(),permissionName: permission)return result == GrantStatus.PERMISSION_GRANTED}// 请求权限async func requestPermission(permission: String): Bool {if (checkPermission(permission)) {return true}let result = await abilityAccessCtrl.requestPermissionsFromUser(permissions: [permission])return result.authResults[0] == GrantStatus.PERMISSION_GRANTED}
}// 使用示例
@Component
struct CameraPage {private var permissionManager: PermissionManager = PermissionManager()func build() {Column() {Button("打开相机").onClick(async () => {let hasPermission = await permissionManager.requestPermission("ohos.permission.CAMERA")if (hasPermission) {openCamera()} else {showToast("需要相机权限")}})}}
}

十、应用发布

10.1、应用签名配置

// build-profile.json5
{"app": {"signingConfigs": [{"name": "default","type": "HarmonyOS","material": {"certpath": "cert.pem","storePassword": "your_password","keyAlias": "your_alias","keyPassword": "your_password","profile": "profile.p7b","signAlg": "SHA256withECDSA","storeFile": "keystore.p12"}}]}
}

10.2、HAP 包构建与发布

# 构建 HAP 包
hvigorw assembleHap --mode release# 输出位置
# build/outputs/hap/release/entry-release.hap

十一、调试技巧

11.1、调试日志与错误追踪

// 使用 hilog
import ohos.hiviewdfx.HiLogclass Logger {private const DOMAIN: Int32 = 0x0001private const TAG: String = "MyApp"static func debug(message: String): Unit {HiLog.debug(DOMAIN, TAG, message)}static func info(message: String): Unit {HiLog.info(DOMAIN, TAG, message)}static func warn(message: String): Unit {HiLog.warn(DOMAIN, TAG, message)}static func error(message: String): Unit {HiLog.error(DOMAIN, TAG, message)}
}// 使用
Logger.info("应用启动")
Logger.error("发生错误: ${error.message}")

11.2、应用性能分析工具

class PerformanceTracker {private var startTime: Int64 = 0func start(tag: String): Unit {startTime = Time.nanoTime()Logger.debug("开始: ${tag}")}func end(tag: String): Unit {let duration = (Time.nanoTime() - startTime) / 1_000_000Logger.debug("结束: ${tag}, 耗时: ${duration}ms")}
}// 使用
let tracker = PerformanceTracker()
tracker.start("loadData")
await loadData()
tracker.end("loadData")

十二、关于作者与参考资料

12.1、作者简介

郭靖,笔名“白鹿第一帅”,大数据与大模型开发工程师,中国开发者影响力年度榜单人物。在移动应用开发和 UI 框架方面有丰富经验,对声明式 UI、状态管理、跨平台开发有深入实践。作为技术内容创作者,自 2015 年至今累计发布技术博客 300 余篇,全网粉丝超 60000+,获得 CSDN“博客专家”等多个技术社区认证,并成为互联网顶级技术公会“极星会”成员。

同时作为资深社区组织者,运营多个西南地区技术社区,包括 CSDN 成都站(10000+ 成员)、AWS User Group Chengdu、字节跳动 Trae Friends@Chengdu 等,累计组织线下技术活动超 50 场,致力于推动技术交流与开发者成长。

CSDN 博客地址:https://blog.csdn.net/qq_22695001

12.2、参考资料

  • HarmonyOS 应用开发官方文档
  • ArkUI 声明式开发范式
  • HarmonyOS 分布式数据管理
  • Flutter 开发文档(声明式 UI 参考)
  • SwiftUI 教程(状态管理参考)

文章作者:白鹿第一帅,作者主页:https://blog.csdn.net/qq_22695001,未经授权,严禁转载,侵权必究!


总结

通过待办事项 App 的完整开发实践,我深刻体会到鸿蒙应用开发的技术特点和优势。ArkUI 的声明式语法让 UI 开发变得简洁高效,状态管理机制通过 @State、@Prop、@Link 等装饰器实现了清晰的数据流动,数据持久化方案从轻量级的 Preferences 到关系型数据库再到分布式数据库提供了完整的解决方案,而分布式能力更是鸿蒙的核心竞争力,让数据跨设备同步和任务迁移变得简单自然。开发过程中也遇到一些挑战,比如文档不够完善需要查看源码,社区资源相对较少需要更多探索。建议开发者循序渐进地学习,从基础组件入手,逐步掌握状态管理和数据持久化,最后探索分布式能力。随着鸿蒙生态的快速发展,现在正是入门的最佳时机。

在这里插入图片描述


我是白鹿,一个不懈奋斗的程序猿。望本文能对你有所裨益,欢迎大家的一键三连!若有其他问题、建议或者补充可以留言在文章下方,感谢大家的支持!

http://www.dtcms.com/a/574508.html

相关文章:

  • 领英被封?账号受限该怎么处理?
  • 信誉好的镇江网站建设网站备案名称中国开头
  • 【C语言】localtime和localtime_r;strftime和strftime_l
  • 扁平化设计网站代码打开网站后直接做跳转
  • Go 语言依赖注入实战指南:从基础到高级实践
  • 全场景自动化 Replay 技术:金仓 KReplay 如何攻克数据库迁移 “难验证“ 难题
  • 阳新县建设局网站win2008系统asp网站建设
  • 网站域名分几种新东方雅思培训机构官网
  • 网站怎么样做不违规学科基地网站建设
  • MySQL-4-视图和索引
  • 电脑被捆绑软件缠上?3 步根治卡顿弹窗~
  • Linux时间处理与系统时间管理详解
  • 上饶建设局网站开封到濮阳
  • 织梦网站动态华为云自助建站
  • RocketMQ集群核心概念 生产者端的负载均衡
  • 做恒生指数看什么网站贵州网站优化
  • 百度搜索引擎平台seo全称英文怎么说
  • 黑马点评学习笔记07(缓存工具封装)
  • BLDC电流采样的四种方式
  • 物流行业网站建设市场分析品牌策划方案案例
  • 高校对网站建设的重视郑州建设电商网站
  • 网站后台管理代码凡科h5在线制作
  • 做网站外包多少钱网站建设 工作计划
  • 自己做的网站很卡深圳建立网站公司
  • Trae 大模型选型对比
  • IO多路复用之epoll
  • 模拟一个机械手指:从数学模型到高保真仿真的全平台指南
  • 响应式网站导航栏内容矿区网站建设
  • 网站建设哪家最好wordpress安装到跟目录
  • FFNN(前馈神经网络)层