第40篇|美颜预设:自然、人像、清透如何变成可解释选项

相机 App 的效果设置很容易做成一堆参数开关,但训练营项目选择了更贴近用户理解的表达:自然、人像、清透。它们不直接暴露底层算法参数,而是作为拍摄语义进入页面状态、拍摄摘要和后续记录。

这一篇我们不讨论美颜算法本身,而是看工程上如何把“可理解的预设”变成稳定的状态管理。对于智能相机,用户不一定关心每个参数值,但他需要知道当前拍出来的照片用了什么风格。

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

本篇目标

  • 把美颜预设设计成稳定枚举,而不是散落字符串。
  • 理解选项数组、选中态和标签推导之间的关系。
  • 掌握效果面板如何复用 chip 组件。
  • 让拍摄摘要记录用户选择,避免效果不可追踪。

对应源码位置

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

一、效果选项要先有产品语义

BeautyPresetKey 只有三个值:naturalportraitclear。这比直接出现“磨皮 30、提亮 20、锐化 10”更适合训练营项目,因为文章、截图和实际体验都能围绕用户能理解的词展开。

当选项变成类型以后,后续 UI、摘要和记录都可以引用同一组 key。这样改文案不会影响业务判断,改状态也不需要全文搜索中文标签。

图1 相机页中的美颜预设入口和用户可感知效果设置

图1 相机页中的美颜预设入口和用户可感知效果设置

二、枚举和状态集中定义,避免魔法字符串

项目把效果分类、美颜预设、补光颜色和水印样式都放在页面顶部的类型定义和状态区域。你可以一眼看出拍摄效果有哪些维度,也能知道当前默认值是什么。

这种写法适合训练营讲解:学员先从状态看功能边界,再进入 UI 和拍摄链路,不会被分散在多个 Builder 里的字符串干扰。

图2 拍摄效果的枚举、状态和选项数组集中定义

图2 拍摄效果的枚举、状态和选项数组集中定义

type BeautyPresetKey = 'natural' | 'portrait' | 'clear';
type FillLightColorKey = 'off' | 'warm' | 'cool' | 'rose';
type WatermarkStyleKey = 'none' | 'time' | 'place' | 'dual';
type CameraEffectCategoryKey = 'beauty' | 'fillLight' | 'watermark';
type PhotoOutputReadyKey = 'dualBack' | 'dualFront' | 'single';

interface MemoryRecordGroup {
  key: string;
  latitude: number;
  longitude: number;

这里建议继续保持 key 的稳定性。后续如果要接入真正的图像效果参数,可以在 key 到参数表之间新增映射,而不是直接改掉 key。

三、标签和摘要从状态推导

getBeautyPresetLabel 把内部 key 转成用户能看懂的中文标签。拍摄摘要再把美颜、补光、水印拼成一句简短说明。这样用户拍完以后知道“这张照片为什么看起来这样”,记录回看时也能知道当时的拍摄设置。

摘要不是装饰文案,它承担了轻量审计作用。以后做云同步、分享或导出时,摘要可以成为照片元信息的一部分。

图3 美颜标签和拍摄摘要由当前状态推导

图3 美颜标签和拍摄摘要由当前状态推导

  private getBeautyPresetLabel(preset: BeautyPresetKey): string {
    if (preset === 'portrait') {
      return '人像';
    }
    if (preset === 'clear') {
      return '清透';
    }
    return '自然';
  }

  private getBeautyOverlayColor(): string {
    if (this.selectedBeautyPreset === 'portrait') {
      return '#22FFD3C2';
    }
    if (this.selectedBeautyPreset === 'clear') {
      return '#1EDDF8FF';
    }
    return '#0DFFFFFF';
  }

  private getBeautyOverlayOpacity(): number {
    if (this.selectedBeautyPreset === 'portrait') {
      return 0.54;
    }
    if (this.selectedBeautyPreset === 'clear') {
      return 0.42;
    }
    return 0.22;
  }

把标签推导封装成函数还有一个好处:UI 多处展示同一个预设时,不会出现一个地方叫“清透”、另一个地方叫“通透”的不一致。

四、效果面板复用同一个 Chip 组件

效果面板根据当前分类渲染对应选项。美颜、补光、水印看起来是三组不同功能,但它们在交互上都是“点选一个 chip,更新状态,刷新选中态”。因此项目用 buildCameraEffectChip 复用样式和点击行为。

如果每一组效果都手写按钮,很快会出现圆角、字体、背景和点击逻辑不一致。训练营项目更适合把这类重复 UI 收口,让文章可以把重点放在状态流上。

图4 buildCameraEffectSelectedOptions 渲染美颜、补光、水印选项

图4 buildCameraEffectSelectedOptions 渲染美颜、补光、水印选项

  private buildCameraEffectSelectedOptions() {
    Scroll() {
      Row({ space: 8 }) {
        if (this.selectedCameraEffectCategory === 'beauty') {
          ForEach(this.beautyPresetOptions, (preset: BeautyPresetKey) => {
            this.buildCameraEffectChip(this.getBeautyPresetLabel(preset), this.selectedBeautyPreset === preset, () => {
              this.selectedBeautyPreset = preset;
              this.cameraEffectOptionRevision += 1;
              this.lastCaptureSummary = this.getCameraEffectSummary();
            })
          }, (preset: BeautyPresetKey) => `${preset}-${this.selectedBeautyPreset}-${this.cameraEffectOptionRevision}`)
        } else if (this.selectedCameraEffectCategory === 'fillLight') {
          ForEach(this.fillLightColorOptions, (color: FillLightColorKey) => {
            this.buildCameraEffectChip(this.getFillLightLabel(color), this.selectedFillLightColor === color, () => {
              this.selectedFillLightColor = color;
              this.cameraEffectOptionRevision += 1;
              this.lastCaptureSummary = this.getCameraEffectSummary();
            })
          }, (color: FillLightColorKey) => `${color}-${this.selectedFillLightColor}-${this.cameraEffectOptionRevision}`)
        } else {
          ForEach(this.watermarkStyleOptions, (style: WatermarkStyleKey) => {
            this.buildCameraEffectChip(this.getWatermarkStyleLabel(style), this.selectedWatermarkStyle === style, () => {
              this.selectedWatermarkStyle = style;
              this.cameraEffectOptionRevision += 1;
              this.lastCaptureSummary = this.getCameraEffectSummary();
            })
          }, (style: WatermarkStyleKey) => `${style}-${this.selectedWatermarkStyle}-${this.cameraEffectOptionRevision}`)
        }
      }

这一段代码也说明了一个 ArkUI 实战习惯:列表 key 要包含当前选中状态或修订号,避免选项变化后 UI 复用造成显示不刷新。

工程检查清单

  • 预设 key 使用英文稳定值,展示文案通过函数转换。
  • 默认值在状态区清晰可见,方便排查首次进入页面的效果。
  • 拍摄摘要要包含用户选择过的效果信息。
  • 效果面板复用 chip 组件,减少 UI 分叉。
  • 新增效果时先扩展类型和选项数组,再补标签和摘要。

今日练习

  1. 新增一个 cinematic 美颜 key,先不实现算法,只让 UI 能显示并进入摘要。
  2. 检查效果面板切换分类时,之前选中的美颜状态是否仍然保留。
  3. 思考如果以后接入真实美颜 SDK,key 到参数表应该放在页面层还是服务层。

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

Logo

讨论HarmonyOS开发技术,专注于API与组件、DevEco Studio、测试、元服务和应用上架分发等。

更多推荐