一、前言你是否好奇自己每天在手机上花了多少时间哪些 App 占据了你最多的注意力今天我们就用 HarmonyOS NEXTAPI20 手把手实现一个「屏幕使用时间追踪器」。通过这个实战项目你将掌握✅ HarmonyOS NEXT 页面布局与组件使用✅ Scroll 滚动容器 ForEach 列表渲染✅ 状态管理 State 与数据驱动 UI 刷新✅ 综合 UI 设计卡片、进度条、图标✅ API20 完全兼容无弃用 API项目名称ScreenTimeTrackerSDK 版本HarmonyOS 4.x / API 20开发工具DevEco Studio项目模板Empty Ability二、项目效果展示2.1 界面总览应用分为三个核心区域区域 内容 顶部标题栏 应用名称 右侧刷新按钮 统计卡片 今日总使用时长大字体展示 解锁次数 拿起次数 App 明细列表 各应用使用时长 彩色进度条2.2 交互功能 点击刷新按钮随机生成新数据模拟不同场景 应用列表每个 App 显示图标 名称 时长 进度条 进度条颜色每个 App 使用品牌色视觉区分度高三、完整代码可直接运行将以下代码复制到 entry/src/main/ets/pages/Index.ets 中点击运行即可。typescriptinterfaceAppUsageItem{appName:string;icon:string;minutes:number;color:string;}EntryComponentstruct ScreenTimeTracker{StatetotalMinutes:number187;StateunlockCount:number46;Statepickups:number62;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{consthMath.floor(this.totalMinutes/60);constmthis.totalMinutes%60;returnh小时m分钟;}/** 获取最大分钟数用于进度条100%基准 */getMaxMinutes():number{letmax0;for(leti0;ithis.appList.length;i){if(this.appList[i].minutesmax){maxthis.appList[i].minutes;}}returnmax;}/** 计算进度条宽度百分比 */getBarWidth(percent:number):string{if(percent100){percent100;}returnpercent%;}/** 模拟刷新数据 */refreshData():void{this.totalMinutes120Math.floor(Math.random()*150);this.unlockCount20Math.floor(Math.random()*50);this.pickups30Math.floor(Math.random()*60);constnames:string[][微信,抖音,浏览器,小红书,哔哩哔哩,其他];consticons:string[][,,,,,];constcolors:string[][#07C160,#333333,#4285F4,#FF2442,#FB7299,#94A3B8];constnewList:AppUsageItem[][];letremaining:numberthis.totalMinutes;for(leti0;i5;i){constmMath.min(remaining-5,5Math.floor(Math.random()*(remaining-5)/3));newList.push({appName:names[i],icon:icons[i],minutes:m,color:colors[i]});remainingremaining-m;}newList.push({appName:names[5],icon:icons[5],minutes:remaining,color:colors[5]});this.appListnewList;}// 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.minutesmin).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 数据模型AppUsageItemtypescriptinterfaceAppUsageItem{appName:string;// 应用名称icon:string;// 图标 Emojiminutes:number;// 使用时长分钟color:string;// 进度条颜色品牌色}每个 App 对应一个 AppUsageItem 对象通过 State appList 管理列表数据。ArkTS 要求接口定义必须放在文件最顶部否则会报 Cannot find name 错误。4.2 状态管理StatetypescriptStatetotalMinutes:number187;StateunlockCount:number46;Statepickups:number62;StateappList:AppUsageItem[][...]State装饰的变量变更时会自动触发UI重绘。注意数组更新必须赋值新数组对象而非直接push()否则UI不会刷新。4.3 滚动容器ScrolltypescriptScroll(){Column(){...}}.scrollable(ScrollDirection.Vertical)这是 API20 中实现滚动的标准写法。❌ 错误写法是直接在 Column 上调用 .scrollable()API20 不支持。4.4 百分比进度条实现typescriptRow(){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)// ✅ 正确 — 创建新数组赋值给 Statethis.appList this.appList.concat([newItem])❌ 坑3接口定义位置typescript// ✅ 必须放在文件最顶部Entry 之前interface AppUsageItem { … }Entry Component struct MyApp { … }❌ 坑4Canvas 回调此项目未使用但如果要用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 开发中的核心知识点。运行在真机或模拟器上效果都非常棒如果你在开发过程中遇到问题欢迎在评论区留言交流
HarmonyOS NEXT 实战:零基础实现屏幕使用时间追踪器(ScreenTimeTracker)
发布时间:2026/6/16 3:30:02
一、前言你是否好奇自己每天在手机上花了多少时间哪些 App 占据了你最多的注意力今天我们就用 HarmonyOS NEXTAPI20 手把手实现一个「屏幕使用时间追踪器」。通过这个实战项目你将掌握✅ HarmonyOS NEXT 页面布局与组件使用✅ Scroll 滚动容器 ForEach 列表渲染✅ 状态管理 State 与数据驱动 UI 刷新✅ 综合 UI 设计卡片、进度条、图标✅ API20 完全兼容无弃用 API项目名称ScreenTimeTrackerSDK 版本HarmonyOS 4.x / API 20开发工具DevEco Studio项目模板Empty Ability二、项目效果展示2.1 界面总览应用分为三个核心区域区域 内容 顶部标题栏 应用名称 右侧刷新按钮 统计卡片 今日总使用时长大字体展示 解锁次数 拿起次数 App 明细列表 各应用使用时长 彩色进度条2.2 交互功能 点击刷新按钮随机生成新数据模拟不同场景 应用列表每个 App 显示图标 名称 时长 进度条 进度条颜色每个 App 使用品牌色视觉区分度高三、完整代码可直接运行将以下代码复制到 entry/src/main/ets/pages/Index.ets 中点击运行即可。typescriptinterfaceAppUsageItem{appName:string;icon:string;minutes:number;color:string;}EntryComponentstruct ScreenTimeTracker{StatetotalMinutes:number187;StateunlockCount:number46;Statepickups:number62;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{consthMath.floor(this.totalMinutes/60);constmthis.totalMinutes%60;returnh小时m分钟;}/** 获取最大分钟数用于进度条100%基准 */getMaxMinutes():number{letmax0;for(leti0;ithis.appList.length;i){if(this.appList[i].minutesmax){maxthis.appList[i].minutes;}}returnmax;}/** 计算进度条宽度百分比 */getBarWidth(percent:number):string{if(percent100){percent100;}returnpercent%;}/** 模拟刷新数据 */refreshData():void{this.totalMinutes120Math.floor(Math.random()*150);this.unlockCount20Math.floor(Math.random()*50);this.pickups30Math.floor(Math.random()*60);constnames:string[][微信,抖音,浏览器,小红书,哔哩哔哩,其他];consticons:string[][,,,,,];constcolors:string[][#07C160,#333333,#4285F4,#FF2442,#FB7299,#94A3B8];constnewList:AppUsageItem[][];letremaining:numberthis.totalMinutes;for(leti0;i5;i){constmMath.min(remaining-5,5Math.floor(Math.random()*(remaining-5)/3));newList.push({appName:names[i],icon:icons[i],minutes:m,color:colors[i]});remainingremaining-m;}newList.push({appName:names[5],icon:icons[5],minutes:remaining,color:colors[5]});this.appListnewList;}// 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.minutesmin).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 数据模型AppUsageItemtypescriptinterfaceAppUsageItem{appName:string;// 应用名称icon:string;// 图标 Emojiminutes:number;// 使用时长分钟color:string;// 进度条颜色品牌色}每个 App 对应一个 AppUsageItem 对象通过 State appList 管理列表数据。ArkTS 要求接口定义必须放在文件最顶部否则会报 Cannot find name 错误。4.2 状态管理StatetypescriptStatetotalMinutes:number187;StateunlockCount:number46;Statepickups:number62;StateappList:AppUsageItem[][...]State装饰的变量变更时会自动触发UI重绘。注意数组更新必须赋值新数组对象而非直接push()否则UI不会刷新。4.3 滚动容器ScrolltypescriptScroll(){Column(){...}}.scrollable(ScrollDirection.Vertical)这是 API20 中实现滚动的标准写法。❌ 错误写法是直接在 Column 上调用 .scrollable()API20 不支持。4.4 百分比进度条实现typescriptRow(){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)// ✅ 正确 — 创建新数组赋值给 Statethis.appList this.appList.concat([newItem])❌ 坑3接口定义位置typescript// ✅ 必须放在文件最顶部Entry 之前interface AppUsageItem { … }Entry Component struct MyApp { … }❌ 坑4Canvas 回调此项目未使用但如果要用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 开发中的核心知识点。运行在真机或模拟器上效果都非常棒如果你在开发过程中遇到问题欢迎在评论区留言交流