《鸿蒙ArkTS实战:从零开发一个游戏每日签到应用》
前言
最近开始学习 HarmonyOS 鸿蒙应用开发,为了练手,我写了一个简单的“游戏每日签到”小 Demo。虽然功能简单,但涵盖了鸿蒙开发中核心的 ArkTS 语法、状态管理 (@State) 以及 UI 布局。本文将分享我的开发思路和核心代码,希望能给同样在入门鸿蒙的朋友一点参考。
🛠️ 开发环境
IDE:DevEco Studio (Next版本)
语言:ArkTS (基于 TypeScript)
SDK:HarmonyOS SDK 5.0+
✨ 功能展示
先来看一下最终的运行效果。界面采用深色背景风格,包含累计签到天数显示、签到按钮以及底部的统计信息。
项目运行预览效果如下:


1. 页面结构与状态定义
我们使用 @Entry 和 @Component 装饰器来定义页面入口。为了实现点击按钮后数字自动变化,我们需要使用 @State 装饰器来定义响应式变量。
* ── ArkTS 状态装饰器说明(本文件及鸿蒙开发常用) ──
*
* @Entry : 标记页面入口组件,每个页面有且仅有一个,系统通过它加载并渲染页面。
* @Component : 标记自定义 UI 组件,配合 struct 使用,使结构体具备 UI 构建能力。
*
* @State : 组件内部私有状态。变量变化时,仅刷新持有该状态的组件,驱动 UI 自动重绘。
* 本页面用 @State 管理签到次数、今日签到状态、按钮缩放等 UI 相关数据。
*
* @Prop : 父组件向子组件单向传递数据(父 → 子)。子组件修改 @Prop 不会影响父组件。
* 适用于展示型子组件,如签到统计卡片接收 totalCount 仅用于显示。
*
* @Link : 父组件与子组件双向绑定(父 ↔ 子)。子组件修改 @Link 变量会同步回父组件。
* 适用于表单输入、开关等需要父子联动的场景。
*
* @Provide / @Consume : 跨多层级的祖先后代组件状态共享,无需逐层传递参数。
*
* @StorageLink : 与 AppStorage 双向绑定,修改会同步到全局 AppStorage 并通知所有关联组件。
* @StorageProp : 与 AppStorage 单向绑定,仅读取全局状态,本地修改不写回 AppStorage。
*
* @Watch : 监听 @State/@Prop/@Link 等变量的变化,变化时自动执行指定回调方法。
*
* 本页面采用 @State + Preferences 本地持久化方案,未使用 AppStorage,
* 数据在 aboutToAppear 生命周期中从磁盘加载,签到成功后写回磁盘。
*/
// ═══════════════════════════════════════════════════════════
// 区域一:状态定义
// ═══════════════════════════════════════════════════════════
/**
* @State 累计签到总天数(同时也是累计总次数,每日限签一次)
* 变化时自动触发 build() 重绘,更新顶部与底部的数字展示
*/
@State totalCount: number = 0;
/**
* @State 今日是否已完成签到
* true → 按钮显示"今日已签到"并禁用
* false → 按钮显示"立即签到"并可点击
*/
@State hasCheckedInToday: boolean = false;
/**
* @State 签到按钮缩放比例,用于实现点击时的按压动效
* 点击瞬间缩小至 0.92,松开后弹回 1.0,营造游戏按钮手感
*/
@State buttonScale: number = 1.0;
/**
* @State 数据是否已从 Preferences 加载完毕
* 加载完成前隐藏主内容,避免闪烁默认值
*/
@State isDataLoaded: boolean = false;
/** 上次签到日期(YYYY-MM-DD),不直接驱动 UI,无需 @State */
private lastCheckInDate: string = '';
/** UIAbility 上下文,用于 Preferences 读写,在 aboutToAppear 中初始化 */
private uiContext: common.UIAbilityContext | null = null;
2. UI 布局构建
在 build() 方法中,我们使用了 Column 容器进行垂直布局。为了让界面好看,我设置了一个深色的背景颜色,并使用了 Stack 组件来叠加显示“今日已签到”的遮罩层。
build() {
Stack({ alignContent: Alignment.Center }) {
// ── 深色游戏风渐变背景层 ──
Column()
.width('100%')
.height('100%')
.linearGradient({
angle: 160,
colors: [
['#0f0c29', 0.0],
['#302b63', 0.45],
['#24243e', 1.0]
]
})
if (this.isDataLoaded) {
Column() {
// ── 顶部:累计签到总天数 ──
this.buildTopSection()
Blank()
// ── 中间:大号游戏风签到按钮 ──
this.buildCheckInButton()
Blank()
// ── 底部:累计总次数 ──
this.buildBottomSection()
}
.width('100%')
.height('100%')
.padding({ left: 32, right: 32, top: 60, bottom: 60 })
} else {
// 数据加载中提示
Text('加载中...')
.fontSize(18)
.fontColor('#8899BB')
}
}
.width('100%')
.height('100%')
}
3. 交互逻辑
当用户点击“计算”或“签到”按钮时,我们需要更新状态。
// ═══════════════════════════════════════════════════════════
// 区域三:业务逻辑
// ═══════════════════════════════════════════════════════════
/**
* 页面即将显示时触发(生命周期钩子)
* 在此初始化上下文并从 Preferences 加载历史签到数据
*/
aboutToAppear(): void {
this.uiContext = getContext(this) as common.UIAbilityContext;
this.loadCheckInData();
}
/**
* 从 Preferences 异步加载签到数据,并根据日期判断是否跨天
*/
async loadCheckInData(): Promise<void> {
if (this.uiContext === null) {
return;
}
try {
const data: CheckInData = await CheckInStorage.load(this.uiContext);
this.totalCount = data.totalCount;
this.lastCheckInDate = data.lastCheckInDate;
const today: string = this.getTodayDateString();
// 若上次签到日期等于今天,说明今日已签;若不等于(含空字符串),则允许签到
this.hasCheckedInToday = (this.lastCheckInDate === today);
} catch (err) {
console.error('加载签到数据异常: ' + JSON.stringify(err));
} finally {
this.isDataLoaded = true;
}
}
/**
* 处理签到按钮点击:播放动效 → 校验状态 → 计数 +1 → 持久化
*/
handleCheckInClick(): void {
if (this.hasCheckedInToday) {
return;
}
// 按压动效:先缩小再弹回,模拟游戏按钮点击反馈
animateTo({ duration: 120, curve: Curve.EaseOut }, () => {
this.buttonScale = 0.92;
});
animateTo({ duration: 120, delay: 120, curve: Curve.EaseIn }, () => {
this.buttonScale = 1.0;
});
this.performCheckIn();
}
/**
* 执行签到核心逻辑:更新状态并写入 Preferences
*/
async performCheckIn(): Promise<void> {
if (this.uiContext === null || this.hasCheckedInToday) {
return;
}
const today: string = this.getTodayDateString();
// 累计次数 +1(@State 赋值会自动刷新 UI)
this.totalCount = this.totalCount + 1;
this.lastCheckInDate = today;
this.hasCheckedInToday = true;
await CheckInStorage.save(this.uiContext, this.totalCount, this.lastCheckInDate);
}
🚀 遇到的挑战与解决 (这部分可以让文章更真实,你可以简单写一两句)
问题:一开始不知道如何让点击按钮后数字自动变。 解决:查阅文档发现必须使用 @State 装饰器,普通的变量改变不会触发 UI 刷新。
📝 总结
通过这个小小的签到 Demo,我初步掌握了鸿蒙 ArkTS 的声明式 UI 开发范式。接下来我计划加入数据持久化 (Preferences),这样即使关闭应用,签到记录也不会丢失。 如果你也对鸿蒙开发感兴趣,欢迎在评论区交流!
更多推荐



所有评论(0)