随手拍,实时生成专属票根
拍摄页把取景、变焦、拍照和票根预览放在同一工作台中。底部保留图库与镜头切换入口,用户可以先确定构图,再决定是否套用票根。


Keepsake 的中文名是“拾刻相机”。它是一款面向 HarmonyOS 的原生影像应用,目标不是做一个只负责按下快门的相机,而是把拍摄、色彩、票根排版、动态照片和本地整理放进同一条创作链路。
这类应用的难点不只在 UI。相机预览需要和设备能力、屏幕方向、镜头切换保持一致;拍摄结果需要在应用自己的图库中稳定保存;票根和色卡要同时作用于静态图片和动态照片;用户还希望在导出前继续编辑,而不是每次都重新拍摄。Keepsake 的工程结构正是围绕这些边界拆开的。
| 方向 | 项目实现 |
|---|---|
| 运行平台 | HarmonyOS,兼容 SDK 6.1.0(23),目标 SDK 6.1.1(24) |
| 开发语言 | ArkTS / ETS |
| 应用模型 | Stage 模型,入口为 EntryAbility |
| UI 框架 | ArkUI 声明式 UI、HdsNavigation、系统 Symbol、半模态面板 |
| 相机能力 | @kit.CameraKit,预览流、拍照、镜头位置、变焦和相机状态管理 |
| 影像能力 | @kit.ImageKit、@kit.MediaLibraryKit、PixelMap 与图像资源读写 |
| 本地数据 | @kit.ArkData preferences,保存设置、色卡和图库轻量元数据 |
| 文件管理 | 应用沙箱 filesDir/gallery,照片本体与元数据分开处理 |
| 动态照片 | Moving Photo 导出与 native transcoding bridge |
项目入口模块是 entry。源码主要分成页面、模型、组件和数据存储四层:页面负责交互,模型负责相机和影像流程,组件负责票根与预览的组合渲染,Store 类负责把可恢复的数据写入本地。
拾刻相机的核心链路可以概括为:
1相机预览2 ↓3拍摄静态照片或动态照片4 ↓5实时色卡与影像参数6 ↓7票根模板、地点、时间和设备信息8 ↓9应用内图库10 ↓11编辑、调色、保存或导出这个流程把“拍摄”和“整理”连接起来。拍完的照片先进入应用自己的图库,用户可以稍后重新选择票根、修改调色、调整分享背景,再决定是否写入系统相册。
CameraPage.ets 是应用最复杂的页面,它同时管理取景、拍摄、底部控制栏、票根预览、色卡面板和图库入口。页面没有把每个功能拆成互不相关的页面,而是用面板模式区分 CONTROLS、COLOR_PRESETS、COLOR_EDITOR 和 TEMPLATES,让用户在同一取景上下文中完成调整。
CameraModel.ets 则把相机生命周期从页面中抽出来,负责创建相机输入、预览输出和拍照输出,并向页面提供当前镜头、变焦范围和运行状态。这样做的好处是:页面只关心“当前能不能拍”和“用户选择了什么”,相机资源的创建、释放和异常处理集中在模型层。
项目在模块配置中声明了相机、麦克风、定位和网络权限。定位能力用于补充票根中的地点信息,麦克风权限用于动态照片相关的声音场景;具体权限仍然由系统在运行时授予,应用不能绕过用户授权。
色卡不是几张静态滤镜图片。ColorCardProfile 中保存了色温、色调、饱和度、强度、对比度、褪色、颗粒和暗角等参数,同时还包含色卡分类、配方编号和预览资源。
ColorCardStore 负责内置色卡和用户自定义色卡的读取、校验、归一化与持久化。应用启动时会过滤已经退役的旧预设,避免历史 preferences 中的旧数据被误认为是用户自定义内容重新出现。这是一个很小但很重要的数据迁移细节:资源版本变化时,持久化数据也必须跟着升级。
取景阶段使用 ColorTextureOverlay 等组件呈现实时效果,导出阶段则把相同的调色参数传给图片或动态视频处理流程。预览参数与最终结果共用一份配置,能够减少“预览看起来是一个效果,保存后变成另一个效果”的偏差。
TicketTemplate.ets 用数据结构描述票根:图片区域、画布比例、标题、页脚、条码、打孔线、颜色和圆角都由模板字段定义。模板包括横版票根、竖版旅行邮票、拍立得、地图和液态玻璃等风格。
模板渲染时,应用会把城市、日期、时间、序列号、设备型号和自定义文字组装成 TicketVariables,再交给 TicketOverlay 或 PhotoPrintOverlay 绘制。这样一来,模板本身是可复用的,动态信息也不会被写死在图片资源里。
票根编辑页和取景页共用模板注册表,因此用户可以在设置页指定默认票根,也可以在拍摄现场临时切换。对于不同画幅,项目还维护了照片槽的裁切比例和输出比例,避免照片被简单拉伸。
动态照片是 Keepsake 中最有工程挑战的一部分。静态封面可以直接生成票根图层,但视频部分需要逐帧叠加相同的图片槽、色彩参数和透明票根层。
MovingPhotoVideoComposer.ets 的处理流程大致如下:
ImageSource 和 PixelMap,避免长时间使用相机后出现资源泄漏。代码中还对动态照片的图片路径、视频路径和 URI 做了区分。应用沙箱文件路径用于写入内容,fileUri 用于资源引用,二者不能混用。这个边界处理直接影响动态照片能否被正确导出。
GalleryStore.ets 的设计是“轻量元数据 + 应用沙箱文件”。图库记录会保存照片路径、是否为动态照片、票根模板、地点、时间、设备型号、调色参数、分享背景和导出状态;照片本体则复制到 filesDir/gallery,避免把重要文件放在可能被系统清理的 cache 目录里。
图库页面支持预览、编辑、重新调色、删除和保存到系统相册。对于动态照片,页面需要同时管理静态封面和视频资源,播放时再根据记录中的参数还原对应的预览效果。
1CameraPage2 ├─ CameraModel 相机输入、预览输出、拍照输出3 ├─ ColorCardStore 内置色卡与用户色卡4 ├─ TicketTemplate 票根模板与动态变量5 ├─ TicketOverlay ArkUI 票根图层6 └─ GalleryStore 文件路径、元数据、导出状态7 ├─ Preferences 设置与色卡数据8 └─ filesDir/gallery 照片与动态照片文件这个边界让影像链路更容易维护:相机模型不需要知道图库列表如何显示,图库也不需要重新创建相机会话;色卡只负责描述参数,模板只负责描述版式,最终由页面或导出器把它们组合起来。
下面的链接对应项目实际使用到的 HarmonyOS 能力。文档链接优先指向华为开发者官网,API 版本和设备支持范围应以打开页面时的最新说明为准。
CameraPage.etsCameraModel.ets
photoAccessHelperMovingPhoto
HdsNavigationHdsTabs
ColorCardStoreGalleryStore
下面的图片来自拾刻相机的项目素材。它们按功能拆成横向卡片,桌面端可以横向滚动,移动端可以左右滑动查看。
拍摄页把取景、变焦、拍照和票根预览放在同一工作台中。底部保留图库与镜头切换入口,用户可以先确定构图,再决定是否套用票根。
色卡被设计成可直接点选的成像配方,而不是单纯的滤镜名称。原色、自然、明艳、明快等预设可以在取景阶段即时切换。
票根模板通过统一数据结构描述照片槽、比例、文字、条码和装饰。这样既可以保留统一的编辑体验,也能持续增加新的旅行票、拍立得和胶片风格。
应用图库承接拍摄后的照片和动态照片,提供编辑、调色、删除和保存入口。作品不会因为还没有导出到系统相册就失去后续编辑能力。
相机设置页可以指定默认票根、默认焦段、色卡入口和自拍镜像行为。设置被持久化后,下一次打开相机即可恢复常用工作方式。
用户可以在预览图上观察色温、饱和度等参数的变化,并把一套调整结果保存为个人色卡。预设和自定义配置共用同一套数据模型。
拾刻相机的技术特点,不是把多个系统能力简单堆在一个页面里,而是给每种能力划定了清晰的边界:Camera Kit 负责采集,色卡负责参数,票根模板负责版式,图库负责生命周期,导出器负责把它们合成为最终作品。
项目还体现了鸿蒙原生开发的几个典型问题:权限需要在合适的时机申请;设备能力不能假设所有机型都相同;预览、拍照和导出要考虑方向与画幅;文件路径和 URI 需要区分;长时间使用相机和图片资源时必须主动释放对象。
对于个人影像应用来说,本地优先的设计也很合适。照片和动态照片保存在应用沙箱,设置和色卡保存为本地 preferences,用户不需要注册服务器账号就能完成完整的拍摄和整理流程。
拾刻相机把拍摄、调色和记录生活的过程,收进了一个可以继续生长的原生应用里。
一张照片按下快门的时间很短,真正留下来的,却是那一刻的光线、地点和心情。愿每次打开图库时,旧日的风也还能从画面里吹出来。
如果这篇文章对你有帮助,欢迎分享给更多人!
部分内容可能已过时