效果

img

一、代码

先给出所有代码:

// 任务类
class Task {
  //静态变量是所有对象共享的变量,是单例模式
  static id: number = 1
  // 任务名称
  name: string = `任务${Task.id++}`
  //任务状态:是否完成
  finished: boolean = false
}

// 统一的卡片样式(用在row组件)
@Styles
function card() {
  .width('95%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({
    radius: 6,
    color: '#1F000000',
    offsetX: 2,
    offsetY: 4
  })
}

// 任务完成的文本样式
@Extend(Text)
function finishedTask() {
  .decoration({ type: TextDecorationType.LineThrough })
  .fontColor('#B1B2B1')
}

@Entry
@Component
struct PropPage {
  // 总任务数量
  @Provide totalTask: number = 0
  // 已完成任务数量
  @Provide finishedTask: number = 0;
  // 任务数组
  @Provide tasks: Task[] = []

  build() {
    Column({ space: 10 }) {
      // 1.任务卡片
      TaskStatistics()
      // 2.任务列表
      TaskList()
        .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

// 任务列表
@Component
struct TaskList {
  // 总任务数量
  @Consume totalTask: number
  // 已完成任务数量
  @Consume finishedTask: number
  // 任务数组
  @Consume tasks: Task[]

  handleTaskChange() {
    // 更新任务总数量
    this.totalTask = this.tasks.length
    // 更新已完成的任务数量
    this.finishedTask = this.tasks.filter(item => item.finished).length
  }

  build() {
    Column({space: 10}) {
      Button('新增任务')
        .width(200)
        .onClick(() => {
          // 新增任务数据
          this.tasks.push(new Task())
          this.handleTaskChange()
        })
      // 3.任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
              Row() {
                Text(item.name)
                  .fontSize(20)
                Checkbox()
                  .onChange(value => {
                    // 更新当前任务状态
                    item.finished = value
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(index) })
          }
        )
      }
      .width('100%')
      // 设置list中的元素左右居中
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }
  }

  @Builder
  DeleteButton(index: number) {
    Button() {
      Image($r('app.media.ic_public_delete_filled'))
        .fillColor(Color.White)
        .width(20)
    }
    .width(40)
    .height(40)
    .backgroundColor(Color.Red)
    .margin(5)
    .onClick(() => {
      // 删除任务数组中对应下标的数据
      this.tasks.splice(index, 1)
      this.handleTaskChange()
    })
  }
}


// 任务统计组件
@Component
struct TaskStatistics {
  // 总任务数量
  @Consume totalTask: number
  // 已完成任务数量
  @Consume finishedTask: number

  build() {
    Row() {
      Text('任务进度:')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      // 堆叠容器,可以覆盖,组件按照顺序依次入栈,后一个子组件覆盖前一个子组件
      Stack() {
        //进度条
        Progress({
          value: this.finishedTask, //当前进度数
          total: this.totalTask, // 总数
          type: ProgressType.Ring  // 进度条类型
        })
          .width(100)
        Row() {
          Text(this.finishedTask.toString())
            .fontSize(24)
            .fontColor('#36D')
          Text(' / ' + this.totalTask.toString())
            .fontSize(24)
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 10 })
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}

接下来我将一步一步分析代码。

二、分析代码

2.1 类 Task

我们要定义一个类用来存放每一个任务的具体信息,将任务抽象出来,方便将数据用于展示。

class Task {
  static id: number = 1 // 静态变量,用于生成任务的唯一标识
  name: string = `任务${Task.id++}` // 每个任务的名称,默认以任务序号命名
  finished: boolean = false // 标识任务是否完成
}

每个变量具体解释如下:
1、静态变量 id:共享的变量,所有任务对象共用,用于生成唯一的任务名称。
2、属性 name:任务的名称,默认格式为 "任务X"。
3、属性 finished:任务状态,标识任务是否完成,默认为 false。

2.2 样式声明

因为每个卡片样式是统一的,所以我们将卡片的样式统计定义。

// 统一的卡片样式(用在row组件)
@Styles
function card() {
  .width('95%')
  .padding(20)
  .backgroundColor(Color.White)
  .borderRadius(15)
  .shadow({
    radius: 6,
    color: '#1F000000',
    offsetX: 2,
    offsetY: 4
  })
}

2.3 页面入口组件 PropPage

每一个页面都有一个入口文件,用@Entry和@Component装饰器来装饰,负责整体布局的组织和逻辑的控制。

@Entry
@Component
struct PropPage {
  // 总任务数量
  @Provide totalTask: number = 0
  // 已完成任务数量
  @Provide finishedTask: number = 0;
  // 任务数组
  @Provide tasks: Task[] = []

  build() {
    Column({ space: 10 }) {
      // 1.任务卡片
      TaskStatistics()
      // 2.任务列表
      TaskList()
        .layoutWeight(1)
    }
    .width('100%')
    .height('100%')
    .backgroundColor('#F1F2F3')
  }
}

1、属性:@Provide 修饰符:允许子组件访问共享的状态,包括任务总数、完成的任务数、任务数组。
2、UI 结构:
(1)使用 Column 容器将页面分为两部分:
(2)任务统计(TaskStatistics 组件)。
(3)任务列表(TaskList 组件,占用大部分页面空间)。

2.4 任务列表组件 TaskList

因为每个任务的结构和样式是一模一样的,所以我们直接将任务列表封装成一个组件,通过父组件传递任务数组,通过ForEach函数来遍历任务列表,将所有任务动态生成。

// 任务列表
@Component
struct TaskList {
  // 总任务数量
  @Consume totalTask: number
  // 已完成任务数量
  @Consume finishedTask: number
  // 任务数组
  @Consume tasks: Task[]

  handleTaskChange() {
    // 更新任务总数量
    this.totalTask = this.tasks.length
    // 更新已完成的任务数量
    this.finishedTask = this.tasks.filter(item => item.finished).length
  }

  build() {
    Column({space: 10}) {
      Button('新增任务')
        .width(200)
        .onClick(() => {
          // 新增任务数据
          this.tasks.push(new Task())
          this.handleTaskChange()
        })
      // 3.任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
              Row() {
                Text(item.name)
                  .fontSize(20)
                Checkbox()
                  .onChange(value => {
                    // 更新当前任务状态
                    item.finished = value
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(index) })
          }
        )
      }
      .width('100%')
      // 设置list中的元素左右居中
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)
    }
  }

  @Builder
  DeleteButton(index: number) {
    Button() {
      Image($r('app.media.ic_public_delete_filled'))
        .fillColor(Color.White)
        .width(20)
    }
    .width(40)
    .height(40)
    .backgroundColor(Color.Red)
    .margin(5)
    .onClick(() => {
      // 删除任务数组中对应下标的数据
      this.tasks.splice(index, 1)
      this.handleTaskChange()
    })
  }
}

1、@Consume 修饰符:
允许组件从父组件接收共享的数据,包括 totalTask、finishedTask、tasks。

2、功能:
(1)新增任务:

Button('新增任务')
  .onClick(() => { ... })

每次点击新增任务按钮时,创建一个新的 Task 对象并添加到任务数组。
调用 handleTaskChange 更新任务统计信息。

(2)显示任务列表:

// 3.任务列表
      List({ space: 10 }) {
        ForEach(
          this.tasks,
          (item: Task, index: number) => {
            ListItem() {
              Row() {
                Text(item.name)
                  .fontSize(20)
                Checkbox()
                  .onChange(value => {
                    // 更新当前任务状态
                    item.finished = value
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)
            }
            .swipeAction({ end: this.DeleteButton(index) })
          }
        )
      }
      .width('100%')
      // 设置list中的元素左右居中
      .alignListItem(ListItemAlign.Center)
      .layoutWeight(1)

遍历任务数组,用 ForEach 动态渲染每个任务项。
每个任务项是一个 ListItem,包含任务名称和状态切换按钮(Checkbox)。

(3)任务状态更新:

Checkbox()
                  .onChange(value => {
                    // 更新当前任务状态
                    item.finished = value
                    this.handleTaskChange()
                  })
              }
              .card()
              .justifyContent(FlexAlign.SpaceBetween)

监听 Checkbox 状态切换,更新对应任务的 finished 属性。
更新统计信息。

(4)删除任务:

@Builder
  DeleteButton(index: number) {
    Button() {
      Image($r('app.media.ic_public_delete_filled'))
        .fillColor(Color.White)
        .width(20)
    }
    .width(40)
    .height(40)
    .backgroundColor(Color.Red)
    .margin(5)
    .onClick(() => {
      // 删除任务数组中对应下标的数据
      this.tasks.splice(index, 1)
      this.handleTaskChange()
    })
  }

使用滑动操作附加删除按钮,点击后根据任务索引移除任务。
调用 handleTaskChange 更新统计信息。

2.5 任务统计组件 TaskStatistics

// 任务统计组件
@Component
struct TaskStatistics {
  // 总任务数量
  @Consume totalTask: number
  // 已完成任务数量
  @Consume finishedTask: number

  build() {
    Row() {
      Text('任务进度:')
        .fontSize(30)
        .fontWeight(FontWeight.Bold)
      // 堆叠容器,可以覆盖,组件按照顺序依次入栈,后一个子组件覆盖前一个子组件
      Stack() {
        //进度条
        Progress({
          value: this.finishedTask, //当前进度数
          total: this.totalTask, // 总数
          type: ProgressType.Ring  // 进度条类型
        })
          .width(100)
        Row() {
          Text(this.finishedTask.toString())
            .fontSize(24)
            .fontColor('#36D')
          Text(' / ' + this.totalTask.toString())
            .fontSize(24)
        }
      }
    }
    .card()
    .margin({ top: 20, bottom: 10 })
    .justifyContent(FlexAlign.SpaceEvenly)
  }
}

1、@Consume 修饰符:接收任务总数(totalTask)和已完成任务数(finishedTask)。

2、UI:
(1)使用 Row 布局:显示任务进度和任务统计信息。
(2)进度条:

Progress({
  value: this.finishedTask,
  total: this.totalTask,
  type: ProgressType.Ring
})

显示任务完成进度,类型为环形(Ring)。

(3)统计文本:
任务完成数 finishedTask 和总任务数 totalTask 动态显示。

三、功能点解析

  1. 任务新增
    点击 新增任务 按钮,新增一个任务到任务数组中,动态更新页面。

  2. 任务完成状态切换
    通过 Checkbox 切换任务完成状态,同时更新统计数据。

  3. 任务删除
    滑动任务项出现删除按钮,点击后移除任务并更新统计数据。

  4. 任务统计
    TaskStatistics 显示任务总数和完成任务数,并通过环形进度条直观显示完成比例。

Logo

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

更多推荐