在这里插入图片描述

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));
}

保存流程说明:

  1. 通过findIndex查找当前选中便签在数组中的索引位置
  2. 如果找到(索引不为-1),更新该便签的三个字段:内容、标题、颜色
  3. 调用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();
}

初始化流程:

  1. 从AppStorage读取之前保存的便签列表
  2. JSON反序列化还原为JavaScript对象数组
  3. 如果有便签数据,自动选中第一个便签

9.2 aboutToDisappear回调

aboutToDisappear在组件即将销毁时调用,执行数据保存和清理操作:

aboutToDisappear(): void {
  this.app_saveNotes();
}

清理流程:

  1. 将当前便签列表序列化为JSON字符串
  2. 调用AppStorage保存到本地存储
  3. 组件销毁,数据已持久化

10. 功能扩展方向

10.1 当前功能总结

便签纸应用实现了以下核心功能:

功能模块 实现描述
多便签管理 创建、编辑、删除便签,支持多便签并存
颜色选择 6种预设颜色,通过ForEach渲染颜色选择器
分栏布局 左右分栏,固定宽度列表+弹性宽度编辑区
标题编辑 TextField组件,支持双向数据绑定
内容编辑 TextArea组件,支持多行文本输入
文本预览 30字符截断处理,空内容显示占位提示
实时保存 编辑内容自动持久化,无需手动保存
选中高亮 背景色+边框双重选中态视觉反馈

10.2 功能扩展方向

基于当前的便签纸应用架构,可以进行以下功能扩展:

便签搜索功能:添加搜索框,支持按标题或内容关键词过滤便签列表。这需要使用TextInput组件获取用户输入,使用filter方法匹配便签数据。

便签排序功能:支持按创建时间、修改时间、标题字母顺序排序。这需要添加排序选项UI和数组排序逻辑。

富文本支持:当前仅支持纯文本,可以扩展支持加粗、斜体、列表等富文本格式。这需要使用RichText组件或自定义渲染逻辑。

云端同步:接入华为AppGallery Connect的数据管理服务,实现便签跨设备同步。这需要使用AGC的云存储API。

便签分类标签:为便签添加分类标签(如"工作"、“生活”、“学习”),支持按标签筛选。这需要扩展数据模型添加标签字段。

PIN置顶功能:支持将重要便签置顶显示,方便快速访问。这需要在列表渲染时调整顺序,将置顶项放在最前面。

这些扩展方向不仅能增加应用的实用性,也能帮助开发者更深入地掌握HarmonyOS开发的各个方面。希望本文的分析和讲解能够为HarmonyOS开发者提供有价值的参考和指导。

Logo

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

更多推荐