前言

本篇我们主要做的是从零做一个能跑的"待办事项 App"——增、删、改、持久化、动画、主题全用上。

A hand-drawn doodle illustration on pure white pap

做完这个,你对 HarmonyOS 开发会有"整体感"。代码我分模块给你,跟着敲就能跑。

先想清楚要什么

一个待办 App 的最小功能:

  1. 列表展示待办项(标题、是否完成)
  2. 新增待办
  3. 勾选完成(带过渡动画)
  4. 删除待办
  5. 数据持久化(关掉再开还在)

A hand-drawn doodle illustration on pure white pap

  1. 深色模式切换(顺手加)

数据模型 & 存储

Preferences 存简单列表(前面讲过的)。先定义模型:

代码实现

interface Todo {
  id: number
  title: string
  done: boolean
}

// 存储封装
import { preferences } from '@kit.ArkData'
let prefs: preferences.Preferences

async function initStore() {
  prefs = await preferences.getPreferences(getContext(), 'todo')
}
async function save(list: Todo[]) {
  await prefs.put('list', JSON.stringify(list))
  await prefs.flush()
}
async function load(): Promise<Todo[]> {
  const s = await prefs.get('list', '[]') as string
  return JSON.parse(s) as Todo[]
}
  • 用 JSON 把数组序列化存进 Preferences,简单够用。
  • save 写完必须 flush,否则重启丢失(老坑了)。

主页面:列表 + 状态

@Entry
@ComponentV2
struct TodoApp {
  @Local todos: Todo[] = []
  @Local input: string = ''
  @Local dark: boolean = false

  aboutToAppear() {
    initStore().then(() => load()).then(list => this.todos = list)
  }

  build() {
    Column() {
      // 顶部:标题 + 主题切换
      Row() {
        Text('我的待办').fontSize(22).fontWeight(FontWeight.Bold)
        Blank()
        Toggle({ type: ToggleType.Switch, isOn: this.dark })
          .onChange(v => this.dark = v)
      }.padding(12)

      // 输入区
      Row() {
        TextInput({ placeholder: '写点什么...', text: this.input })
          .onChange(v => this.input = v).layoutWeight(1)
        Button('添加').onClick(() => this.add())
      }.padding({ left: 12, right: 12, bottom: 8 })

      // 列表
      List() {
        ForEach(this.todos, (t: Todo) => {
          ListItem() {
            this.TodoRow(t)
          }
        }, (t: Todo) => t.id.toString())
      }
      .layoutWeight(1)
    }
    .backgroundColor(this.dark ? '#1A1A1A' : '#F5F5F5')
    .width('100%').height('100%')
  }
}

A hand-drawn doodle illustration on pure white pap

讲解:

  • aboutToAppear 里初始化存储并加载已有数据。
  • ForEach 渲染列表,用 id 当唯一键(重要,前面踩坑篇讲过)。
  • 主题切换直接改 dark 状态,根背景随之变化。

列表项:勾选 + 删除 + 动画

@Builder
TodoRow(t: Todo) {
  Row() {
    Checkbox({ name: t.id.toString(), group: 'todo' })
      .select(t.done)
      .onChange(v => this.toggle(t.id, v))
    Text(t.title)
      .fontSize(16)
      .decoration({ type: t.done ? TextDecorationType.LineThrough : TextDecorationType.None })
      .fontColor(t.done ? '#999999' : (this.dark ? '#EEEEEE' : '#333333'))
      .layoutWeight(1)
    Button('删').onClick(() => this.remove(t.id))
  }
  .padding(12)
  .backgroundColor(this.dark ? '#2A2A2A' : '#FFFFFF')
  .borderRadius(8)
  .margin({ left: 12, right: 12, bottom: 8 })
}

toggle(id: number, done: boolean) {
  animateTo({ duration: 250, curve: Curve.EaseOut }, () => {
    this.todos = this.todos.map(t => t.id === id ? { ...t, done } : t)
  })
  this.persist()
}
  • Checkbox 勾选触发 toggle,用 animateTo 让"划线 + 变灰"平滑过渡。
  • decoration 给完成的项加删除线,视觉反馈清晰。
  • 改完立刻 persist() 存盘。

增与删

add() {
  if (!this.input.trim()) return
  const item: Todo = { id: Date.now(), title: this.input.trim(), done: false }
  this.todos = [item, ...this.todos]   // 新项置顶
  this.input = ''
  this.persist()
}
remove(id: number) {
  animateTo({ duration: 200 }, () => {
    this.todos = this.todos.filter(t => t.id !== id)
  })
  this.persist()
}
persist() {
  save(this.todos)   // 防抖可后续优化
}
  • 新增用 Date.now() 当唯一 id(够用,且天然唯一)。
  • 删除用 filter,配合 animateTo 有消失动画。
  • 每次变更都 persist() 落盘,保证持久化。

这个 App 用到了哪些知识

能力 来自哪篇
Column/Row 布局 布局篇
@Local 状态驱动 状态原理篇
ForEach 唯一键 列表踩坑篇
Checkbox/TextInput/Toggle 表单组件
animateTo 动画 属性动画篇
Preferences 存储 轻量存储篇
aboutToAppear 初始化 生命周期篇
主题切换 深色模式篇

你看,一个看似完整的小 app,就是把前面零散的知识点"拼"起来。这就是实战的意义:知识不再是孤岛。

常见误区(小白必踩)

误区 说明
唯一键用下标 ForEach 用下标当键,删除会错乱,用 id(如 Date.now())当唯一键。
改完不 flush Preferences 存待办忘了 flush,重启数据全丢。
勾选用 animateTo 套错对象 动画要包在状态变更上,包错地方就没过渡效果。
主题不持久化 深色模式切换后没存 Preferences,下次打开又回浅色。

下面这段代码可以直接复制到 DevEco Studio 里运行。建议你边读边敲,改一改文末「动手改一改」里的参数,亲眼看看效果。

完整可运行示例:待办 App 的持久化

把前面综合实战里的待办数据落盘,关掉 App 再开数据还在。

核心代码

// 用 Preferences 持久化待办列表
import { preferences } from '@kit.ArkData'

async function saveTodos(list: string[]) {
  const pref = await preferences.getPreferences(getContext(), 'todo')
  await pref.put('list', JSON.stringify(list))   // 数组序列化后存
  await pref.flush()
}

async function loadTodos(): Promise<string[]> {
  const pref = await preferences.getPreferences(getContext(), 'todo')
  const s = pref.get('list', '[]') as string
  return JSON.parse(s) as string[]
}

你会看到什么:在综合实战的待办 App 里接上这两个函数——新增待办后调用 saveTodosaboutToAppear 时调用 loadTodos 恢复。这样即使杀掉进程,下次打开待办仍在。

动手改一改

  • Preferences 换成 relationalStore,支持「按完成状态筛选」。
  • 给每条待办加 iddone 字段,做「已完成划线」。
  • 用分布式 KV 把待办同步到平板,体验多端一致。

写在最后

这个待办 App 虽小,却是 HarmonyOS 开发的"微缩全景"——布局、状态、列表、动画、存储、主题全齐了。

建议你别只是看,把代码敲进 DevEco 真跑一遍,再自己加个功能(比如"编辑待办"“按完成筛选”)。当你能把零散知识拼成完整产品,才算真正入门 HarmonyOS 开发。

Logo

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

更多推荐