系列文章目录
HarmonyOS期末项目——作业清单(二)任务创建与编辑
HarmonyOS期末项目——作业清单(三)搭建关系型数据库
HarmonyOS期末项目——作业清单(四)通用工具类
HarmonyOS期末项目——作业清单(完结篇)组件与视图
目录
HarmonyOS期末项目——作业清单(三)搭建关系型数据库
前言
在当今快节奏的生活中,高效的任务管理工具对于提高工作和学习效率至关重要。今天,我将介绍一个功能齐全的任务管理应用,并深入探讨其核心功能和实现细节。
一、项目介绍
本项目旨在综合运用本学期所学内容及个人自学知识,使用HarmonyOS 4.0及以上版本开发一款具有实用性和创新性的移动应用软件。该项目不仅是对所学知识的实践,也是对个人开发能力的一次提升和检验。
1. 软件特色
- 美观、遵循 HarmonyOS 设计规范的 UX 设计:
- 应用将采用HarmonyOS的设计规范,确保界面美观统一。
- 使用大量原生组件,保证流畅的用户体验和高效的性能。
- 支持任务名称、截止日期、完成情况分组查看的待办清单:
- 用户可以按任务名称、截止日期和完成情况对待办事项进行分组查看,方便管理和查找。
- 支持用颜色、进度条指示距离截止日期的距离:
- 通过颜色和进度条直观显示每个任务距离截止日期的时间,帮助用户快速了解任务的紧迫性。
- 提供一目了然的时间管理工具,让用户在繁忙的日程中更好地安排和调整任务优先级。
2. 软件运行截图
二、应用主页面设计
用户进入应用的初始页面,提供导航到首页或其他功能页面的入口。
1. 应用入口
1.1 EntryAbility.ts
程序入口类,定义应用程序的启动和生命周期管理。
1.1.1 功能简介
- EntryAbility 是程序的入口类,负责定义应用程序的启动和生命周期管理。
- 在 onWindowStageCreate 方法中,加载主页面 Index.ets 并显示在主窗口中,充当用户访问应用的初始页面。
- 在 onWindowStageDestroy 方法中,释放与 UI 相关的资源。
- onForeground 和 onBackground 方法分别处理应用被切换到前台和后台的情况。
1.1.2 代码文件
import UIAbility from '@ohos.app.ability.UIAbility';
import hilog from '@ohos.hilog';
import window from '@ohos.window';
export default class EntryAbility extends UIAbility {
onCreate(want, launchParam) {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onCreate');
}
onDestroy() {
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onDestroy');
}
onWindowStageCreate(windowStage: window.WindowStage) {
// Main window is created, set main page for this ability
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageCreate');
windowStage.loadContent('pages/Index', (err, data) => {
if (err.code) {
hilog.error(0x0000, 'testTag', 'Failed to load the content. Cause: %{public}s', JSON.stringify(err) ?? '');
return;
}
hilog.info(0x0000, 'testTag', 'Succeeded in loading the content. Data: %{public}s', JSON.stringify(data) ?? '');
});
}
onWindowStageDestroy() {
// Main window is destroyed, release UI related resources
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onWindowStageDestroy');
}
onForeground() {
// Ability has brought to foreground
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onForeground');
}
onBackground() {
// Ability has back to background
hilog.info(0x0000, 'testTag', '%{public}s', 'Ability onBackground');
}
}
1.2 OOBE.ets
提供用户使用说明引导。
1.2.1 功能简介
- 用户进入应用的初始页面,提供了欢迎使用作业清单的引导界面。
- 引导界面使用了 Swiper 组件实现多页轮播效果,展示了作业清单的功能特点和使用方法。
- 用户可通过向左滑动或点击右下角的加号按钮切换到下一页引导内容。
- 在最后一页引导内容中,用户可点击按钮 "开始使用",跳转到首页进行实际的应用操作。
1.2.2 代码文件
import router from '@ohos.router'
class oobeRes {
id: string = ''
imgPath: string = ''
constructor(id, path) {
this.id = id
this.imgPath = path
}
}
class dataSource implements IDataSource {
resource: oobeRes[] = []
private listener: DataChangeListener
constructor(list: oobeRes[]) {
this.resource = list
}
unregisterDataChangeListener(listener: DataChangeListener): void {
}
registerDataChangeListener(listener: DataChangeListener): void {
this.listener = listener
}
getData(index: number) {
return this.resource[index]
}
totalCount(): number {
return this.resource.length
}
}
@Entry
@Component
struct OOBE {
@State message: string = 'Hello World'
private oobeResource: dataSource = new dataSource([
{id: '欢迎使用作业清单', imgPath: 'oobe/o1.png'},
{id: '点击右下角加号新建第一个待办', imgPath: 'oobe/o2.png'},
{id: '在这里可以填写待办信息', imgPath: 'oobe/o3.png'},
{id: '长按待办标记为重要', imgPath: 'oobe/o6.png'},
{id: '截止时间页面\n让您的待办信息一目了然', imgPath: 'oobe/o4.png'},
{id: '通过待办清单进行分类管理', imgPath: 'oobe/o5.png'},
{id: '开始使用作业清单', imgPath: 'oobe/o1.png'},
])
@Builder LongText(value: string) {
Text(value)
.fontSize(24)
.margin({bottom: 20})
.textAlign(TextAlign.Center)
}
@Builder oobePage(oobe: oobeRes, index: number) {
Column() {
Column() {
this.LongText(oobe.id)
Image($rawfile(oobe.imgPath))
.height((index == 0 || index == 6 ? '30%' : '70%'))
.borderRadius((index == 0 || index == 6 ? 16 : 0))
}
if (index == 6) Button('开始使用')
.onClick(() => {
router.back()
})
.width('70%')
}.height('100%')
.justifyContent(FlexAlign.SpaceEvenly)
}
build() {
Row() {
Column() {
Swiper() {
LazyForEach(this.oobeResource, (oobe, id) => {
this.oobePage(oobe, id)
})
}.height('100%')
.width('100%')
.cachedCount(2)
.index(0)
.loop(false)
.indicatorStyle({bottom: 24})
}
.width('100%')
}
.height('100%')
.backgroundColor('#f1f2f3')
}
}
2. 首页
Index.ets (主页面)
2.1 功能简介
- 主页面展示了应用的核心功能模块,包括任务清单、截止时间和设置页面。
- 使用 Tabs 组件实现了页面切换功能,用户可以通过点击底部的选项卡切换不同的功能页面。
- 在页面初始化时,会进行一系列的数据准备操作,包括获取偏好设置、查询任务分类列表和任务列表等。
- 首次打开应用时,如果检测到是首次打开,则会引导用户进行 OOBE(首次使用体验)。
- 如果发现数据库结构有更新,会自动进行数据库字段更新操作。
- 作业清单页面展示了用户的所有任务列表,并提供了增删改查等操作功能。
- 截止时间页面展示了用户任务的截止时间信息,用以提醒用户任务的紧急程度。
- 设置页面展示了用户的设置选项,包括任务列表的显示设置等。
2.2 代码文件
let storage = new LocalStorage({ 'taskTable': 1 })
@Entry(storage)
@Component
struct Index {
@State message: string = 'Hello World'
@State currentIndex: number = 0
@State taskList: Array<TaskListItemData> = []
@State categoryList: Array<TaskCategoryData> = []
@State taskTable: TaskTable = new TaskTable(() => {});
@State categoryTable: CategoryTable = new CategoryTable(() => {})
@State prefData: PreferenceData = new PreferenceData()
private controller: TabsController = new TabsController();
@Builder TabBuilder(title: string, targetIndex: number, selectedImg: Resource, normalImg: Resource) {
Column() {
Image(this.currentIndex === targetIndex ? selectedImg : normalImg)
.size({ width: 25, height: 25 })
.fillColor(this.currentIndex === targetIndex ? '#0A59F7' : '#6B6B6B')
Text(title)
.fontColor(this.currentIndex === targetIndex ? '#0A59F7' : '#6B6B6B')
.fontSize(12)
}
.width('100%')
.height(50)
.justifyContent(FlexAlign.Center)
.onClick(() => {
this.currentIndex = targetIndex
this.controller.changeIndex(targetIndex)
})
}
async aboutToAppear() {
await PreferenceDB.getPreferenceStorage()
let prefFirstTimeOpen = await PreferenceDB.getPreference('firstOpen')
Logger.debug(`PrefDB: query = ${prefFirstTimeOpen}`)
if (prefFirstTimeOpen === '') {
await PreferenceDB.putPreference('firstOpen', 'true')
await PreferenceDB.putPreference('nowVerColumn', JSON.stringify(CommonConstants.TASK_TABLE_INIT.columns))
// OOBE部分
router.pushUrl({
url: "pages/OOBE"
}, router.RouterMode.Single, (err) => {
if (err) {
console.error(`Invoke pushUrl failed, code is ${err.code}, message is ${err.message}`);
}
})
} else {
let last_columns: string[] = JSON.parse(await PreferenceDB.getPreference('nowVerColumn'))
if (last_columns.length == CommonConstants.TASK_TABLE_INIT.columns.length) Logger.debug(`PrefDB: TaskTable Columns up to date`)
else {
this.taskTable.getRdbStore(() => {
CommonConstants.TASK_TABLE_INIT.columns.forEach((column) => {
if (last_columns.indexOf(column) == -1) {
Logger.debug(`PrefDB: New Column detected: ${column}`)
this.taskTable.updateColumns(column, CommonConstants.COLUMN_TYPE[column])
}
})
})
}
}
this.categoryTable.getRdbStore(() => {
this.categoryTable.query(0, (res) => {
// first-time
if (res.length == 0) {
Logger.debug('first install, loading Category')
let newData = new TaskCategoryData()
this.categoryTable.insertData(newData, (id) => {
newData.id = id
this.categoryList.push(newData)
})
} else res.forEach((value) => {
this.categoryList.push(value)
})
this.taskTable.getRdbStore(() => {
this.taskTable.queryTaskName('', (result: TaskListItemData[]) => {
result.sort((a, b) => {
return Number(b.topped == true ? 2 : 0) - Number(a.topped == true ? 2 : 0)
})
this.taskList = result
for (let i = 0; i < this.taskList.length; ++i) {
this.taskList[i].setAll(this.categoryList)
}
Logger.debug(`TaskListItemData = ${JSON.stringify(this.taskList)}`)
}, true)
})
})
})
}
pageTransition() {
PageTransitionExit({ type: RouteType.Push, duration: 500, curve: Curve.Ease })
.slide(SlideEffect.Left)
}
build() {
Column() {
Tabs({ barPosition: (BarPosition.End), controller: this.controller }) {
TabContent() {
TaskList({taskList: $taskList, taskTable: $taskTable, prefData: $prefData, categoryList: $categoryList, categoryTable: $categoryTable})
}.tabBar(this.TabBuilder("清单", 0, $r('app.media.ic_public_home_filled'), $r('app.media.ic_public_home')))
TabContent() {
DDLState({tasks: $taskList, categoryList: $categoryList})
}.tabBar(this.TabBuilder("截止时间", 1, $r('app.media.ic_public_time_filled'), $r('app.media.ic_public_time')))
TabContent() {
Settings({tasks: $taskList})
}.tabBar(this.TabBuilder("设置", 2, $r('app.media.ic_public_settings_filled'), $r('app.media.ic_public_settings')))
}.onChange((index: number) => {
this.currentIndex = index
})
.scrollable(false)
}.width('100%')
.height('100%')
.backgroundColor(CommonUI.COLOR_DEFAULT.background)
}
}
三、关键技术
入口能力和页面管理
- 入口能力(EntryAbility):通过定义EntryAbility类,管理应用的启动和生命周期,确保应用在不同场景下的稳定运行。
- 页面管理(Pages):开发了多个页面(如About、Index、OOBE),分别用于显示应用的相关信息、主要界面和初次使用引导,确保用户在不同操作场景下的流畅体验。
四、遇到的问题及解决方案
1. 问题:Index在Previewer显示白屏
描述:Index在Previewer中不能正常显示,可以在模拟器上显示,而其他页面(比如关于界面)可以在Previewer中正常显示。
解决方案:由于用到了用户首选项存储,涉及到存储内容不能在Previewer中正常运行,必须使用模拟器或者真机才能运行。
2. 问题:首次启动进入用户引导页面
描述:怎么判断应用是否首次启动,首次启动时怎么加载用户引导界面,处理应用非首次启动时的逻辑。
解决方案:通过检查 PreferenceDB 中的 firstOpen 键来判断。如果 firstOpen 键不存在或为空,则认为是首次启动。在首次启动时,将 firstOpen 设置为 true,并通过 router.pushUrl 加载 pages/OOBE 引导界面。检查表的列是否需要更新,并在需要时调用 updateColumns 方法更新列。
总结
在此次项目开发中,通过实现用户引导界面(OOBE)、极大地提升了应用的用户体验和性能表现。在设计上采用了模块化和可复用的组件,确保了代码的可维护性和扩展性。