文章目录
- 前言
- 一、知识体系回顾
- 1.1 语言与基础(01-04)
- 1.2 Ability 与系统(05-09)
- 1.3 数据与工具(10-14)
- 1.4 地图与交互(15-20)
- 1.5 列表与组件(21-25)
- 二、项目设计的亮点
- 2.1 关注点分离做得好
- 2.2 常量管理规范
- 2.3 全局状态管理思路正确
- 三、项目可以改进的地方
- 3.1 定位调用次数优化
- 3.2 Marker 动画的 from 值
- 3.3 权限被拒绝后引导用户
- 3.4 加油站数据从网络获取
- 3.5 距离排序
- 3.6 完整支持暗色模式
- 四、可以继续添加的功能
- 五、HarmonyOS 开发的真实体验
- 六、学习路线建议
- 总结
前言
这是系列文章的最后一篇。
25 篇文章,我们从认识项目结构开始,一路讲到 ArkTS 语言基础、ArkUI 布局、Ability 生命周期、状态管理、Navigation 路由、权限申请、数据模型、工具类封装、MapKit 地图、定位服务、暗色模式与国际化……
这篇文章做两件事:回顾整个系列的知识体系,以及给出项目可以继续改进的方向,让这个教程不只是读完就结束,而是成为你继续学习的起点。
项目预览
一、知识体系回顾
1.1 语言与基础(01-04)
| 知识点 | 关键概念 |
|---|---|
| ArkTS 语言 | interface、class、async/await、装饰器 |
| ArkUI 声明式 UI | Column、Row、Stack、Text、Image |
| 常量管理 | static readonly、SCREAMING_SNAKE_CASE |
| 资源文件 | $r()、color.json、string.json、media |
核心记忆点:ArkTS = TypeScript + 装饰器扩展;ArkUI = 声明式,不是命令式。
1.2 Ability 与系统(05-09)
| 知识点 | 关键概念 |
|---|---|
| UIAbility 生命周期 | onCreate → onWindowStageCreate → onForeground |
| 沉浸式全屏 | setWindowLayoutFullScreen、getWindowAvoidArea |
| AppStorage | setOrCreate、@StorageProp、@StorageLink |
| Navigation 路由 | NavPathStack、pushPathByName、pop、NavDestination |
| 权限申请 | abilityAccessCtrl、checkAccessToken、requestPermissionsFromUser |
核心记忆点:安全区域高度存 AppStorage → @StorageProp 全局响应式;Navigation = 现代路由,不用 Router。
1.3 数据与工具(10-14)
| 知识点 | 关键概念 |
|---|---|
| 数据模型 | interface StationData、ResourceStr、export const |
| Logger | hilog、domain/tag/format、日志分级 |
| 距离计算 | map.calculateDistance、toFixed(1)、m → km |
| MapKit 接入 | MapOptions、MapComponent、MapComponentController |
| 坐标系转换 | WGS84(GPS)→ GCJ02(中国地图)、map.convertCoordinate |
核心记忆点:interface 定义数据模型,class 封装行为;GPS = WGS84,中国地图 = GCJ02,必须转换。
1.4 地图与交互(15-20)
| 知识点 | 关键概念 |
|---|---|
| Marker 标记 | MarkerOptions、addMarker、anchorU/anchorV、SVG 图标 |
| 相机动画 | CameraPosition、CameraUpdate、animateCamera |
| ScaleAnimation | fromX/toX/fromY/toY、setDuration、FillMode.FORWARDS |
| MapUtil 封装 | @Observed、单例export const mapUtil |
| bindSheet | detents、dragBar、blurStyle、onWillDismiss |
核心记忆点:Marker 底部对准坐标点(anchorV=1);所有地图操作在 callback 里;bindSheet 必须调用 dismiss()。
1.5 列表与组件(21-25)
| 知识点 | 关键概念 |
|---|---|
| List + ForEach | key 生成函数、LazyForEach(大数据量) |
| @Builder | 局部/全局、共享父组件状态、vs @Component |
| LocationKit | getCurrentLocation、WGS84、错误处理 |
| 暗色模式 | dark/element/color.json、setColorMode |
| 多语言 | zh_CN/element/string.json、资源优先级 |
核心记忆点:@Builder 共享状态适合页面内拆分,@Component 独立适合跨页面复用;资源文件自动按设备语言/主题选择,代码无感知。
二、项目设计的亮点
这个项目虽然功能简单,但设计上有几个值得学习的亮点:
2.1 关注点分离做得好
页面(GasStationPage) ← 只关心业务逻辑 ↓ 调用 工具类(MapUtil) ← 只关心地图操作技术细节 ↓ 调用 Kit(MapKit、LocationKit)← 系统能力页面不知道地图坐标如何转换,工具类不知道业务逻辑。层次清晰。
2.2 常量管理规范
所有魔法数字集中在Constants.ets,static readonly+ 全大写命名,改动一处全局生效。这个习惯值得在所有项目里坚持。
2.3 全局状态管理思路正确
安全区域高度用AppStorage存储,页面用@StorageProp读取,动态监听变化。这种模式可以推广到所有需要跨组件/跨页面共享的数据。
三、项目可以改进的地方
3.1 定位调用次数优化
// 现在:定位了 3 次(moveToMyLocation 1次 + getMyLocation 2次)mapUtil.moveToMyLocation(mapController);this.currentLatitude=(awaitmapUtil.getMyLocation()).latitude;this.currentLongitude=(awaitmapUtil.getMyLocation()).longitude;// 优化:只定位 1 次letlocation=awaitmapUtil.getMyLocation();this.currentLatitude=location.latitude;this.currentLongitude=location.longitude;awaitmapUtil.moveToMyLocationByCoord(location,mapController);// 传入已有的位置3.2 Marker 动画的 from 值
// 现在:放大时 from=1,缩小时也是 from=1(不连续)letanimation=newmap.ScaleAnimation(Constants.ONE,imageScale,// 永远从 1 开始Constants.ONE,imageScale);// 优化:记录当前比例作为起始值letanimation=newmap.ScaleAnimation(this.currentScale,imageScale,// 从当前比例开始this.currentScale,imageScale);this.currentScale=imageScale;3.3 权限被拒绝后引导用户
// 现在:权限被拒绝只记日志.catch((err:BusinessError)=>{Logger.error(`Failed to request permissions from user...`);});// 优化:弹窗引导用户去设置页.catch(()=>{AlertDialog.show({title:'需要定位权限',message:'请在设置中开启位置权限,否则无法查看附近加油站',confirm:{value:'去设置',action:()=>{openAppSettings(context);}}});});3.4 加油站数据从网络获取
// 现在:静态数据this.stationInfoList=STATION_LIST;// 优化:从后端 API 获取asyncloadStations(){this.isLoading=true;try{letresponse=awaithttp.request('https://api.example.com/nearby-stations');this.stationInfoList=JSON.parse(response.resultasstring);}catch(err){Logger.error('加载数据失败,使用本地数据');this.stationInfoList=STATION_LIST;// 降级使用静态数据}finally{this.isLoading=false;}}3.5 距离排序
// 优化:加油站列表按距离由近到远排序this.stationInfoList=[...STATION_LIST].sort((a,b)=>{letdistA=parseFloat(CalculateUtil.getDistance(this.currentLatitude,this.currentLongitude,a.latitude,a.longitude));letdistB=parseFloat(CalculateUtil.getDistance(this.currentLatitude,this.currentLongitude,b.latitude,b.longitude));returndistA-distB;});3.6 完整支持暗色模式
// 删除这行强制亮色代码// this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);// 然后在 dark/element/color.json 里补全所有颜色的暗色版本四、可以继续添加的功能
如果你想基于这个项目继续练习,以下是一些功能扩展方向:
| 功能 | 涉及技术 |
|---|---|
| 加油站详情页 | Navigation 传参、新页面开发 |
| 导航功能 | 调用系统导航 App(startAbility + Want) |
| 收藏加油站 | PersistentStorage 持久化 |
| 搜索功能 | TextInput 组件、数据过滤 |
| 加载状态 | 骨架屏、LoadingProgress 组件 |
| 下拉刷新 | Refresh 组件 |
| 评分显示 | 自定义 Rating 组件 |
| 燃油价格 | 网络请求、数据展示 |
| 路线规划 | MapKit 路线规划 API |
五、HarmonyOS 开发的真实体验
学完这个系列,说说我对 HarmonyOS 开发的整体感受:
优点:
- ArkUI 声明式 UI 写起来很流畅,和 SwiftUI/Compose 体验类似
- Kit 体系封装了大量系统能力,地图、定位这些调用门槛低
- TypeScript 基础的 ArkTS 入门曲线平滑,有前端经验很容易上手
- 状态管理体系(@State/@StorageProp/AppStorage)设计合理,响应式明确
挑战:
- 文档质量参差不齐,有些 API 的参数说明不够详细
- 部分功能只能在真机上测试(地图、定位在模拟器上有限制)
- 生态相比 Android/iOS 还在发展中,第三方库较少
- 折叠屏、多窗口等适配需要额外工作
六、学习路线建议
如果你想继续深入 HarmonyOS 开发,推荐路线:
基础(本系列) └─> 进阶功能 ├─> 网络请求(@ohos/axios / http模块) ├─> 本地数据库(关系型数据库、首选项) ├─> 多 Ability 应用 └─> 元服务(HarmonyOS的小程序) └─> UI 深入 ├─> 自定义动画(animateTo、transition) ├─> 自定义绘制(Canvas) └─> 折叠屏适配 └─> 架构进阶 ├─> MVVM 架构模式 ├─> 模块化设计(HAR/HSP) └─> 性能优化(启动速度、渲染性能)总结
25 篇文章,我们一起解析了"附近加油站"这个项目的每一行代码。
从一个陌生的.ets文件,到能说清楚每个装饰器的作用、每个 API 的调用方式、每个设计决策背后的原因——这就是这个系列的目标。
希望这个系列对你有帮助。HarmonyOS 还很年轻,它的生态还在快速发展。现在入坑,有机会参与到一个新平台的成长过程中——这是一件挺值得期待的事。
代码要写,也要想清楚为什么这样写。加油!