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
该代码仅为应用开发示例,功能并不完善。测试界面较简陋,仅供简单测试
写在最后
●如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
●点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
●关注小编,同时可以期待后续文章ing🚀,不定期分享原创知识。
●更多鸿蒙最新技术知识点,请移步前往小编:https://gitee.com/