#星计划# 基于鸿蒙应用开发:任务列表 原创

Aircreach
发布于 2023-12-28 13:19
浏览
6收藏

基于鸿蒙应用开发:任务列表

环境:OpenHarmony 4.0

1.项目介绍

任务列表应用,基于OpenHarmony 4.0开发,主要功能:任务列表增删改查,任务进度显示,后台通知提醒

2.项目架构

main
├─ets
│  ├─component	// 自定义组件
│  │      TaskDialog.ets	// 自定义添加/修改弹窗
│  │      TaskKeyboard.ets
│  │      TaskListItem.ets	// 自定义ListItem组件
│  │      TaskProgress.ets	// 进度展示
│  │
│  ├─entryability	// 程序入口
│  │      EntryAbility.ets
│  │
│  ├─pages
│  │      DetailsPage.ets
│  │      TaskPage.ets		// 主页
│  │
│  ├─pojo	// 实体类
│  │  │  Task.ets
│  │  │  TaskMenuItem.ets
│  │  │
│  │  └─dto	// 增强实体类
│  │          TaskDto.ets
│  │
│  ├─service
│  │      TaskReminderService.ets	// 后台提醒提醒工具类
│  │
│  └─utils	// 工具类
│          FileManager.ets
│          NotificationUtil.ets
│          PreferencesUtil.ets
│          RDBUtil.ets
│          TransferToUtil.ets
│
└─resources	// 资源目录

3.项目解析

需要权限: PUBLISH_AGENT_REMINDER

(1)主界面

应用入口界面,在生命周期aboutToAppear中创建/连接数据库 ‘Task’ ,同时查询数据库中所有数据,为数组赋值。使用List组件遍历数组,实现任务列表的呈现。在自定义Menu组件中提供筛选分类功能,根据onClick()点击事件重新查询对应分类数据并赋值。

预览图

#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区

源码
import font from '@ohos.font'
import relationalStore from '@ohos.data.relationalStore'
import { AddTaskDialog, EditTaskDialog } from '../component/TaskDialog'
import { TaskListItem } from '../component/TaskListItem'
import { TaskProgress } from '../component/TaskProgress'
import { rdbUtil } from '../utils/RDBUtil'
import TaskDto from '../pojo/dto/TaskDto'
import TaskMenuItem from '../pojo/TaskMenuItem'
import router from '@ohos.router'
import { taskReminderService } from '../service/TaskReminderService'

@Entry
@Component
struct TaskPage {
  @Provide arr: TaskDto[] = []
  @Provide progress: number = 0
  @State index: number = -1
  @State isChecked: boolean = false
  @State menuItems: TaskMenuItem[] = [new TaskMenuItem('全部', '#ffffd88b', true), new TaskMenuItem('待完成', '#ffff', false), new TaskMenuItem('已完成', '#ffff', false)]
  @State menuIndex: number = 0
  addTaskDialogController: CustomDialogController = new CustomDialogController({
    builder: AddTaskDialog()
  })
  editTaskDialogController: CustomDialogController = new CustomDialogController({
    builder: EditTaskDialog({index: this.index})
  })

  build() {
    Column() {
      Row() {
        Text('任务进度:').fontSize(30).fontWeight(FontWeight.Bold).fontFamily('02').margin({ left: 10 })
        TaskProgress().margin({ right: 40 }).id('TaskProgress')
      }
      .backgroundColor('#ffff')
      .borderRadius(20)
      .width('90%')
      .height('20%')
      .justifyContent(FlexAlign.SpaceBetween)
      .margin({ top: 20 })

      Row() {
        Button('添加任务').margin({ left: 10 })
          .onClick(() => {
            this.addTaskDialogController.open()
          })
        Text('全选').fontSize(17).fontWeight(FontWeight.Bold).margin({ left: '45%' })
        Checkbox().width(15).onChange((flag: boolean) => {
          this.isChecked = flag
        })
        Image($r('app.media.screen')).width(30).bindMenu(this.filter_Menu())
      }.width('90%').height('5%').margin({ top: 20 })

      List({ space: 10 }) {
        ForEach(this.arr, (item: TaskDto, index: number) => {
          ListItem() {
            TaskListItem({ taskDto: this.arr[index], isChecked: this.isChecked})
          }.swipeAction({
            start: {
              builder: this.taskListItem_Menu(index)
            }
          })
        })
      }.width('90%').height('65%').margin({ top: 20 })

    }.width('100%').backgroundColor('#ffe9e6e6').justifyContent(FlexAlign.Center)
  }

  // 生命周期 初始化数据
  async aboutToAppear() {
    // 注册字体
    font.registerFont({
      familyName: '01',
      familySrc: $rawfile('font/media.ttf')
    })
    font.registerFont({
      familyName: '02',
      familySrc: $rawfile('font/02.ttf')
    })
    font.registerFont({
      familyName: '03',
      familySrc: $rawfile('font/YanZhenQingDuoBaoTaBei-2.ttf')
    })
    await rdbUtil.createRDB('Task').then((rdb) => {
      rdb.executeSql('create table if not exists Task(id integer primary key autoincrement, title string, content string, date string, reminderId integer, state boolean, isExpand boolean)')
    })
    let pre = new relationalStore.RdbPredicates('Task')
    this.arr = await rdbUtil.queryArray <TaskDto> (pre)
    this.progress = this.getProgress()
  }

  // 获取数据
  getData() {
    let predicates: relationalStore.RdbPredicates = new relationalStore.RdbPredicates('Task')
    switch (this.menuIndex) {
      case 0:
        predicates.orderByAsc('state').orderByDesc('id')
        rdbUtil.queryArray <TaskDto> (predicates).then((value: TaskDto[]) => {
          this.arr = value
        })
        return
      case 1:
        predicates.equalTo('state', '0')
        rdbUtil.queryArray <TaskDto> (predicates).then((value: TaskDto[]) => {
          this.arr = value
        })
        return
      case 2:
        predicates.equalTo('state', '1')
        rdbUtil.queryArray <TaskDto> (predicates).then((value: TaskDto[]) => {
          this.arr = value
        })
        return
    }
  }

  // 获取当前完成任务数
  getProgress() : number {
    let num: number = 0
    for (let item of this.arr) {
      if (item.state == '1') {
        num++
      }
    }
    return num
  }

  // 自定义组件 ListItemMenu
  @Builder taskListItem_Menu(index: number) {
    Row({space: 2}) {
      Image($r('app.media.edit03')).width(30).onClick(() => {
        this.index = index
        this.editTaskDialogController.open()
        // router.pushUrl({
        //   url: 'pages/DetailsPage',
        //   params: this.arr[index]
        // }).then(() => {
        //   this.getData()
        // })
      })
      Image($r('app.media.delete03')).width(30).onClick(() => {
        rdbUtil.deleteById('Task', this.arr[index].id)
        // taskReminderService
        this.getData()
      })
    }.width(65)
  }

  // 自定义组件 过滤菜单
  @Builder filter_Menu() {
    Menu() {
      ForEach(this.menuItems, (item: TaskMenuItem, index: number) => {
        MenuItem({content: item.itemContent}).selected(item.isSelected).backgroundColor(item.isSelected ? '#ffffd88b' : '#ffff').onClick(() => {
          this.menuItems.forEach((it: TaskMenuItem, i: number) => {
            if (i == index) {
              it.isSelected = true
            } else {
              it.isSelected = false
            }
          })
          this.menuIndex = index
          this.getData()
        })
      })
    }
  }
}

(2) 自定义ListItem组件

ListItem组件实现复选框勾选完成,展开详细信息功能

预览图

#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区

源码
import { Driver } from '@ohos.UiTest'
import TaskDto from '../pojo/dto/TaskDto'
import Task from '../pojo/Task'
import { rdbUtil } from '../utils/RDBUtil'

@Component
export struct TaskListItem {
  @State isSelected: boolean = false
  @State isExpand: boolean = false
  @Consume progress: number
  @Link isChecked: boolean
  @ObjectLink taskDto: TaskDto

  aboutToAppear() {
    this.isSelected = (this.taskDto.state == '0') ? false : true
    this.isExpand = (this.taskDto.isExpand == '1')
  }

  build() {
    Column() {
      Row() {

        Text(this.taskDto.title)
          .fontSize(20)
          .fontWeight(FontWeight.Bold)
          .fontColor(this.isSelected ? '#ffa7a2a2' : '#ff000000')
          .decoration({type: this.isSelected ? TextDecorationType.LineThrough : TextDecorationType.None, color: '#ffff008f'})
          .maxLines(1).width('90%')

        Checkbox().select(this.isChecked ? true : this.isSelected).width(15).onChange((flag) => {
          this.taskDto.state = flag ? '1' : '0'
          this.isSelected = !this.isSelected
          if (!flag) {
            this.isChecked = false
            this.progress --
          } else {
            this.progress ++
          }
          rdbUtil.updateById <TaskDto> ('Task', this.taskDto.id, this.taskDto)
        })

        Image($r('app.media.expand02')).width(20).rotate({angle: this.isExpand ? 0 : -90}).margin({left: 2})
          .onClick(() => {
            this.isExpand = !this.isExpand
            this.taskDto.isExpand = this.isExpand ? '1' : '0'
            rdbUtil.updateById <TaskDto> ('Task', this.taskDto.id, this.taskDto)
          })

      }.width('95%').height(50).padding(10)

      if (this.isExpand) {
        Text(this.taskDto.content).fontSize(16.5).fontFamily('03').width('90%')
        Text(new Date(this.taskDto.date).toLocaleString()).fontSize(14).fontStyle(FontStyle.Italic).fontColor('#ff835ff5').opacity(0.3).textAlign(TextAlign.End).width('90%')
      }
    }.backgroundColor('#ffff').borderRadius(20).width('100%')
  }
}

(3)自定义Progress组件

获取父组件传入的进度,在环形进度条实时显示

源码
import TaskDto from '../pojo/dto/TaskDto'

@Component
export struct TaskProgress {

  @Consume arr: TaskDto[]
  @Consume progress: number

  // getProgress() : number {
  //   let num: number = 0
  //   for (let item of this.arr) {
  //     if (item.state == '1') {
  //       num++
  //     }
  //   }
  //   return num
  // }

  build() {
    Stack() {
      Progress({value: this.progress, total: this.arr.length, type: ProgressType.Ring})
        .width(100)
        .color('#ff60b1e9')
        .backgroundColor('#ffe75b82')
        .style({status: ProgressStatus.PROGRESSING, enableSmoothEffect: true})

      Text() {
        Span(this.progress.toString())
        Span('/')
        Span(this.arr.length.toString())
      }.fontSize(25)
    }
  }
}

(4)自定义Dialog组件

在 CustomDialog 中获取当前数组,并通过 RdbUtil 在数据库内插入、修改数据,实现数据持久化。同时调起 TaskReminderService 实现后台代理提醒。

预览图

#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区
#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区

源码
import TaskDto from '../pojo/dto/TaskDto'
import Task from '../pojo/Task'
import { taskReminderService, TaskReminderService } from '../service/TaskReminderService'
import { rdbUtil } from '../utils/RDBUtil'

// 添加弹窗
@CustomDialog
export struct AddTaskDialog {
  @State dialogSize: number = 200
  @State areaSize: number = 100
  @State isExpand: boolean = false
  @Consume arr: TaskDto[]

  addTaskDialogController: CustomDialogController
  title: string = ''
  content: string = ''
  date: Date = new Date()

  build() {
    Column() {
      Row() {
        Image($r('app.media.expand01'))
          .width(15)
          .onClick(() => {
            if (this.isExpand) {
              animateTo({ duration: 400, curve: Curve.Linear }, () => {
                this.dialogSize = 200
                this.areaSize = 100
              })
              this.isExpand = !this.isExpand
            } else {
              animateTo({ duration: 400, curve: Curve.Linear }, () => {
                this.dialogSize = 420
                this.areaSize = 200
              })
              this.isExpand = !this.isExpand
            }
          })
      }.justifyContent(FlexAlign.End).width('90%').height(15).margin({top: 5})

      if (this.isExpand) {
        TextInput({text: this.title, placeholder: '请输入标题'}).height(40).width('90%').margin({top: '3%'})
          .onChange((value: string) => {
            this.title = value
          })
      }

      TextArea({text: this.content, placeholder: '请输入任务内容'}).height(this.areaSize).width('90%').margin({top: '3%'})
        .onChange((value: string) => {
          this.content = value
        })
      if (this.isExpand)  {
        Button('设置提醒日期', {type: ButtonType.Normal}).height(40).margin({top: 20}).backgroundColor('#ff48cdc1').onClick(() => {
          DatePickerDialog.show({
            start: new Date("2000-1-1"),
            end: new Date("2100-12-31"),
            selected: this.date,
            showTime:true,
            useMilitaryTime:false,
            disappearTextStyle: {color: '#ffbfbfbf', font: {size: '22fp', weight: FontWeight.Bold}},
            textStyle: {color: '#ffbfbfbf', font: {size: '18fp', weight: FontWeight.Normal}},
            selectedTextStyle: {color: '#ff583db7', font: {size: '14fp', weight: FontWeight.Regular}},
            onDateAccept: (date: Date) => {
              this.date = date
              console.log(this.date.toString());
            }
          })
        })
      }
      Row() {
        // 取消Button
        Button('cancel').onClick(() => {
          AlertDialog.show({
            title: '警告',
            message: '取消将丢失所有内容',
            autoCancel: false,
            alignment: DialogAlignment.Center,
            primaryButton: {
              value: '取消',
              action: () => {}
            },
            secondaryButton: {
              value: '确认',
              action: () => {
                this.addTaskDialogController.close()
              }
            }
          })
        })
        // 确认Button 插入数据到RDB
        Button('confirm').onClick(() => {
          let task: Task
          task = this.isExpand ? new Task(this.title, this.content, this.date.toString()) : new Task('Task未命名' , this.content, new Date().toString())
          console.log(JSON.stringify(task))
          rdbUtil.insert <Task> ('Task', task).then(async (num) => {
            let taskDto = await rdbUtil.queryById <TaskDto> ('Task', num)
            this.arr.push(taskDto)
            // 相同 notificationID 的通知会覆盖
            taskReminderService.init(taskDto.id, taskDto.title, taskDto.date)
            taskReminderService.publish((reminderId: number) => {
              task.reminderId = reminderId
              rdbUtil.updateById <Task> ('Task', taskDto.id, task)
            })
          })
          this.addTaskDialogController.close()
        })
      }
      .width('80%')
      .justifyContent(FlexAlign.SpaceBetween)
      .margin({top: 15})
    }
    .height(this.dialogSize)
    .justifyContent(FlexAlign.Start)
  }
}

// 编辑弹窗
@CustomDialog
export struct EditTaskDialog {

  @Consume arr: TaskDto[]
  @Prop index: number = -1
  @State title: string = ''
  @State content: string = ''
  @State date: Date = new Date()

  editTaskDialogController : CustomDialogController
  aboutToAppear() {
    let taskDto: TaskDto = this.arr[this.index]
    this.title = taskDto.title
    this.content = taskDto.content
    this.date = new Date(taskDto.date)
  }

  build() {
    Column() {
      TextInput({text: this.title, placeholder: '请输入标题'}).height(40).width('90%').margin({top: '3%'})
        .onChange((value: string) => {
          this.title = value
        })
      TextArea({text: this.content, placeholder: '请输入任务内容'}).height(200).width('90%').margin({top: '3%'})
        .onChange((value: string) => {
          this.content = value
        })
      Button('设置提醒日期', {type: ButtonType.Normal}).height(40).margin({top: 20}).backgroundColor('#ff48cdc1').onClick(() => {
        DatePickerDialog.show({
          start: new Date("2000-1-1"),
          end: new Date("2100-12-31"),
          selected: this.date,
          showTime:true,
          useMilitaryTime:false,
          disappearTextStyle: {color: '#ffbfbfbf', font: {size: '22fp', weight: FontWeight.Bold}},
          textStyle: {color: '#ffbfbfbf', font: {size: '18fp', weight: FontWeight.Normal}},
          selectedTextStyle: {color: '#ff583db7', font: {size: '14fp', weight: FontWeight.Regular}},
          onDateAccept: (date: Date) => {
            this.date = date
          }
        })
      })

      Row() {
        // 取消Button
        Button('cancel').onClick(() => {
          AlertDialog.show({
            title: '警告',
            message: '取消将丢失所有内容',
            autoCancel: false,
            alignment: DialogAlignment.Center,
            primaryButton: {
              value: '取消',
              action: () => {}
            },
            secondaryButton: {
              value: '确认',
              action: () => {
                this.editTaskDialogController.close()
              }
            }
          })
        })
        // 确认Button 更新数据到RDB
        Button('confirm').onClick(() => {
          let task: Task = new Task(this.title, this.content, this.date.toString())
          let id: number = this.arr[this.index].id
          rdbUtil.updateById <Task> ('Task', id, task).then(async (num) => {
            let taskDto = await rdbUtil.queryById <TaskDto> ('Task', id)
            this.arr[this.index] = taskDto
            // 相同 notificationID 的通知会覆盖
            taskReminderService.init(taskDto.id, taskDto.title, taskDto.date)
            taskReminderService.publish((reminderId: number) => {
              task.reminderId = reminderId
              rdbUtil.updateById <Task> ('Task', taskDto.id, task)
            })
          })
          this.editTaskDialogController.close()
        })
      }
      .width('80%')
      .justifyContent(FlexAlign.SpaceBetween)
      .margin({top: 15})
    }
    .height(420)
    .justifyContent(FlexAlign.Start)
  }
}

(5)后台代理提醒

发送日历类型后台代理提醒

预览图

#星计划# 基于鸿蒙应用开发:任务列表-鸿蒙开发者社区

源码

import reminderAgentManager from '@ohos.reminderAgentManager'
import { BusinessError, Callback } from '@ohos.base'

const TAG = '[TaskReminderService]'
export class TaskReminderService {

  timer: reminderAgentManager.ReminderRequestCalendar = {
    reminderType: 1,
    actionButton: [
      {title: '忽略', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_CLOSE},
      {title: '延时', type: reminderAgentManager.ActionButtonType.ACTION_BUTTON_TYPE_SNOOZE}
    ],
    title: '[Task]',
    dateTime: this.str2DateTime(new Date().toLocaleString())
  }

  init (id: number, content: string, dateTime: string) {
    this.timer.notificationId = id
    this.timer.content  = content
    this.timer.dateTime = this.str2DateTime(dateTime)
  }

  publish (callback: Callback<number>) {
    try {
      reminderAgentManager.publishReminder(this.timer, (err: BusinessError, reminderId: number) => {
        if (err) {
          console.error(TAG, `  (publish) ERROR => ${JSON.stringify(err)}`)
        } else {
          console.info(TAG, `  (publish) SUCCESS => ${reminderId}`)
          callback(reminderId)
        }
      })
    } catch (error) {
      console.error(TAG, `  (publish) ERROR => ${JSON.stringify(error as BusinessError)}`)
    }
  }

  cancel(id: number) {
    reminderAgentManager.getValidReminders()
  }

  private str2DateTime(str: string): reminderAgentManager.LocalDateTime {
    let date = new Date(str)
    let datetime: reminderAgentManager.LocalDateTime = {
      year: date.getFullYear(),
      // 注意: getMonth 返回 => month - 1
      month: date.getMonth() + 1,
      day: date.getDate(),
      hour: date.getHours(),
      minute: date.getMinutes(),
      second: date.getSeconds()
    }
    return datetime
  }

}
export let taskReminderService = new TaskReminderService()

(6)实体类

Task

@Observed
export default class Task {
  title: string
  content: string
  date: string
  reminderId: number = -1
  state: boolean | string = false
  isExpand: boolean | string = false

  constructor(title : string , content : string , date : string, reminderId ?: number, state ?: boolean | string) {
    this.title = title
    this.content = content
    this.date = date
    if (reminderId) {
      this.reminderId = reminderId
    }
    if (state) {
      this.state = state
    }
  }
}

TaskDto

import Task from '../Task'

@Observed
export default class TaskDto extends Task {
  id: number

  constructor(id: number, title : string , content : string , date : string, reminderId ?: number, state ?: boolean | string) {



    super(title, content, date, (reminderId ? reminderId : -1))
    this.id = id
    this.title = title
    this.content = content
    this.date = date
    if (state) {
      this.state = state
    }
  }
}

TaskMenuItem

@Observed
export default class TaskMenuItem {
  itemContent: string
  bgColor: string
  isSelected: boolean

  constructor(itemContent: string, bgColor: string, isSelected: boolean) {
    this.itemContent = itemContent
    this.bgColor = bgColor
    this.isSelected = isSelected
  }
}
注意:工具类博客 项目所需工具类github

==该代码仅为应用开发示例,功能并不完善。测试界面较简陋,仅供简单测试==

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
已于2023-12-28 13:19:36修改
9
收藏 6
回复
举报
1条回复
按时间正序
/
按时间倒序
qq658d529c8a1f6
qq658d529c8a1f6

很棒的内容


回复
2023-12-28 18:49:32
回复
    相关推荐