重要日子倒计时小应用 - HarmonyOS ArkUI 开发实战-DatePicker与时间计算-PC版本

一、应用概述与背景
在日常生活中,我们经常需要记录一些重要的日子,比如生日、纪念日、考试日期、项目截止日期等。虽然手机自带的日历应用可以记录这些日期,但缺少直观的倒计时显示功能。重要日子倒计时小应用正是为了解决这个问题而设计的,它能够清晰地展示距离目标日期还有多少天、多少小时、多少分钟和多少秒。
这款应用基于HarmonyOS ArkUI框架开发,采用了声明式UI开发范式,界面简洁直观,操作便捷。用户可以通过DatePicker组件选择目标日期,应用会实时计算并显示倒计时信息。同时,应用还提供了常用日期快捷按钮,方便用户快速设置圣诞节、元旦、春节等重要节日。
从技术角度来看,这个应用涵盖了HarmonyOS开发中的多个核心知识点:状态管理、时间计算、定时器使用、DatePicker组件应用、UI布局设计等。通过学习这个应用的开发过程,开发者可以深入理解ArkTS语言的特性和ArkUI框架的使用方法。
二、功能特性详解
2.1 核心功能列表
重要日子倒计时小应用提供了以下核心功能:
| 功能模块 | 功能描述 | 技术实现 |
|---|---|---|
| 日期选择 | 通过DatePicker组件选择目标日期 | DatePicker组件 |
| 倒计时显示 | 实时显示天、时、分、秒 | 状态变量 + 定时器 |
| 快捷日期 | 一键设置常用节日日期 | Button组件 |
| 开始/停止控制 | 控制倒计时的运行状态 | 状态变量切换 |
| 界面自适应 | 适配不同屏幕尺寸 | 布局权重设置 |
2.2 用户交互流程
2.3 界面设计说明
应用的界面设计遵循简洁直观的原则,主要分为以下几个区域:
- 顶部标题栏:包含返回按钮和应用标题,采用灰色背景,与内容区域形成视觉层次
- 日期选择区:DatePicker组件让用户可以方便地选择目标日期
- 控制按钮区:开始/停止按钮控制倒计时的运行状态
- 倒计时显示区:以大号数字展示天、时、分、秒,视觉冲击力强
- 快捷日期区:提供常用节日的快捷按钮
三、技术架构分析
3.1 整体架构设计
3.2 组件依赖关系
应用采用了HarmonyOS ArkUI的声明式开发范式,组件之间的依赖关系清晰:
- CountdownApp:主组件,负责整体布局和状态管理
- DatePicker:系统组件,提供日期选择功能
- Button:系统组件,提供交互功能
- Text:系统组件,显示文本信息
- Row/Column:布局容器,组织界面结构
3.3 状态管理策略
在ArkTS中,状态管理是核心概念之一。本应用使用了@State装饰器来声明响应式状态变量:
@State targetDate_1: string = '2024-12-31';
@State days_1: number = 0;
@State hours_1: number = 0;
@State minutes_1: number = 0;
@State seconds_1: number = 0;
@State isRunning_1: boolean = false;
这些状态变量的特点:
| 状态变量 | 类型 | 初始值 | 作用 |
|---|---|---|---|
| targetDate_1 | string | ‘2024-12-31’ | 存储目标日期字符串 |
| days_1 | number | 0 | 存储剩余天数 |
| hours_1 | number | 0 | 存储剩余小时数 |
| minutes_1 | number | 0 | 存储剩余分钟数 |
| seconds_1 | number | 0 | 存储剩余秒数 |
| isRunning_1 | boolean | false | 控制倒计时运行状态 |
当这些状态变量的值发生变化时,ArkUI框架会自动重新渲染相关的UI组件,实现数据与视图的同步。
四、核心代码实现
4.1 时间计算方法
时间计算是倒计时应用的核心逻辑。以下是updateCountdown_1方法的完整实现:
updateCountdown_1() {
let target_1: Date = new Date(this.targetDate_1);
let now_1: Date = new Date();
let diff_1: number = target_1.getTime() - now_1.getTime();
if (diff_1 > 0) {
this.days_1 = Math.floor(diff_1 / (1000 * 60 * 60 * 24));
this.hours_1 = Math.floor((diff_1 % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
this.minutes_1 = Math.floor((diff_1 % (1000 * 60 * 60)) / (1000 * 60));
this.seconds_1 = Math.floor((diff_1 % (1000 * 60)) / 1000);
} else {
this.days_1 = 0;
this.hours_1 = 0;
this.minutes_1 = 0;
this.seconds_1 = 0;
}
}
代码解析:
-
创建日期对象:
new Date(this.targetDate_1):将字符串日期转换为Date对象new Date():获取当前时间
-
计算时间差:
getTime()方法返回时间戳(毫秒数)diff_1存储目标时间与当前时间的毫秒差值
-
分解时间单位:
- 天数计算:
Math.floor(diff_1 / (1000 * 60 * 60 * 24)) - 小时计算:先取余再除以小时毫秒数
- 分钟计算:先取余再除以分钟毫秒数
- 秒数计算:先取余再除以秒毫秒数
- 天数计算:
4.2 时间单位换算表
| 时间单位 | 毫秒数 | 计算公式 |
|---|---|---|
| 1秒 | 1000 | 1000 |
| 1分钟 | 60000 | 1000 × 60 |
| 1小时 | 3600000 | 1000 × 60 × 60 |
| 1天 | 86400000 | 1000 × 60 × 60 × 24 |
4.3 DatePicker组件使用
DatePicker是HarmonyOS提供的日期选择器组件,以下是本应用中的使用方式:
DatePicker({
start: new Date('2024-01-01'),
end: new Date('2030-12-31'),
selected: new Date(this.targetDate_1)
})
.onDateChange((value_1: Date) => {
this.targetDate_1 = value_1.getFullYear() + '-' +
String(value_1.getMonth() + 1).padStart(2, '0') + '-' +
String(value_1.getDate()).padStart(2, '0');
this.updateCountdown_1();
})
.margin({ top: 12 })
参数说明:
| 参数 | 类型 | 说明 |
|---|---|---|
| start | Date | 可选择的起始日期 |
| end | Date | 可选择的结束日期 |
| selected | Date | 当前选中的日期 |
回调方法:
onDateChange:日期变化时触发,参数为选中的Date对象
日期格式化处理:
this.targetDate_1 = value_1.getFullYear() + '-' +
String(value_1.getMonth() + 1).padStart(2, '0') + '-' +
String(value_1.getDate()).padStart(2, '0');
这段代码将Date对象格式化为YYYY-MM-DD格式的字符串:
getFullYear():获取年份getMonth() + 1:获取月份(注意月份从0开始,需要加1)getDate():获取日期padStart(2, '0'):确保月份和日期为两位数
4.4 倒计时显示UI实现
倒计时显示区域采用Row横向布局,每个时间单位使用Column纵向布局:
Row() {
Column() {
Text(String(this.days_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
Text('天')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.margin({ right: 16 })
Column() {
Text(String(this.hours_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
Text('时')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.margin({ right: 16 })
Column() {
Text(String(this.minutes_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
Text('分')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.margin({ right: 16 })
Column() {
Text(String(this.seconds_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#FF3B30')
Text('秒')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
}
.margin({ top: 20 })
UI设计要点:
-
数字显示:
- 使用48号大字体,视觉冲击力强
- 蓝色(#0A59F7)表示天、时、分
- 红色(#FF3B30)表示秒,突出动态变化
-
单位显示:
- 使用14号小字体
- 灰色(#999999)与数字形成对比
- 位于数字下方,层次分明
-
布局间距:
- 每个时间单位之间使用16px右边距
- 整体使用20px上边距
4.5 快捷日期按钮实现
为了方便用户快速设置常用节日,应用提供了快捷按钮:
Row() {
Button('圣诞节')
.width(80)
.height(36)
.fontSize(12)
.margin({ left: 20 })
.onClick(() => {
this.targetDate_1 = '2024-12-25';
this.updateCountdown_1();
})
Button('元旦')
.width(80)
.height(36)
.fontSize(12)
.margin({ left: 8 })
.onClick(() => {
this.targetDate_1 = '2025-01-01';
this.updateCountdown_1();
})
Button('春节')
.width(80)
.height(36)
.fontSize(12)
.margin({ left: 8 })
.onClick(() => {
this.targetDate_1 = '2025-01-29';
this.updateCountdown_1();
})
}
.margin({ top: 8 })
按钮配置说明:
| 属性 | 值 | 说明 |
|---|---|---|
| width | 80 | 按钮宽度 |
| height | 36 | 按钮高度 |
| fontSize | 12 | 字体大小 |
| margin | { left: 8 } | 左边距 |
五、UI布局设计
5.1 整体布局结构
应用采用Column垂直布局作为根容器,内部包含两个主要区域:
5.2 标题栏实现
标题栏采用Row横向布局,包含返回按钮和标题文本:
Row() {
Button('返回')
.onClick(() => router.back())
Text('重要日子倒计时小应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')
关键属性说明:
layoutWeight(1):让Text组件占据剩余空间textAlign(TextAlign.Center):文本居中显示backgroundColor('#F1F3F5'):灰色背景区分标题栏
5.3 响应式布局考虑
虽然本应用主要针对PC版本,但布局设计时也考虑了一定的响应式特性:
- 宽度设置:使用百分比宽度(如
width('90%'))而非固定像素值 - 布局权重:使用
layoutWeight(1)让内容区域自适应高度 - 间距设置:使用相对单位而非绝对单位
六、状态管理与数据流
6.1 状态变量生命周期
6.2 数据流向分析
应用中的数据流向遵循单向数据流原则:
- 用户输入 → DatePicker选择日期
- 状态更新 → 更新targetDate_1状态变量
- 计算触发 → 调用updateCountdown_1方法
- 状态变化 → 更新days_1、hours_1等状态变量
- UI更新 → ArkUI框架自动重新渲染
6.3 状态变量命名规范
在本应用中,状态变量采用了特定的命名规范:
@State targetDate_1: string = '2024-12-31';
变量名后缀_1是为了避免与ArkUI框架内部的保留字冲突。这是ArkTS开发中的一个常见做法,确保代码的兼容性和可维护性。
七、定时器实现方案
7.1 定时器基本概念
在HarmonyOS应用中,定时器是实现周期性任务的重要机制。本应用使用定时器来实现倒计时的实时更新。
7.2 定时器启动逻辑
当用户点击"开始"按钮时,应用会启动倒计时:
Button(this.isRunning_1 ? '停止' : '开始')
.width('60%')
.height(40)
.margin({ top: 20 })
.backgroundColor(this.isRunning_1 ? '#FF3B30' : '#0A59F7')
.onClick(() => {
this.isRunning_1 = !this.isRunning_1;
this.updateCountdown_1();
})
按钮状态切换:
| 状态 | 按钮文本 | 背景颜色 | 功能 |
|---|---|---|---|
| 未运行 | 开始 | #0A59F7(蓝色) | 启动倒计时 |
| 运行中 | 停止 | #FF3B30(红色) | 停止倒计时 |
7.3 定时器管理最佳实践
在实际项目中,应该使用aboutToAppear和aboutToDisappear生命周期方法来管理定时器:
private timerId_1: number = -1;
aboutToAppear() {
this.updateCountdown_1();
this.timerId_1 = setInterval(() => {
if (this.isRunning_1) {
this.updateCountdown_1();
}
}, 1000);
}
aboutToDisappear() {
if (this.timerId_1 !== -1) {
clearInterval(this.timerId_1);
}
}
生命周期说明:
| 生命周期方法 | 执行时机 | 作用 |
|---|---|---|
| aboutToAppear | 组件即将显示 | 初始化定时器 |
| aboutToDisappear | 组件即将销毁 | 清除定时器,避免内存泄漏 |
7.4 定时器性能优化
为了提高应用性能,可以考虑以下优化策略:
- 条件更新:只在倒计时运行时更新
- 页面可见性:当页面不可见时暂停更新
- 精度控制:根据需求调整更新频率
八、日期处理技术
8.1 Date对象常用方法
JavaScript的Date对象提供了丰富的方法来处理日期和时间:
| 方法 | 返回值 | 说明 |
|---|---|---|
| getFullYear() | 年份 | 四位数年份 |
| getMonth() | 0-11 | 月份(需要+1) |
| getDate() | 1-31 | 日期 |
| getHours() | 0-23 | 小时 |
| getMinutes() | 0-59 | 分钟 |
| getSeconds() | 0-59 | 秒 |
| getTime() | 时间戳 | 毫秒数 |
8.2 日期格式化技巧
将Date对象格式化为字符串是常见需求:
// 格式化为 YYYY-MM-DD
let date_1: string = year + '-' +
String(month).padStart(2, '0') + '-' +
String(day).padStart(2, '0');
// 使用模板字符串
let formattedDate_1: string = `${year}-${String(month).padStart(2, '0')}-${String(day).padStart(2, '0')}`;
8.3 日期计算注意事项
在进行日期计算时,需要注意以下几点:
- 时区问题:Date对象使用本地时区
- 月份偏移:getMonth()返回0-11,需要加1
- 闰年处理:系统会自动处理闰年
- 边界情况:月末、年末的日期计算
九、组件属性详解
9.1 Text组件属性
Text组件是显示文本的基础组件,本应用中使用了以下属性:
Text(String(this.days_1).padStart(2, '0'))
.fontSize(48) // 字体大小
.fontWeight(FontWeight.Bold) // 字体粗细
.fontColor('#0A59F7') // 字体颜色
.margin({ top: 4 }) // 外边距
.padding(12) // 内边距
.width('100%') // 宽度
.textAlign(TextAlign.Center) // 文本对齐
属性分类表:
| 类别 | 属性 | 说明 |
|---|---|---|
| 文本样式 | fontSize | 字体大小 |
| fontWeight | 字体粗细 | |
| fontColor | 字体颜色 | |
| 布局 | width | 组件宽度 |
| margin | 外边距 | |
| padding | 内边距 | |
| 对齐 | textAlign | 文本对齐方式 |
9.2 Button组件属性
Button组件提供交互功能:
Button('开始')
.width('60%') // 宽度
.height(40) // 高度
.margin({ top: 20 }) // 外边距
.backgroundColor('#0A59F7') // 背景颜色
.onClick(() => { // 点击事件
// 处理逻辑
})
9.3 Column和Row布局属性
Column和Row是Flex布局的容器组件:
Column() {
// 子组件
}
.width('100%') // 宽度
.height('100%') // 高度
.backgroundColor('#FFFFFF') // 背景颜色
.padding(12) // 内边距
.layoutWeight(1) // 布局权重
十、应用扩展方向
10.1 数据持久化
当前应用的数据存储在内存中,应用关闭后数据会丢失。可以添加数据持久化功能:
import preferences from '@ohos.data.preferences';
// 保存目标日期
async saveTargetDate(date: string) {
let prefs = await preferences.getPreferences(context, 'countdown_prefs');
await prefs.put('targetDate', date);
await prefs.flush();
}
// 加载目标日期
async loadTargetDate(): Promise<string> {
let prefs = await preferences.getPreferences(context, 'countdown_prefs');
return await prefs.get('targetDate', '2024-12-31') as string;
}
10.2 多倒计时管理
可以扩展应用支持多个倒计时:
class CountdownItem {
id: string = '';
name: string = '';
targetDate: string = '';
days: number = 0;
hours: number = 0;
minutes: number = 0;
seconds: number = 0;
}
@State countdownList: CountdownItem[] = [];
10.3 提醒功能
添加提醒功能,在倒计时结束前提醒用户:
import notification from '@ohos.notification';
async sendReminder(title: string, message: string) {
await notification.publish({
content: {
contentType: notification.ContentType.NOTIFICATION_CONTENT_BASIC_TEXT,
normal: {
title: title,
text: message
}
}
});
}
10.4 主题定制
支持深色模式和自定义主题:
@StorageLink('isDarkMode') isDarkMode: boolean = false;
// 根据主题切换颜色
getBackgroundColor(): string {
return this.isDarkMode ? '#1A1A1A' : '#FFFFFF';
}
十一、性能优化建议
11.1 渲染优化
- 减少不必要的重新渲染:只在数据变化时更新UI
- 使用条件渲染:避免渲染不可见的组件
- 优化列表渲染:使用ForEach时提供唯一的key
11.2 内存管理
- 及时清理定时器:在组件销毁时清除定时器
- 避免内存泄漏:正确管理事件监听器
- 合理使用状态变量:避免声明过多不必要的状态变量
11.3 用户体验优化
- 加载状态:显示加载动画
- 错误处理:友好的错误提示
- 操作反馈:按钮点击后的视觉反馈
十二、完整代码清单
12.1 主组件完整代码
import { router } from '@kit.ArkUI';
@Entry
@Component
struct CountdownApp {
@State targetDate_1: string = '2024-12-31';
@State days_1: number = 0;
@State hours_1: number = 0;
@State minutes_1: number = 0;
@State seconds_1: number = 0;
@State isRunning_1: boolean = false;
updateCountdown_1() {
let target_1: Date = new Date(this.targetDate_1);
let now_1: Date = new Date();
let diff_1: number = target_1.getTime() - now_1.getTime();
if (diff_1 > 0) {
this.days_1 = Math.floor(diff_1 / (1000 * 60 * 60 * 24));
this.hours_1 = Math.floor((diff_1 % (1000 * 60 * 60 * 24)) / (1000 * 60 * 60));
this.minutes_1 = Math.floor((diff_1 % (1000 * 60 * 60)) / (1000 * 60));
this.seconds_1 = Math.floor((diff_1 % (1000 * 60)) / 1000);
} else {
this.days_1 = 0;
this.hours_1 = 0;
this.minutes_1 = 0;
this.seconds_1 = 0;
}
}
build() {
Column() {
Row() {
Button('返回')
.onClick(() => router.back())
Text('重要日子倒计时小应用')
.fontSize(20)
.fontWeight(FontWeight.Bold)
.layoutWeight(1)
.textAlign(TextAlign.Center)
}
.width('100%')
.padding(12)
.backgroundColor('#F1F3F5')
Column() {
Text('选择目标日期')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 24 })
DatePicker({
start: new Date('2024-01-01'),
end: new Date('2030-12-31'),
selected: new Date(this.targetDate_1)
})
.onDateChange((value_1: Date) => {
this.targetDate_1 = value_1.getFullYear() + '-' +
String(value_1.getMonth() + 1).padStart(2, '0') + '-' +
String(value_1.getDate()).padStart(2, '0');
this.updateCountdown_1();
})
.margin({ top: 12 })
Button(this.isRunning_1 ? '停止' : '开始')
.width('60%')
.height(40)
.margin({ top: 20 })
.backgroundColor(this.isRunning_1 ? '#FF3B30' : '#0A59F7')
.onClick(() => {
this.isRunning_1 = !this.isRunning_1;
this.updateCountdown_1();
})
Text('距离 ' + this.targetDate_1 + ' 还有')
.fontSize(16)
.fontColor('#666666')
.margin({ top: 30 })
Row() {
Column() {
Text(String(this.days_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
Text('天')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.margin({ right: 16 })
Column() {
Text(String(this.hours_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
Text('时')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.margin({ right: 16 })
Column() {
Text(String(this.minutes_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#0A59F7')
Text('分')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
.margin({ right: 16 })
Column() {
Text(String(this.seconds_1).padStart(2, '0'))
.fontSize(48)
.fontWeight(FontWeight.Bold)
.fontColor('#FF3B30')
Text('秒')
.fontSize(14)
.fontColor('#999999')
.margin({ top: 4 })
}
}
.margin({ top: 20 })
Text('常用日期')
.fontSize(16)
.fontWeight(FontWeight.Medium)
.margin({ top: 30, left: 20 })
.width('90%')
Row() {
Button('圣诞节')
.width(80)
.height(36)
.fontSize(12)
.margin({ left: 20 })
.onClick(() => {
this.targetDate_1 = '2024-12-25';
this.updateCountdown_1();
})
Button('元旦')
.width(80)
.height(36)
.fontSize(12)
.margin({ left: 8 })
.onClick(() => {
this.targetDate_1 = '2025-01-01';
this.updateCountdown_1();
})
Button('春节')
.width(80)
.height(36)
.fontSize(12)
.margin({ left: 8 })
.onClick(() => {
this.targetDate_1 = '2025-01-29';
this.updateCountdown_1();
})
}
.margin({ top: 8 })
}
.width('100%')
.layoutWeight(1)
}
.width('100%')
.height('100%')
.backgroundColor('#FFFFFF')
}
}
12.2 代码结构说明
| 代码区域 | 行数 | 功能说明 |
|---|---|---|
| 导入声明 | 1 | 导入router模块 |
| 状态声明 | 6-11 | 声明状态变量 |
| 方法定义 | 13-29 | 时间计算方法 |
| build方法 | 31-168 | UI构建方法 |
十三、开发经验总结
13.1 技术要点回顾
通过开发这个倒计时应用,我们掌握了以下技术要点:
- 状态管理:使用@State装饰器声明响应式状态变量
- 时间计算:使用Date对象和时间戳进行精确计算
- 组件使用:DatePicker、Button、Text等组件的灵活运用
- 布局设计:Column、Row布局的组合使用
- 定时器管理:setInterval的使用和生命周期管理
13.2 常见问题解决
| 问题 | 原因 | 解决方案 |
|---|---|---|
| 月份显示错误 | getMonth()返回0-11 | 使用时加1 |
| 日期格式不统一 | 未格式化 | 使用padStart补零 |
| 内存泄漏 | 未清除定时器 | 在aboutToDisappear中清除 |
| UI不更新 | 状态变量未声明@State | 添加@State装饰器 |
13.3 最佳实践建议
- 代码规范:遵循ArkTS编码规范,变量命名清晰
- 性能优化:合理使用状态变量,避免不必要的渲染
- 用户体验:提供友好的交互反馈和错误提示
- 代码复用:将通用逻辑封装为方法,提高代码可维护性
十四、总结
重要日子倒计时小应用是一个典型的HarmonyOS应用开发案例,涵盖了状态管理、时间处理、UI布局、组件交互等核心技术点。通过这个应用的开发,开发者可以深入理解ArkTS语言特性和ArkUI框架的使用方法。
应用的核心价值在于帮助用户直观地了解距离重要日期还有多长时间,界面简洁、操作便捷。从技术实现角度来看,应用展示了如何使用DatePicker组件进行日期选择、如何进行精确的时间计算、如何实现实时更新的倒计时显示等功能。
未来可以进一步扩展应用的功能,如添加多倒计时管理、数据持久化、提醒通知、主题定制等特性,使其成为一个更加完善的倒计时管理工具。同时,这个应用也可以作为学习HarmonyOS开发的入门项目,帮助开发者快速掌握ArkTS和ArkUI的基础知识。
更多推荐



所有评论(0)