HarmonyOS NEXT 实战:零基础实现屏幕使用时间追踪器(ScreenTimeTracker)
一、前言
你是否好奇自己每天在手机上花了多少时间?哪些 App 占据了你最多的注意力?
今天我们就用 HarmonyOS NEXT(API20) 手把手实现一个「屏幕使用时间追踪器」。通过这个实战项目,你将掌握:
✅ HarmonyOS NEXT 页面布局与组件使用
✅ Scroll 滚动容器 + ForEach 列表渲染
✅ 状态管理 @State 与数据驱动 UI 刷新
✅ 综合 UI 设计:卡片、进度条、图标
✅ API20 完全兼容,无弃用 API
项目名称:ScreenTimeTracker
SDK 版本:HarmonyOS 4.x / API 20
开发工具:DevEco Studio
项目模板:Empty Ability
二、项目效果展示
2.1 界面总览
应用分为三个核心区域:
区域 内容
🏆 顶部标题栏 应用名称 + 右侧刷新按钮
📊 统计卡片 今日总使用时长(大字体展示)+ 解锁次数 + 拿起次数
📋 App 明细列表 各应用使用时长 + 彩色进度条
2.2 交互功能
🔄 点击刷新按钮:随机生成新数据模拟不同场景
📱 应用列表:每个 App 显示图标 + 名称 + 时长 + 进度条
📊 进度条颜色:每个 App 使用品牌色,视觉区分度高
三、完整代码(可直接运行)
将以下代码复制到 entry/src/main/ets/pages/Index.ets 中,点击运行即可。
typescript
interfaceAppUsageItem{appName:string;icon:string;minutes:number;color:string;}@Entry@Componentstruct ScreenTimeTracker{@StatetotalMinutes:number=187;@StateunlockCount:number=46;@Statepickups:number=62;@StateappList:AppUsageItem[]=[{appName:'微信',icon:'💬',minutes:52,color:'#07C160'},{appName:'抖音',icon:'🎵',minutes:38,color:'#333333'},{appName:'浏览器',icon:'🌐',minutes:27,color:'#4285F4'},{appName:'小红书',icon:'📕',minutes:22,color:'#FF2442'},{appName:'哔哩哔哩',icon:'📺',minutes:18,color:'#FB7299'},{appName:'其他',icon:'📱',minutes:30,color:'#94A3B8'}];// ========== 工具方法 ==========/** 将总分钟数格式化为"X小时Y分钟" */getTotalHours():string{consth=Math.floor(this.totalMinutes/60);constm=this.totalMinutes%60;returnh+'小时'+m+'分钟';}/** 获取最大分钟数(用于进度条100%基准) */getMaxMinutes():number{letmax=0;for(leti=0;i<this.appList.length;i++){if(this.appList[i].minutes>max){max=this.appList[i].minutes;}}returnmax;}/** 计算进度条宽度百分比 */getBarWidth(percent:number):string{if(percent>100){percent=100;}return''+percent+'%';}/** 模拟刷新数据 */refreshData():void{this.totalMinutes=120+Math.floor(Math.random()*150);this.unlockCount=20+Math.floor(Math.random()*50);this.pickups=30+Math.floor(Math.random()*60);constnames:string[]=['微信','抖音','浏览器','小红书','哔哩哔哩','其他'];consticons:string[]=['💬','🎵','🌐','📕','📺','📱'];constcolors:string[]=['#07C160','#333333','#4285F4','#FF2442','#FB7299','#94A3B8'];constnewList:AppUsageItem[]=[];letremaining:number=this.totalMinutes;for(leti=0;i<5;i++){constm=Math.min(remaining-5,5+Math.floor(Math.random()*(remaining-5)/3));newList.push({appName:names[i],icon:icons[i],minutes:m,color:colors[i]});remaining=remaining-m;}newList.push({appName:names[5],icon:icons[5],minutes:remaining,color:colors[5]});this.appList=newList;}// ========== UI 构建 ==========build(){Scroll(){Column(){// ====== 标题栏 ======Row(){Text('📱 屏幕使用时间').fontSize(24).fontWeight(FontWeight.Bold)Blank()Button('🔄').width(40).height(40).borderRadius(20).backgroundColor('#F1F5F9').fontSize(18).fontColor('#333').onClick(()=>this.refreshData())}.width('92%').margin({top:20,bottom:16})// ====== 总时长卡片 ======Column(){Text('今日屏幕使用').fontSize(14).fontColor('#94A3B8')Text(this.getTotalHours()).fontSize(48).fontWeight(FontWeight.Bold).fontColor('#1E293B').margin({top:8,bottom:8})Row(){Column(){Text(''+this.unlockCount).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#3B82F6')Text('解锁次数').fontSize(12).fontColor('#94A3B8')}.layoutWeight(1)Column(){Text(''+this.pickups).fontSize(20).fontWeight(FontWeight.Bold).fontColor('#F59E0B')Text('拿起次数').fontSize(12).fontColor('#94A3B8')}.layoutWeight(1)}.padding({top:12})}.width('92%').padding(20).backgroundColor('#FFFFFF').borderRadius(16).shadow({radius:8,color:'#10000000',offsetY:2}).margin({bottom:16}).alignItems(HorizontalAlign.Center)// ====== App 明细标题 ======Row(){Text('应用使用明细').fontSize(18).fontWeight(FontWeight.Bold)}.width('92%').margin({bottom:10})// ====== App 列表 ======Column(){ForEach(this.appList,(item:AppUsageItem)=>{Column(){Row(){Text(item.icon).fontSize(22).margin({right:10})Column(){Text(item.appName).fontSize(16).fontWeight(FontWeight.Medium).fontColor('#1E293B')Text(item.minutes+'分钟').fontSize(12).fontColor('#94A3B8')}.layoutWeight(1).alignItems(HorizontalAlign.Start)Text(item.minutes+'min').fontSize(14).fontWeight(FontWeight.Bold).fontColor(item.color)}.width('100%').margin({bottom:6})Row(){Row().width(this.getBarWidth(item.minutes/this.getMaxMinutes()*100)).height(6).backgroundColor(item.color).borderRadius(3)}.width('100%').backgroundColor('#F1F5F9').borderRadius(3)}.width('100%').padding({top:8,bottom:8})})}.width('92%').padding({left:16,right:16,top:8,bottom:8}).backgroundColor('#FFFFFF').borderRadius(16).shadow({radius:8,color:'#10000000',offsetY:2}).margin({bottom:16})// ====== 底部提示 ======Text('🔄 点击右上角刷新按钮可模拟数据变化').fontSize(12).fontColor('#CBD5E1').margin({bottom:20})}.width('100%').alignItems(HorizontalAlign.Center)}.scrollable(ScrollDirection.Vertical)}}四、核心代码详解
4.1 数据模型:AppUsageItem
typescript
interfaceAppUsageItem{appName:string;// 应用名称icon:string;// 图标 Emojiminutes:number;// 使用时长(分钟)color:string;// 进度条颜色(品牌色)}每个 App 对应一个 AppUsageItem 对象,通过 @State appList 管理列表数据。ArkTS 要求接口定义必须放在文件最顶部,否则会报 Cannot find name 错误。
4.2 状态管理:@State
typescript
@StatetotalMinutes:number=187;@StateunlockCount:number=46;@Statepickups:number=62;@StateappList:AppUsageItem[]=[...]@State装饰的变量变更时会自动触发UI重绘。注意:数组更新必须赋值新数组对象而非直接push(),否则UI不会刷新。4.3 滚动容器:Scroll
typescript
Scroll(){Column(){...}}.scrollable(ScrollDirection.Vertical)
这是 API20 中实现滚动的标准写法。❌ 错误写法是直接在 Column 上调用 .scrollable(),API20 不支持。
4.4 百分比进度条实现
typescript
Row(){Row()// 彩色部分.width('65%')// 动态百分比.height(6).backgroundColor(item.color).borderRadius(3)}.width('100%').backgroundColor('#F1F5F9')// 灰色背景.borderRadius(3)使用嵌套 Row 实现进度条:外层 Row 作为灰色背景轨道,内层 Row 作为彩色填充部分,宽度由 getBarWidth() 根据占比计算。
五、踩坑记录 & 避坑指南
❌ 坑1:.scrollable() 不存在
typescript
// ❌ 错误 — API20 不支持
Column() { … }.scrollable(ScrollDirection.Vertical)
// ✅ 正确 — 用 Scroll 包裹
Scroll() { Column() { … } }.scrollable(ScrollDirection.Vertical)
❌ 坑2:数组 push 后 UI 不刷新
typescript
// ❌ 错误 — 引用没变,UI 不会刷新
this.appList.push(newItem)
// ✅ 正确 — 创建新数组赋值给 @State
this.appList = this.appList.concat([newItem])
❌ 坑3:接口定义位置
typescript
// ✅ 必须放在文件最顶部,@Entry 之前
interface AppUsageItem { … }
@Entry @Component struct MyApp { … }
❌ 坑4:Canvas 回调(此项目未使用,但如果要用)
typescript
// ❌ 错误 — API20 不支持回调构造
Canvas((ctx) => { ctx.fillRect(…) })
// ✅ 正确 — 传 CanvasRenderingContext2D 对象
Canvas(this.canvasCtx)
六、运行效果预览
在 DevEco Studio 中运行项目后,你将看到:
启动画面:顶部标题栏显示"📱 屏幕使用时间",右侧有刷新按钮
统计卡片:大字体显示"3小时7分钟",下方有解锁次数和拿起次数
App 列表:微信、抖音等应用按时间排序,附图标和彩色进度条
点击刷新:所有数据随机变化,模拟不同天的使用情况
💡 小提示:你可以修改 totalMinutes 范围为 60 + Math.random() * 300 来模拟不同的使用强度,或者替换 App 数据源为用户手机上真实的 App 名称。
七、项目扩展思路
如果你想让这个 Demo 更加完善,可以尝试以下方向:
方向 实现思路
📈 周趋势图 用 Canvas 绘制折线图展示一周使用趋势
⏰ 自定义目标 增加每日限额设置功能,超时弹窗提醒
📂 真实数据 通过 HarmonyOS 系统 API 读取真实屏幕使用数据
🎨 主题切换 增加深色模式支持
八、总结
通过本文,我们实现了:
✅ 界面搭建:卡片式 UI + 进度条 + 列表渲染
✅ 状态管理:@State 驱动数据与 UI 同步
✅ 滚动容器:Scroll 组件的正确用法
✅ API20 兼容:不依赖新版本 API,避开常见的踩坑点
这个项目麻雀虽小五脏俱全,涵盖了 HarmonyOS NEXT 开发中的核心知识点。运行在真机或模拟器上效果都非常棒!
如果你在开发过程中遇到问题,欢迎在评论区留言交流 🚀
