鸿蒙开发初体验:手撸一个高颜值的 TodoList(附源码)

哈喽大家好!我是正在死磕鸿蒙开发的大师兄

最近在学 ArkTS,官方文档看得我眼花缭乱,不如直接上手敲个 Demo 来得实在。俗话说得好:“好记性不如烂笔头,烂笔头不如敲键盘”。今天就带大家看看我刚出炉的TodoList(待办清单)

别看它功能简单,里面可是藏着鸿蒙开发的“核心内功”。来,搬好小板凳,咱们一段代码一段代码地“盘”它!


1. 万物皆对象:先造个“砖”

在写界面之前,我们得先定义一下,“待办事项”这玩意儿到底长啥样。
这里我定义了一个简单的 MyItem 类。它只有两个属性:一个独一无二的身份证号 id,还有一个具体的任务内容 text
这就像是盖房子前的烧砖环节,砖头造好了,后面才能砌墙。

class MyItem{
  id:number;
  text:string;

  constructor(id:number,text:string) {
    this.id=id;
    this.text=text;
  }
}

2. 这里的指挥官:主组件与状态管理

接下来是主界面。在鸿蒙里,@State 是个魔法修饰符。一旦你给变量加上了这个标记,UI 界面就会死死地盯着它。只要数据一变,界面立马自动刷新,完全不需要我们手动去操作 DOM 或者 View。

这里我初始化了两个状态:

  1. text:用来接收输入框里的字。
  2. list:存放我们所有的待办任务(为了不让界面太冷清,我先塞了两个测试任务进去)。
@Entry
@Component
struct TodoList {
  @State text: string = '';
  @State list: MyItem[]=[
    new MyItem(Date.now(),"测试任务1"),
    new MyItem(Date.now(),"测试任务2"),
  ]
  
  // build() 函数下面会讲...

3. 颜值即正义:标题与输入框

进入 build() 函数,这就是我们搭积木的地方。
首先是标题,大大的“待办清单”四个字,得加粗,得显眼!

然后是输入框区域。这里有个小技巧:TextInputonChange 事件。你每敲一个字,我就把它同步到 this.text 里。当你点击“添加”按钮时,我不仅要把任务塞进列表,还得顺手把输入框清空,不然还得手动删字,那多累啊。

  build() {
    Row() {
      Column() {
        // 标题区域
        Text('待办清单')
          .fontSize(32)
          .fontWeight(FontWeight.Bold)
          .fontColor('#2c3e50')
          .margin({ top: 40, bottom: 30 })
        
        // 输入区域
        Row() {
          TextInput({ placeholder: '添加新的待办事项...', text: this.text })
            .width('75%')
            .height(48)
            .fontSize(16)
            .backgroundColor('#f8f9fa')
            .borderRadius(8)
            .padding({ left: 16, right: 16 })
            .onChange((value) => {
              this.text = value
            })
          
          Button('添加')
            .width('20%')
            .height(48)
            .fontSize(12)
            .fontColor(Color.White)
            .backgroundColor('#3498db')
            .borderRadius(8)
            .margin({ left: 12 })
            .onClick(() => {
              if(this.text.trim()) {
                this.list.push(new MyItem(Date.now(), this.text.trim()))
                this.text = ""
              }
            })
        }
        .width('100%')
        .margin({ bottom: 10 })
        
        // 下面是列表区域...

4. 列表的魔法:有数据显列表,没数据显图片

这部分逻辑我特意加了个判断:如果 list 里有货,就用 ForEach 循环渲染出来;如果 list 被删光了,那就显示一张“空空如也”的图片,顺便卖个萌。

List 组件里包裹着 ForEach,这是鸿蒙处理列表的标准姿势。注意看 TodoItem,这是我自己封装的子组件(下面会说),这样代码看起来就不会乱成一锅粥。

(PS: 那个 app.media.ic_empty 是我在资源文件夹里放的一张图,大家自己练手时随便找张图替换就行)

        // 列表区域
        if(this.list.length) {
          Text(`${this.list.length} 个待办事项`)
            .fontSize(14)
            .fontColor('#7f8c8d')
            .margin({ bottom: 2 })
          
          List() {
            ForEach(this.list, (item: MyItem, index: number) => {
              ListItem() {
                TodoItem({ item, index, list: this.list })
              }
              .padding({ top: 8, bottom: 8 })
            })
          }
          .layoutWeight(1)
          .divider({
            strokeWidth: 1,
            color: '#ecf0f1',
            startMargin: 20,
            endMargin: 20
          })
        } else {
          Column() {
            Image($r('app.media.ic_empty'))
              .width(120)
              .height(120)
              .margin({ bottom: 20 })
            Text('待办事项空空如也~')
              .fontSize(18)
              .fontColor('#95a5a6')
          }
          .width('100%')
          .height('60%')
          .justifyContent(FlexAlign.Center)
        }
      }
      .width('100%')
      .height('100%')
      .padding({ left: 24, right: 24 })
      .backgroundColor(Color.White)
    }
    .height('100%')
    .backgroundColor('#f5f7fa')
  }
}

5. 子组件:每一行都是戏

最后是 TodoItem 组件,也就是列表里的每一行。
这里我有两个小心思:

  1. 划掉它:点击复选框或文字,字变灰并加上删除线。这种“干掉一个任务”的快感,懂的都懂。
  2. 删掉它:后面跟着一个红色的删除按钮。这里我用 list.splice(index, 1) 直接操作父组件传过来的数组。虽然在大型项目里咱们推荐用事件回调,但在这种小 Demo 里,简单粗暴才是王道!

我还加了个 .onHover,鼠标放上去会有个变色效果,细节拉满有没有!

@Component
struct TodoItem {
  private item: MyItem = new MyItem(Date.now(), "")
  private index: number = 0
  private list: MyItem[] = []

  @State isChecked: boolean = false
  @State isHover: boolean = false

  build() {
    Row() {
      // 复选框区域
      Row() {
        Checkbox()
          .width(24)
          .height(24)
          .selectedColor('#3498db')
          .onChange((value) => {
            this.isChecked = value
          })
        
        Text(this.item.text)
          .fontSize(18)
          .fontColor(this.isChecked ? '#95a5a6' : '#2c3e50')
          .margin({ left: 12 })
          .decoration({
            type: this.isChecked ? TextDecorationType.LineThrough : TextDecorationType.None
          })
          .maxLines(1)
          .textOverflow({ overflow: TextOverflow.Ellipsis })
      }
      .layoutWeight(1)
      .onClick(() => {
        this.isChecked = !this.isChecked
      })
      
      // 删除按钮
      Button('删除')
        .width(60)
        .height(32)
        .fontSize(12)
        .fontColor(Color.White)
        .backgroundColor(this.isHover ? '#e74c3c' : '#e67e22')
        .borderRadius(6)
        .opacity(this.isHover ? 1 : 0.7)
        .onClick(() => {
          this.list.splice(this.index, 1)
        })
        .onHover((isHover) => {
          this.isHover = isHover
        })
    }
    .width('100%')
    .height(56)
    .padding({ left: 16, right: 16 })
    .backgroundColor(this.isHover ? '#f8f9fa' : Color.White)
    .borderRadius(8)
    .shadow({ radius: 2, color: '#00000008', offsetX: 0, offsetY: 1 })
    .onHover((isHover) => {
      this.isHover = isHover
    })
  }
}

📝 总结

搞定!不到 200 行代码,一个功能齐全、长得还不赖的 TodoList 就诞生了。

通过这个 Demo,我们学到了:

  • 如何用 ArkTS 搭建界面(Row, Column)。
  • 如何处理用户输入。
  • 如何渲染列表。
  • 如何让数据驱动 UI 变化。

感觉鸿蒙开发也没那么难嘛(除了偶尔要跟括号做斗争之外 😂)。

希望这个笔记对想入门的小伙伴有帮助,咱们下个 Demo 见!

Logo

作为“人工智能6S店”的官方数字引擎,为AI开发者与企业提供一个覆盖软硬件全栈、一站式门户。

更多推荐