鸿蒙原生应用从0到1:项目搭建与首页开发实战 鸿蒙原生应用从0到1项目搭建与首页开发实战本系列文章将带大家从零开发一个完整的鸿蒙原生应用——「生活助手」涵盖项目搭建、页面开发、状态管理、数据交互等核心实战内容。一、写在前面鸿蒙生态发展至今原生应用开发已经不再是要不要学的问题而是什么时候开始学的问题。作为一名移动端开发者我决定用 ArkTS Stage 模型开发一个完整的「生活助手」App把我的学习和实战过程记录下来希望对同样在学习的你有所帮助。本文是系列第一篇重点讲解项目搭建、工程结构设计、以及首页的开发实现。二、项目概况「生活助手」是一个集待办管理、记账理财、备忘录、心情日记、个人中心于一体的日常管理工具涵盖了一个完整 App 开发中的核心场景功能模块核心能力技术亮点首页仪表盘每日一言、统计概览、快捷入口、动态流Scroll 组合 Builder待办管理分类筛选、优先级标记、添加/删除/切换完成状态管理 过滤逻辑记账本收支汇总、分类统计、记录管理数据聚合计算备忘录分类筛选、搜索、详情查看、编辑搜索逻辑 多视图切换心情日记日历视图、情绪记录、月度统计日历算法 数据可视化三、项目搭建 —— 从 DevEco Studio 开始3.1 创建项目打开 DevEco Studio选择File → New → Create Project模板选择Empty AbilityStage 模型兼容版本API 9我选择 API 23对应新版 SDK包名com.example.myapplication项目位置自行选择创建完成后你会看到如下工程结构MyApplication/ ├── AppScope/ # 全局应用配置 │ ├── app.json5 # 应用级配置 │ └── resources/ # 全局资源 ├── entry/ # 应用主模块 │ ├── src/ │ │ ├── main/ │ │ │ ├── ets/ # 代码目录 │ │ │ │ ├── entryability/ # Ability 生命周期 │ │ │ │ └── pages/ # 页面文件 │ │ │ ├── resources/ # 资源文件 │ │ │ └── module.json5 # 模块配置 │ ├── build-profile.json5 # 构建配置 │ └── oh-package.json5 # 包依赖 ├── hvigor/ # 构建工具配置 ├── build-profile.json5 # 全局构建配置 └── oh-package.json5 # 全局包依赖3.2 重要配置文件解读AppScope/app.json5 —— 应用级配置{app:{bundleName:com.example.myapplication,vendor:example,versionCode:1000000,versionName:1.0.0,icon:$media:layered_image,label:$string:app_name}}这里配置了应用的包名、版本号、图标和显示名称。bundleName是应用的唯一标识一旦发布不可修改。entry/src/main/module.json5 —— 模块配置{module:{name:entry,type:entry,deviceTypes:[phone],pages:$profile:main_pages,abilities:[{name:EntryAbility,srcEntry:./ets/entryability/EntryAbility.ets,exported:true,skills:[{entities:[entity.system.home],actions:[ohos.want.action.home]}]}]}}关键点deviceTypes声明支持的设备类型这里仅支持 phonepages引用$profile:main_pages对应resources/base/profile/main_pages.jsonskills声明该 Ability 可以响应桌面启动意图main_pages.json —— 页面路由注册{src:[pages/Index,pages/TodoPage,pages/FinancePage,pages/NotePage,pages/MoodPage,pages/ProfilePage]}所有页面都必须在这里注册否则无法通过router.pushUrl()跳转。四、资源体系 —— 颜色、字号、字符串鸿蒙应用推荐使用资源引用$r的方式来管理 UI 属性而不是写死常量值。这样做的好处是支持深色模式自动切换同一资源名不同值支持多语言适配修改一处全局生效4.1 颜色资源// resources/base/element/color.json{color:[{name:primary,value:#5B7FFF},{name:primary_light,value:#E8ECFF},{name:text_primary,value:#1A1A2E},{name:text_secondary,value:#6B7280},{name:bg_primary,value:#F5F7FA},{name:bg_card,value:#FFFFFF},{name:divider,value:#E5E7EB},{name:shadow,value:#1A000000}]}我还为深色模式单独配置了dark/element/color.json应用会自动根据系统主题切换。4.2 字号资源// resources/base/element/float.json{float:[{name:title_font_size,value:24fp},{name:subtitle_font_size,value:18fp},{name:body_font_size,value:16fp},{name:small_font_size,value:14fp},{name:tiny_font_size,value:12fp},{name:card_radius,value:16vp},{name:button_radius,value:12vp}]}注意fp是鸿蒙特有的字体单位类似 Android 的 sp会跟随系统字体缩放vp是虚拟像素单位类似 dp。在代码中这样引用Text(标题).fontSize($r(app.float.title_font_size)).fontColor($r(app.color.text_primary))五、EntryAbility —— 应用入口分析// entryability/EntryAbility.etsexportdefaultclassEntryAbilityextendsUIAbility{onCreate(want:Want,launchParam:AbilityConstant.LaunchParam):void{this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);}onWindowStageCreate(windowStage:window.WindowStage):void{windowStage.loadContent(pages/Index,(err){if(err.code){hilog.error(DOMAIN,testTag,Failed to load content: %{public}s,JSON.stringify(err));}});}}核心逻辑onCreate设置颜色模式为跟随系统NOT_SETonWindowStageCreate加载首页pages/Index使用hilog日志系统记录关键生命周期事件六、首页开发 —— 生活助手仪表盘首页是整个 App 的门面我设计了一个信息型仪表盘包含以下区域┌─────────────────────────────┐ │ 生活助手 2025年1月15日 │ ← 顶部标题栏 │ │ ← 点击跳转个人中心 ├─────────────────────────────┤ │ 每日一言 [换一句]│ ← 可切换的名人名言 │ 生活不止眼前的苟且... │ │ —— 高晓松 │ ├─────────────────────────────┤ │ 今日待办 │ 本月支出 │ ← 2×2 统计卡片 │ 5项 │ 3,280元 │ │ 备忘录 │ 连续打卡 │ │ 12篇 │ 7天 │ ├─────────────────────────────┤ │ ➕新增待办 记一笔 │ ← 快捷操作栏 │ ✏️写笔记 记心情 │ ├─────────────────────────────┤ │ 最近动态 │ ← 活动流 │ 完成项目方案 10:30 │ │ 午餐支出 12:00 │ │ 读书笔记 14:30 │ └─────────────────────────────┘6.1 数据模型定义ArkTS 中定义接口统一使用interfaceinterfaceDailyQuote{text:string;author:string;}interfaceStatItem{title:string;value:string;unit:string;color:string;icon:string;}interfaceQuickAction{name:string;icon:string;page:string;color:string;}interfaceActivityItem{title:string;desc:string;time:string;type:string;// todo | finance | note | mood}6.2 状态与数据初始化Componentstruct Index{StatecurrentQuoteIndex:number0;StatecurrentDate:string;StateuserName:string用户;privatequotes:DailyQuote[][{text:生活不止眼前的苟且还有诗和远方,author:高晓松},{text:千里之行始于足下,author:老子},// ...];aboutToAppear():void{this.updateDate();}updateDate():void{constnownewDate();constweekDays:string[][日,一,二,三,四,五,六];this.currentDate${now.getFullYear()}年${now.getMonth()1}月${now.getDate()}日 星期${weekDays[now.getDay()]};}}这里有一个细节updateDate()在aboutToAppear()中调用确保进入页面时日期是最新的。aboutToAppear是 ArkTS 的生命周期钩子类似 Vue 的onMounted。6.3 每日一言 —— 交互卡片Column(){Row(){Text( 每日一言)Blank()Text(换一句).fontColor($r(app.color.primary)).onClick((){this.currentQuoteIndex(this.currentQuoteIndex1)%this.quotes.length;})}Text(this.quotes[this.currentQuoteIndex].text).lineHeight(24)Text(——${this.quotes[this.currentQuoteIndex].author})}通过currentQuoteIndex状态 取模运算实现循环切换。点击换一句会触发 UI 重新渲染因为State装饰器标记了该变量。6.4 统计卡片 —— Builder 复用为了避免重复写相同的卡片布局我使用了Builder装饰器BuilderstatCard(item:StatItem):void{Column(){Row(){Text(item.icon)Text(item.value).fontColor(item.color)}Row(){Text(item.title)Text(item.unit)}}}在 build 方法中直接调用Row(){ForEach(this.stats.slice(0,2),(item:StatItem){this.statCard(item)},(item:StatItem)item.title)}ForEach的第三个参数是键生成函数用于优化列表渲染性能类似 React 的key。6.5 路由跳转// 导航到个人中心.onClick((){router.pushUrl({url:pages/ProfilePage});})// 快捷操作.onClick((){router.pushUrl({url:action.page});})注意路由的url不需要.ets后缀且路径相对于pages目录。需要在main_pages.json中注册。6.6 活动流 —— 动态类型映射getActivityIcon(type:string):string{consticons:Recordstring,string{todo:,finance:,note:,mood:};returnicons[type]||;}getActivityColor(type:string):ResourceColor{constcolors:Recordstring,string{todo:#E8ECFF,finance:#FFF3E0,note:#E8F5E9,mood:#F3E5F5};returncolors[type]||#F5F5F5;}这里使用了Recordstring, string类型来定义映射表避免使用switch-case长篇代码非常简洁。七、技术总结本篇核心知识点知识点说明Stage 模型鸿蒙推荐的 Ability 架构模块化清晰main_pages.json 路由注册所有页面必须在此注册$r 资源引用颜色/字号/字符串统一管理Component State声明式 UI 与状态驱动BuilderUI 组件复用ForEach列表渲染与 key 优化router.pushUrl跨页面导航避坑指南路由路径不加.ets后缀router.pushUrl({ url: pages/TodoPage })而非TodoPage.ets对象字面量必须显式声明类型ArkTS 严格模式下不允许推断对象类型所有数组元素必须标注接口State只监听一层深度的变化数组内元素的属性变化不会被追踪需要用this.todos [...this.todos]触发刷新资源引用用$r(app.xxx.xxx)不要硬编码颜色/字号值八、下篇预告下一篇中我们将深入开发**「待办事项」页面**涵盖分类标签筛选的实现待办 CRUD增删改查完整流程状态管理的最佳实践弹窗对话框的设计与交互欢迎持续关注一起掌握鸿蒙原生应用开发