第42篇|拍摄预览浮层:让用户确认刚拍的成果

第42篇|拍摄预览浮层:让用户确认刚拍的成果

相机体验里,"拍完之后发生了什么"非常关键。只把照片默默保存进相册,用户会不确定是否成功;直接跳转相册,又会打断连续拍摄。双镜记忆相机采用拍摄预览浮层,在当前相机页轻量展示刚拍结果。

这一篇看预览浮层如何从 GalleryMoment 读取图片 Uri,并在页面上同时处理单拍和双拍。它不是一张静态提示图,而是连接拍摄回调、相册入库和后续用户动作的过渡层。

这一篇继续围绕 21 天「智能相机开发实战」训练营展开。内容只使用当前项目里的 ArkTS、服务层代码和真实页面截图来讲,不把封面图放进正文。阅读时可以先看截图理解用户侧效果,再顺着函数名回到工程定位实现。

本篇目标

  • 理解拍摄成功后为什么需要轻量预览。
  • 掌握 showCameraCapturePreview 如何从记录填充浮层状态。
  • 读懂浮层 UI 如何处理双镜小窗和操作按钮。
  • 把 appendGalleryRecord、预览浮层和相册选择状态串起来。

对应源码位置

  • entry/src/main/ets/pages/Index.ets

一、预览浮层解决的是"成功感"和"不中断"

用户按下快门后,需要立刻看到成果,确认没有黑图、错位或只拍到一边。但智能相机又要支持连续拍摄,如果每拍一张都跳到相册,会让拍摄节奏断掉。

浮层是两者之间的平衡:它覆盖在相机页上,展示刚拍图片和少量动作;用户可以关闭继续拍,也可以进入相册做更完整整理。

图1 拍摄预览浮层在相机页中的展示位置和状态流向

二、showCameraCapturePreview:从记录填充预览状态

入库记录里已经有 backUrifrontUricreatedLabel。预览浮层不重新计算文件路径,而是直接消费 GalleryMoment。这样它展示的内容和相册即将展示的内容完全一致。

双拍时 frontUribackUri 不同,小窗才会出现;单拍时两者可能相同或前摄为空,浮层自然退化为主图预览。

图2 showCameraCapturePreview 从 GalleryMoment 填充预览浮层状态

ts 复制代码
  private showCameraCapturePreview(record: GalleryMoment): void {
    if (this.activeTab !== 'camera') {
      return;
    }
    if (this.capturePreviewHideTimer > 0) {
      clearTimeout(this.capturePreviewHideTimer);
      this.capturePreviewHideTimer = 0;
    }
    this.cameraCapturePreviewBackUri = record.backUri;
    this.cameraCapturePreviewFrontUri = record.frontUri;
    this.cameraCapturePreviewTitle = record.createdLabel;
    this.cameraCapturePreviewActionsVisible = false;
    this.cameraCapturePreviewVisible = true;
  }

  private hideCameraCapturePreview(): void {
    if (this.capturePreviewHideTimer > 0) {
      clearTimeout(this.capturePreviewHideTimer);
      this.capturePreviewHideTimer = 0;
    }
    this.cameraCapturePreviewVisible = false;
    this.cameraCapturePreviewActionsVisible = false;
  }

这段代码体现了模型驱动 UI 的好处:浮层不关心照片来自单拍、双拍还是导入,只要记录字段完整,就能正常展示。

三、浮层 UI 同时承接主图、小窗和动作

buildCameraCapturePreviewOverlay 根据 cameraCapturePreviewVisible 和图片 Uri 决定是否渲染。主图占主要区域,前摄图在双镜场景下以小窗叠加;操作按钮只有在动作区可见时出现。

这类浮层要特别注意尺寸控制。文章正文里的截图也统一限制宽度,避免图片把内容撑满;应用内则通过固定高度和条件渲染避免遮挡拍摄按钮。

图3 buildCameraCapturePreviewOverlay 负责主图、小窗和操作按钮

ts 复制代码
  private buildCameraCapturePreviewOverlay() {
    if (this.cameraCapturePreviewVisible && this.cameraCapturePreviewBackUri.length > 0) {
      Stack({ alignContent: Alignment.BottomStart }) {
        Stack({ alignContent: Alignment.BottomEnd }) {
          Image(this.cameraCapturePreviewBackUri)
            .width('100%')
            .height('100%')
            .objectFit(ImageFit.Cover)
            .backgroundColor('#000000')

          if (this.cameraCapturePreviewFrontUri.length > 0 &&
            this.cameraCapturePreviewFrontUri !== this.cameraCapturePreviewBackUri) {
            Image(this.cameraCapturePreviewFrontUri)
              .width(46)
              .height(60)
              .borderRadius(10)
              .border({
                width: 3,
                color: '#FFF7E6'
              })
              .objectFit(ImageFit.Contain)
              .backgroundColor('#050809')
              .margin({
                right: 8,
                bottom: 8
              })
          }

        }
        .width('100%')
        .height('100%')
        .borderRadius(30)
        .clip(true)
        .onClick(() => {
          this.hideCameraCapturePreview();
          this.openLatestCaptureInGallery();
        })

        if (this.cameraCapturePreviewActionsVisible) {
          Row({ space: 10 }) {
            Column({ space: 4 }) {
              Text('已保存')
                .fontSize(15)
                .fontWeight(FontWeight.Medium)
                .fontColor('#FFF7E6')

              Text(this.cameraCapturePreviewTitle)

双镜小窗不是单独的数据源,它只是 frontUri 的另一种呈现方式。这样可以保证详情页、相册卡片和预览浮层都从同一条记录取图。

四、入库后刷新选择项,再展示预览

appendGalleryRecord 把新记录插到列表最前面,更新选中项、分组 key、备注草稿和提示文本,然后调用持久化。它还会调用 showCameraCapturePreview,让用户马上看到新作品。

这里顺序很重要。先整理记录和选中状态,再展示浮层,可以保证用户点进相册时落到刚拍照片,而不是进入旧的选中项。

图4 appendGalleryRecord 入库后同步刷新选择项并显示预览

ts 复制代码
  private async appendGalleryRecord(record: GalleryMoment): Promise<void> {
    this.logCaptureTrace(
      'append-gallery-record-start',
      `recordId=${record.id} pairIndex=${record.pairIndex} backPath=${record.backPath} frontPath=${record.frontPath}`
    );
    const readyRecord = record.aiStatus === 'ready' ? record : GalleryRecordService.applyLocalInsight(record);
    const nextRecords = [readyRecord, ...this.galleryRecords.filter((item: GalleryMoment) => item.id !== readyRecord.id)];
    this.galleryRecords = nextRecords;
    this.syncRecordSelections(nextRecords);
    this.gallerySelectedId = readyRecord.id;
    this.selectedGalleryGroupKey = this.buildGalleryRecordGroupKey(readyRecord);
    this.galleryUserNoteDraft = this.getRecordUserNote(readyRecord);
    this.showCameraCapturePreview(readyRecord);
    this.syncSelectedMapMemory(true);
    this.capturePairCount = nextRecords.length;
    this.galleryNoticeText = this.hasGalleryFocus()
      ? this.getGalleryScopeDescription()
      : ''
    await this.syncMapMarkers();
    this.updateAwarenessRecommendation(false);
    await this.persistGalleryRecords(nextRecords);
    this.gallerySelectedId = readyRecord.id;
    this.selectedGalleryGroupKey = this.buildGalleryRecordGroupKey(readyRecord);
    this.logCaptureTrace(
      'append-gallery-record-finished',
      `recordId=${readyRecord.id} total=${nextRecords.length} selected=${this.gallerySelectedId}`

预览浮层不是孤立组件,它依赖入库闭环。只有相册状态已经更新,预览上的"去相册查看"才是可靠动作。

工程检查清单

  • 拍摄成功后立即给用户可见反馈。
  • 浮层展示的图片 Uri 来自 GalleryMoment,不重新拼路径。
  • 单拍和双拍共用浮层,双拍额外显示前摄小窗。
  • 入库后先刷新选中记录,再展示预览。
  • 浮层要能关闭,不能阻断连续拍摄。

今日练习

  1. 找到 cameraCapturePreviewActionsVisible,观察它在哪些交互下变化。
  2. 把单拍记录和双拍记录分别代入 showCameraCapturePreview,比较浮层字段差异。
  3. 设计一个"预览后删除"动作,思考它应该先删文件还是先删记录。

训练营后面的内容会继续按"真实页面效果 → 源码定位 → 状态闭环 → 可验证结果"的节奏推进。每一篇都尽量让你能拿着代码直接回到项目里复现,而不是只停留在概念说明。

相关推荐
再见6588 小时前
【HarmonyOS】 Todo 应用开发实战
harmonyos
爱吃大芒果9 小时前
面向大型鸿蒙原生应用的工程基建:核心路由、全局样式库与状态管理设计图纸
华为·harmonyos
轻口味13 小时前
HarmonyOS 6.1.1 全栈实战录 - 91 实战 Call Service Kit 扩展企服来去电智慧
华为·harmonyos·鸿蒙
木斯佳14 小时前
鸿蒙开发入门指南:前端开发者快速理解视频编码概念——输入模式
华为·音视频·harmonyos
不羁的木木15 小时前
《HarmonyOS技术精讲》二:用户动作与状态感知实战
华为·harmonyos
G_dou_18 小时前
Flutter+OpenHarmony 实战:stopwatch 秒表应用
flutter·harmonyos
亚信安全官方账号19 小时前
AISTrustOne鸿蒙版安全方案 让终端防护“内生”力量觉醒
安全·华为·harmonyos
夜勤月19 小时前
HarmonyOS 6.0 ArkWeb实战:PDF背景色自定义功能全解析(附完整代码+避坑指南)
华为·pdf·harmonyos
想你依然心痛20 小时前
HarmonyOS 6(API 23)实战:基于悬浮导航、沉浸光感与HMAF的“药界智脑“——PC端AI智能体沉浸式药物研发与分子模拟工作台
人工智能·华为·ar·harmonyos·智能体