便签纸实战开发 - 拖拽排序实现与便签管理应用-鸿蒙ArkTS原生开发

1. 应用概述
便签纸(NotePaper)是一款基于HarmonyOS ArkTS框架开发的多便签管理工具,其核心功能是帮助用户快速记录和管理日常碎片化的信息。用户可以创建多个便签,为每个便签选择不同的颜色,编辑标题和内容,并且所有更改都会自动保存。
从技术实现角度来看,便签纸应用展示了HarmonyOS声明式UI开发范式的核心能力,包括左右分栏布局实现、ForEach组件列表渲染、@State装饰器响应式状态管理、TextField和TextArea双向数据绑定、AppStorage数据持久化等关键技术点。整个应用代码结构紧凑(约260行),业务逻辑清晰,非常适合作为HarmonyOS入门学习的实践案例。
本技术博客将从应用架构设计、核心代码实现、状态管理机制、布局系统原理、数据持久化方案等维度,对这款便签纸应用进行全面的技术剖析。通过本文的讲解,读者能够掌握HarmonyOS ArkTS开发中的分栏布局实现、列表渲染机制、实时保存策略等核心技术,为开发更复杂的HarmonyOS应用奠定基础。
2. 技术架构分析
2.1 整体架构设计
便签纸应用采用了单页面架构,整个应用由一个主页面和若干个可复用组件构成。从代码组织角度来看,应用主要分为以下几个核心部分:页面入口组件(NotePaper)、通用标题栏组件(CommonTitleBar)、数据存储工具(AppStorage)。
┌─────────────────────────────────────────────────────────┐
│ NotePaper页面 │
│ ┌─────────────────────────────────────────────────┐ │
│ │ CommonTitleBar组件 │ │
│ │ (导航栏+标题显示) │ │
│ └─────────────────────────────────────────────────┘ │
│ ┌─────────────────────┬───────────────────────────┐ │
│ │ │ │ │
│ │ 便签列表区域 │ 便签编辑区域 │ │
│ │ (Scroll+ForEach) │ (TextField+TextArea) │ │
│ │ │ │ │
│ │ 宽度: 200vp │ 宽度: flexGrow │ │
│ │ │ │ │
│ └─────────────────────┴───────────────────────────┘ │
└─────────────────────────────────────────────────────────┘
│
▼
┌─────────────────────────────────────────────────────────┐
│ 工具层依赖 │
│ ┌─────────────────┐ ┌─────────────────┐ │
│ │ AppStorage │ │ $r资源引用 │ │
│ │ (数据持久化) │ │ (样式资源) │ │
│ └─────────────────┘ └─────────────────┘ │
└─────────────────────────────────────────────────────────┘
在ArkUI框架中,@Entry装饰器标识的组件是页面的入口点。NotePaper组件作为根组件,负责管理所有的状态变量和业务逻辑,包括便签列表管理、当前选中便签ID、编辑内容状态、选中颜色状态等。UI渲染通过build()方法中的声明式代码完成,所有UI更新都由@State状态变量变化自动触发。
2.2 模块依赖关系
从依赖关系的角度分析,便签纸应用主要依赖以下几个核心模块:
CommonTitleBar组件:提供统一的页面标题栏和返回按钮功能,避免了每个页面都重复实现导航栏的代码。该组件接收三个参数:app_title设置页面标题,app_showBack控制返回按钮显示,app_backCallback自定义返回回调函数。
AppStorage工具类:封装了轻量级数据存储的常用操作,为应用提供状态持久化能力。通过app_getString和app_setString方法,应用可以在本地保存用户的便签列表,实现数据跨会话持久化。
$r资源引用:用于引用应用资源目录中定义的资源,包括颜色资源、字符串资源等。这种方式实现了样式与代码的分离,便于统一管理和主题切换。
2.3 数据模型设计
便签纸应用使用了一个简洁但完整的数据模型来存储便签信息:
interface App_NoteItem {
app_id: string; // 便签唯一标识符
app_content: string; // 便签正文内容
app_color: string; // 便签背景颜色(十六进制色值)
app_title: string; // 便签标题
app_createdAt: string; // 创建时间(本地化字符串)
}
每个便签包含五个字段。app_id使用时间戳字符串作为唯一标识,确保每个便签都有不可重复的ID。app_color存储十六进制颜色值,如"#FFE4C4"(贝壳色)、“#FFFACD”(柠檬黄)等六种预设颜色。app_createdAt使用toLocaleString方法生成本地化时间字符串,更适合人类阅读。
便签列表使用App_NoteItem[]数组形式存储在@State修饰的状态变量中,支持创建、编辑、删除、选中切换等操作。这种基于数组的数据结构配合ForEach组件可以高效渲染列表UI。
3. 核心代码详解
3.1 状态管理机制
状态管理是ArkTS响应式编程的核心。在便签纸应用中,@State装饰器用于声明组件内部的状态变量,当这些变量发生变化时,框架会自动触发UI的重新渲染。
@Entry
@Component
struct NotePaper {
@State app_notes: App_NoteItem[] = [];
@State app_currentNoteId: string = '';
@State app_noteContent: string = '';
@State app_noteTitle: string = '';
@State app_selectedColor: string = '#FFE4C4';
private app_noteColors: string[] = ['#FFE4C4', '#FFFACD', '#E0FFFF', '#FFE4E1', '#DDA0DD', '#98FB98'];
状态变量说明:
| 状态变量 | 类型 | 作用描述 |
|---|---|---|
| app_notes | App_NoteItem[] | 存储所有便签列表 |
| app_currentNoteId | string | 当前选中便签的ID |
| app_noteContent | string | 编辑区域正文内容的临时状态 |
| app_noteTitle | string | 编辑区域标题的临时状态 |
| app_selectedColor | string | 当前选中便签的颜色 |
| app_noteColors | string[] | 预设的6种便签颜色(私有变量) |
值得注意的是,app_noteColors被声明为private类型的普通变量,而不是@State状态变量。这是因为颜色列表是静态数据,不需要驱动UI更新,声明为普通变量可以减少不必要的渲染开销。
状态变量的设计遵循了"单一数据源"原则。便签列表数据统一存储在app_notes中,编辑区域的状态(app_noteContent、app_noteTitle、app_selectedColor)都是对当前选中便签的"投影"。这种设计避免了数据不一致的问题,简化了状态管理的复杂度。
3.2 左右分栏布局实现
便签纸应用的核心布局特点是左右分栏:左侧是便签列表(固定宽度200vp),右侧是便签编辑区域(flexGrow占据剩余空间)。这种布局通过Row容器和子组件的宽度配置实现。
Row({ space: 0 }) {
// 左侧便签列表区域
Scroll({ direction: Axis.Vertical }) {
Column({ space: 8 }) {
Button('+ 新建便签')
.width('100%')
.height(56)
// ...按钮样式
ForEach(this.app_notes, (app_note: App_NoteItem) => {
// 列表项渲染
})
if (this.app_notes.length === 0) {
// 空状态提示
}
}
.width('100%')
.padding({ left: 8, right: 8, top: 8, bottom: 8 })
}
.width(200)
.height('100%')
// 右侧便签编辑区域
Column({ space: 16 }) {
if (this.app_currentNoteId) {
// 编辑区域内容
} else {
// 未选中提示
}
}
.width('flexGrow')
.height('100%')
}
.width('100%')
.flexGrow(1)
布局参数详解:
| 组件 | 宽度 | 高度 | 说明 |
|---|---|---|---|
| Row | 100% | flexGrow(1) | 主容器,占据标题栏下方全部可用空间 |
| Scroll(左侧) | 200 | 100% | 固定宽度200vp,高度100%填充父容器 |
| Column(右侧) | flexGrow | 100% | 占据Row的剩余空间 |
Row组件的space参数设置为0,表示左右两个子组件之间没有间距。左侧Scroll组件固定宽度200vp,右侧Column组件使用flexGrow占据全部剩余空间。这种"固定+弹性"的组合是ArkUI布局中实现分栏效果的常用模式。
右侧Column组件内部使用条件渲染(if语句)控制编辑区域和空状态提示的显示。当app_currentNoteId为空字符串时,显示"选择一个便签"的提示;当有选中便签时,显示完整的编辑界面。
3.3 便签列表渲染
便签列表使用ForEach组件实现,这是ArkTS中渲染列表的核心组件。ForEach接收两个参数:要遍历的数组和遍历回调函数。
ForEach(this.app_notes, (app_note: App_NoteItem) => {
Column({ space: 4 }) {
Text(app_note.app_title)
.fontSize(14)
.fontWeight(FontWeight.Bold)
.fontColor($r('app.color.app_color_text_primary'))
Text(this.app_getNotePreview(app_note.app_content))
.fontSize(12)
.fontColor($r('app.color.app_color_text_secondary'))
.maxLines(2)
Text(app_note.app_createdAt)
.fontSize(10)
.fontColor($r('app.color.app_color_text_tertiary'))
}
.width('100%')
.height(80)
.backgroundColor(this.app_currentNoteId === app_note.app_id ? '#007DFF1A' : $r('app.color.app_color_white'))
.borderRadius(8)
.padding(8)
.borderWidth(this.app_currentNoteId === app_note.app_id ? 2 : 0)
.borderColor($r('app.color.app_color_primary'))
.onClick(() => this.app_selectNote(app_note.app_id))
})
列表项的选中状态通过以下机制实现:
背景色变化:当列表项的app_id等于当前选中ID(app_currentNoteId)时,背景色设置为半透明主题色(‘#007DFF1A’),否则为白色。这种视觉反馈让用户清晰知道当前选中的是哪个便签。
边框高亮:选中项设置2vp宽的主题色边框,未选中项边框宽度为0。边框配合背景色变化,形成了明显的选中态视觉效果。
最大行数限制:内容预览文本使用maxLines(2)限制最多显示2行,超出部分自动截断。这个属性配合文本截断函数,可以确保列表项高度固定,布局整齐美观。
ForEach渲染的关键要点:
// ArkTS要求显式声明遍历元素的类型
ForEach(this.app_notes, (app_note: App_NoteItem) => { ... })
// 回调函数必须是箭头函数形式,不能是普通函数表达式
(app_note: App_NoteItem) => { ... }
ArkTS对语法有严格要求,ForEach的回调必须使用箭头函数,且元素类型必须显式声明。这些约束体现了ArkTS静态类型语言的设计理念,在编译时就能发现潜在的类型错误。
3.4 颜色选择器实现
便签纸应用提供了6种预设颜色供用户选择,颜色选择器使用ForEach组件渲染颜色选项,配合Stack组件实现选中标记。
Row({ space: 8 }) {
ForEach(this.app_noteColors, (app_color: string) => {
Stack() {
if (this.app_selectedColor === app_color) {
Text('✓')
.fontSize(16)
.fontColor($r('app.color.app_color_white'))
}
}
.width(32)
.height(32)
.backgroundColor(app_color)
.borderRadius(16)
.borderWidth(this.app_selectedColor === app_color ? 2 : 0)
.borderColor($r('app.color.app_color_primary'))
.justifyContent(FlexAlign.Center)
.alignItems(HorizontalAlign.Center)
.onClick(() => this.app_setColor(app_color))
})
}
.width('100%')
颜色选择器的技术细节:
Stack层叠布局:Stack组件允许子组件层叠放置。在颜色选择器中,圆形色块作为底层,选中标记(勾号)作为顶层。当没有选中时,只有色块显示;当选中时,勾号叠加在色块上方。
圆形裁剪:通过borderRadius(16)将正方形裁剪为圆形。由于Stack的宽高都是32,borderRadius设置为宽高一半(16),即可得到正圆形。
边框高亮:选中状态通过borderWidth(2)和borderColor主题色实现环形边框效果,让用户明确知道当前选中的是哪个颜色。
颜色数组定义:
private app_noteColors: string[] = ['#FFE4C4', '#FFFACD', '#E0FFFF', '#FFE4E1', '#DDA0DD', '#98FB98'];
| 索引 | 色值 | 颜色名称 | 视觉效果 |
|---|---|---|---|
| 0 | #FFE4C4 | 贝壳色 | 暖米色 |
| 1 | #FFFACD | 柠檬黄 | 淡黄色 |
| 2 | #E0FFFF | 淡青绿 | 浅蓝绿色 |
| 3 | #FFE4E1 | 玫瑰淡 | 浅粉红色 |
| 4 | #DDA0DD | 梅红色 | 中等紫色 |
| 5 | #98FB98 | 嫩绿色 | 浅绿色 |
这六种颜色覆盖了暖色系(米、黄、粉)和冷色系(青、绿、紫),用户可以根据便签内容的性质选择合适的颜色,例如用红色系表示紧急事项,绿色系表示完成状态等。
3.5 文本预览截断处理
便签列表中的内容预览需要处理文本过长的情况。便签纸应用实现了app_getNotePreview方法,对超过30个字符的内容进行截断处理。
app_getNotePreview(app_content: string): string {
if (app_content.length <= 30) {
return app_content || '无内容';
}
return app_content.substring(0, 30) + '...';
}
截断逻辑说明:
| 条件 | 返回值 | 说明 |
|---|---|---|
| 长度 <= 30 且非空 | 原文本 | 完整显示,不做截断 |
| 长度 <= 30 且为空 | ‘无内容’ | 空内容时显示占位提示 |
| 长度 > 30 | 前30字符 + ‘…’ | 截断并添加省略号 |
这个函数的实现简洁但考虑周全。首先处理了空字符串的特殊情况,返回语义化的"无内容"提示。然后判断长度是否超过阈值,未超过则直接返回原文本。最后对超过30字符的内容使用substring方法提取前30个字符,并拼接省略号。
需要注意的是,JavaScript的字符串length属性返回的是Unicode码单元数量,对于纯ASCII字符来说是准确的字符数。但对于包含中文、emoji等双字节字符的内容,length可能不等于视觉上的字符数。考虑到便签应用的场景(中文为主),30个字符的限制对于中文内容来说大约是15-20个汉字,基本能满足预览需求。
3.6 实时编辑自动保存
便签纸应用实现了实时编辑自动保存功能,用户在编辑标题或内容时,所有更改会立即保存到本地存储。这种设计省去了手动保存的步骤,用户体验更加流畅。
TextInput({ placeholder: '标题', text: this.app_noteTitle })
.onChange((app_value: string) => {
this.app_noteTitle = app_value;
this.app_updateNote();
})
TextArea({ placeholder: '在这里输入内容...', text: this.app_noteContent })
.onChange((app_value: string) => {
this.app_noteContent = app_value;
this.app_updateNote();
})
自动保存的实现依赖于TextField和TextArea组件的onChange回调。每次用户输入触发变化时,回调函数将新值更新到状态变量,然后调用app_updateNote方法保存更改。
app_updateNote(): void {
const app_index: number = this.app_notes.findIndex(
(app_item: App_NoteItem) => app_item.app_id === this.app_currentNoteId
);
if (app_index !== -1) {
this.app_notes[app_index].app_content = this.app_noteContent;
this.app_notes[app_index].app_title = this.app_noteTitle;
this.app_notes[app_index].app_color = this.app_selectedColor;
this.app_saveNotes();
}
}
app_saveNotes(): void {
app_setString('notepaper_notes', JSON.stringify(this.app_notes));
}
保存流程说明:
- 通过findIndex查找当前选中便签在数组中的索引位置
- 如果找到(索引不为-1),更新该便签的三个字段:内容、标题、颜色
- 调用app_saveNotes将更新后的数组序列化为JSON字符串并存储
实时保存策略的优点是用户无需担心数据丢失,任何编辑操作都会被立即持久化。缺点是频繁的IO操作可能影响性能,特别是在低端设备上。不过对于便签应用这种轻量级数据场景,影响基本可以忽略。
4. 便签管理功能实现
4.1 创建新便签
创建便签是便签纸应用的核心功能之一。用户点击"新建便签"按钮后,应用会创建一个具有默认属性的新便签,并自动切换到编辑模式。
app_newNote(): void {
const app_newNote: App_NoteItem = {
app_id: Date.now().toString(),
app_content: '',
app_color: this.app_selectedColor,
app_title: '新便签',
app_createdAt: new Date().toLocaleString('zh-CN')
};
this.app_notes.unshift(app_newNote);
this.app_selectNote(app_newNote.app_id);
this.app_saveNotes();
}
新便签创建流程:
| 步骤 | 操作 | 说明 |
|---|---|---|
| 1 | 生成唯一ID | 使用Date.now()获取当前时间戳,转换为字符串 |
| 2 | 构造便签对象 | 设置默认标题"新便签",空内容,当前选中颜色 |
| 3 | 添加到列表头部 | unshift方法将新便签插入数组第一位 |
| 4 | 选中新便签 | 调用selectNote切换到新便签的编辑模式 |
| 5 | 保存到存储 | 调用saveNotes持久化数据 |
ID生成策略使用时间戳而非UUID,优点是实现简单、零依赖,缺点是在极端情况下(极快速度连续创建)可能产生相同ID。但对于便签应用的实际使用场景,这个概率可以忽略不计。
unshift方法将新元素插入数组头部,使得最新创建的便签显示在列表最上方,符合用户的操作习惯。如果使用push方法追加到尾部,用户需要滚动列表才能看到新便签。
4.2 选择便签
便签列表中的每个条目都可以点击选中,选中后右侧编辑区域会显示该便签的内容。
app_selectNote(app_id: string): void {
this.app_currentNoteId = app_id;
const app_note = this.app_notes.find(
(app_item: App_NoteItem) => app_item.app_id === app_id
);
if (app_note) {
this.app_noteContent = app_note.app_content;
this.app_noteTitle = app_note.app_title;
this.app_selectedColor = app_note.app_color;
}
}
选择便签的业务逻辑:
更新选中ID:首先将app_currentNoteId更新为点击的便签ID,这个状态变化会触发列表项样式的重新渲染(选中态变化)。
同步编辑区域状态:使用find方法在数组中查找对应ID的便签对象,然后将便签的内容、标题、颜色同步到编辑区域的三个状态变量。同步后,TextField和TextArea组件会自动显示对应的值。
防御性检查:使用if(app_note)判断便签是否存在。虽然理论上ID应该都能找到,但防御性编程可以避免find返回undefined导致的后续访问错误。
4.3 删除便签
删除便签需要处理复杂的边界情况,特别是当删除的是当前选中便签时。
app_deleteNote(app_id: string): void {
this.app_notes = this.app_notes.filter(
(app_item: App_NoteItem) => app_item.app_id !== app_id
);
if (this.app_currentNoteId === app_id) {
if (this.app_notes.length > 0) {
this.app_selectNote(this.app_notes[0].app_id);
} else {
this.app_currentNoteId = '';
this.app_noteContent = '';
this.app_noteTitle = '';
}
}
this.app_saveNotes();
}
删除逻辑的边界处理:
| 场景 | 处理方式 |
|---|---|
| 删除非选中便签 | 直接从数组过滤掉,保留选中状态 |
| 删除选中便签 + 有剩余 | 自动选中列表第一项 |
| 删除选中便签 + 无剩余 | 清空所有编辑状态,显示空提示 |
filter方法返回一个新数组,包含所有ID不匹配的元素,从而实现删除效果。这种"保留不匹配元素"的设计比"删除匹配元素"更加直观。
ArkTS数组操作要点:filter方法返回新数组而不是修改原数组,因此需要将返回值重新赋值给app_notes。这是ArkTS/TypeScript的常见模式,与React的状态更新模式类似。
5. 数据持久化方案
5.1 AppStorage存储机制
便签纸应用使用AppStorage工具类实现数据的持久化存储。AppStorage基于HarmonyOS的preferences系统,提供了键值对形式的轻量级数据存储能力。
// 存储工具类核心方法
static async app_setString(app_key: string, app_value: string): Promise<void> {
if (app_pref_instance !== null) {
try {
await app_pref_instance.putSync(app_key, app_value);
await app_pref_instance.flushSync();
} catch (error) {
console.error(`存储设置失败: ${error}`);
}
}
}
static app_getString(app_key: string, app_defaultValue: string): string {
if (app_pref_instance !== null) {
try {
return app_pref_instance.getSync(app_key, app_defaultValue) as string;
} catch (error) {
console.error(`存储获取失败: ${error}`);
return app_defaultValue;
}
}
return app_defaultValue;
}
存储操作说明:
putSync + flushSync:putSync方法将数据写入内存缓冲区,flushSync方法将缓冲区数据同步到磁盘。两者配合使用确保数据被持久化。仅调用putSync时数据可能仍在内存中,应用异常退出可能导致数据丢失。
同步方法:app_setString是async方法(写入磁盘需要时间),而app_getString是同步方法(读取在内存中即可完成)。这种设计符合存储的读写特性。
错误处理:所有存储操作都包裹在try-catch中,即使存储失败也不会导致应用崩溃。失败时输出错误日志,便于排查问题。
5.2 JSON序列化与反序列化
便签列表是一个JavaScript对象数组,存储到preferences时需要转换为字符串。这个转换过程使用JSON.stringify实现。
// 加载便签列表
app_loadNotes(): void {
const app_storedNotes: string = app_getString('notepaper_notes', '[]');
try {
this.app_notes = JSON.parse(app_storedNotes);
} catch (error) {
this.app_notes = [];
}
if (this.app_notes.length > 0) {
this.app_selectNote(this.app_notes[0].app_id);
}
}
// 保存便签列表
app_saveNotes(): void {
app_setString('notepaper_notes', JSON.stringify(this.app_notes));
}
数据序列化示例:
[
{
"app_id": "1718610245000",
"app_content": "购买牛奶和面包",
"app_color": "#FFE4C4",
"app_title": "购物清单",
"app_createdAt": "2024/6/17 下午3:44:05"
},
{
"app_id": "1718610301000",
"app_content": "回复邮件",
"app_color": "#98FB98",
"app_title": "待办事项",
"app_createdAt": "2024/6/17 下午3:45:01"
}
]
生命周期管理:
| 生命周期 | 执行操作 | 说明 |
|---|---|---|
| aboutToAppear | app_loadNotes() | 组件加载时从存储读取数据 |
| aboutToDisappear | app_saveNotes() | 组件销毁时保存数据 |
| app_newNote() | app_saveNotes() | 创建便签后保存 |
| app_updateNote() | app_saveNotes() | 更新便签后保存 |
| app_deleteNote() | app_saveNotes() | 删除便签后保存 |
6. 用户界面设计
6.1 整体布局结构
便签纸应用的UI采用典型的移动端分栏布局,左侧固定宽度列表区,右侧弹性宽度编辑区。
build() {
Column() {
CommonTitleBar({
app_title: '便签纸',
app_showBack: true
})
Row({ space: 0 }) {
// 左侧列表区域
Scroll({ direction: Axis.Vertical }) { ... }
.width(200)
.height('100%')
// 右侧编辑区域
Column({ space: 16 }) { ... }
.width('flexGrow')
.height('100%')
}
.width('100%')
.flexGrow(1)
}
.width('100%')
.height('100%')
.backgroundColor($r('app.color.app_color_background'))
}
布局层次说明:
| 层级 | 组件 | 职责 |
|---|---|---|
| 0 | Column | 根容器,满屏显示 |
| 1 | CommonTitleBar | 顶部导航栏,56vp高度 |
| 1 | Row | 内容区分栏容器,flexGrow占据剩余高度 |
| 2 | Scroll(左侧) | 便签列表,可滚动 |
| 2 | Column(右侧) | 编辑区域 |
6.2 条件渲染机制
ArkTS支持通过if语句实现条件渲染,这是控制组件显示隐藏的常用方式。便签纸应用在以下场景使用了条件渲染:
// 空列表状态:没有任何便签时显示引导提示
if (this.app_notes.length === 0) {
Column({ space: 8 }) {
Text('暂无便签')
Text('点击上方新建便签')
}
// ...
}
// 编辑区域状态:没有选中便签时显示选择提示
if (this.app_currentNoteId) {
// 显示TextField、TextArea、颜色选择器、删除按钮
} else {
Column({ space: 16 }) {
Text('选择一个便签')
Text('或点击左侧新建一个')
}
// ...
}
条件渲染的优势:
| 特性 | if条件渲染 | visibility属性 |
|---|---|---|
| 原理 | 条件为false时不创建组件 | 始终创建组件,只是控制显示隐藏 |
| 性能 | 更优,不创建无用的组件 | 较差,组件始终占用内存 |
| 适用场景 | 组件完全不需要时 | 需要保持状态时 |
6.3 选中状态视觉反馈
便签纸应用为选中便签提供了清晰的视觉反馈,主要通过背景色和边框两个维度体现:
.backgroundColor(
this.app_currentNoteId === app_note.app_id
? '#007DFF1A' // 选中:半透明主题色
: $r('app.color.app_color_white') // 未选中:白色
)
.borderWidth(
this.app_currentNoteId === app_note.app_id ? 2 : 0
)
.borderColor($r('app.color.app_color_primary'))
选中态设计说明:
半透明背景:使用’#007DFF1A’(1A表示10%的透明度),蓝色调的主题色背景既能突出选中项,又不会过于刺眼影响其他列表项的可读性。
边框高亮:2vp宽的主题色边框形成"描边"效果,与背景色配合使用,增强选中态的识别度。
非选中态简洁:未选中项使用纯白色背景和无边框设计,视觉上更加简洁,让用户专注于当前选中项。
7. 样式与主题
7.1 颜色资源引用
便签纸应用使用$r语法引用应用级颜色资源,实现了样式与代码的分离:
// 背景色
.backgroundColor($r('app.color.app_color_background'))
// 白色
.backgroundColor($r('app.color.app_color_white'))
// 主题色
.fontColor($r('app.color.app_color_primary'))
// 危险色
.backgroundColor($r('app.color.app_color_danger'))
// 主要文本
.fontColor($r('app.color.app_color_text_primary'))
// 次要文本
.fontColor($r('app.color.app_color_text_secondary'))
// 三级文本
.fontColor($r('app.color.app_color_text_tertiary'))
颜色资源引用的优势:
统一管理:所有颜色定义集中存放在resources/base/element/color.json文件中,修改一处即可全局生效。
主题切换:如果应用需要支持多主题(浅色/深色),只需准备多套颜色资源文件,运行时切换资源包即可。
代码清晰:使用语义化的颜色名称(如text_primary、text_secondary)比直接使用色值(如#333333、#666666)更具可读性。
7.2 布局样式规范
便签纸应用的样式设计遵循了ArkUI的最佳实践:
// 容器样式:圆角+内边距
.backgroundColor($r('app.color.app_color_white'))
.borderRadius(8)
.padding(16)
// 间距控制:使用space属性
Column({ space: 8 }) {
// 子组件之间自动有8vp的间距
}
// 响应式宽度
.width('100%') // 占满父容器宽度
.width('flexGrow') // 占据剩余空间
样式规范说明:
圆角值:列表项使用8vp圆角,按钮使用8vp圆角,编辑区域使用8vp圆角。统一的圆角值形成一致的视觉风格。
内边距:列表项使用8vp内边距,编辑区域使用16vp内边距。更大的编辑区域需要更多的留白空间。
字体粗细:标题使用FontWeight.Bold(700),正文使用默认粗细(400)。粗细对比形成视觉层次。
8. 性能优化策略
8.1 渲染性能优化
便签纸应用在渲染优化方面采用了以下策略:
条件渲染减少组件:使用if语句控制空状态提示和编辑区域的渲染,当没有数据时不创建这些区域的组件,减少内存占用和渲染工作量。
ForEach键值稳定:列表渲染使用便签ID作为唯一标识。当列表项顺序变化或添加删除时,框架可以根据ID准确识别变化的项,只更新需要变化的UI。
固定高度列表项:每个列表项固定高度80vp,配合maxLines(2)限制内容行数,确保列表项高度一致。固定高度可以让Scroll组件准确计算滚动区域,优化滚动性能。
8.2 存储性能优化
即时保存策略:每次添加、编辑、删除便签后立即调用保存方法。虽然这会增加一些IO操作,但保证了数据的安全性。
异常处理保护:数据加载时用try-catch包裹JSON.parse操作,即使存储数据损坏也能优雅降级,返回空数组而不是崩溃。
9. 组件生命周期管理
9.1 aboutToAppear回调
aboutToAppear在组件即将显示时调用,执行数据加载和初始化操作:
aboutToAppear(): void {
this.app_loadNotes();
}
初始化流程:
- 从AppStorage读取之前保存的便签列表
- JSON反序列化还原为JavaScript对象数组
- 如果有便签数据,自动选中第一个便签
9.2 aboutToDisappear回调
aboutToDisappear在组件即将销毁时调用,执行数据保存和清理操作:
aboutToDisappear(): void {
this.app_saveNotes();
}
清理流程:
- 将当前便签列表序列化为JSON字符串
- 调用AppStorage保存到本地存储
- 组件销毁,数据已持久化
10. 功能扩展方向
10.1 当前功能总结
便签纸应用实现了以下核心功能:
| 功能模块 | 实现描述 |
|---|---|
| 多便签管理 | 创建、编辑、删除便签,支持多便签并存 |
| 颜色选择 | 6种预设颜色,通过ForEach渲染颜色选择器 |
| 分栏布局 | 左右分栏,固定宽度列表+弹性宽度编辑区 |
| 标题编辑 | TextField组件,支持双向数据绑定 |
| 内容编辑 | TextArea组件,支持多行文本输入 |
| 文本预览 | 30字符截断处理,空内容显示占位提示 |
| 实时保存 | 编辑内容自动持久化,无需手动保存 |
| 选中高亮 | 背景色+边框双重选中态视觉反馈 |
10.2 功能扩展方向
基于当前的便签纸应用架构,可以进行以下功能扩展:
便签搜索功能:添加搜索框,支持按标题或内容关键词过滤便签列表。这需要使用TextInput组件获取用户输入,使用filter方法匹配便签数据。
便签排序功能:支持按创建时间、修改时间、标题字母顺序排序。这需要添加排序选项UI和数组排序逻辑。
富文本支持:当前仅支持纯文本,可以扩展支持加粗、斜体、列表等富文本格式。这需要使用RichText组件或自定义渲染逻辑。
云端同步:接入华为AppGallery Connect的数据管理服务,实现便签跨设备同步。这需要使用AGC的云存储API。
便签分类标签:为便签添加分类标签(如"工作"、“生活”、“学习”),支持按标签筛选。这需要扩展数据模型添加标签字段。
PIN置顶功能:支持将重要便签置顶显示,方便快速访问。这需要在列表渲染时调整顺序,将置顶项放在最前面。
这些扩展方向不仅能增加应用的实用性,也能帮助开发者更深入地掌握HarmonyOS开发的各个方面。希望本文的分析和讲解能够为HarmonyOS开发者提供有价值的参考和指导。
更多推荐



所有评论(0)