单独调光,画面会亮但缺口气;单独调声,耳朵有反应但眼睛没跟上。氛围是光影和声音一起作用的结果——用户说不出哪里变了,但整个人就是更专注、更放松,或者更警觉了。空间计算里,氛围是可以被设计、被配置、被复用的,前提是把情绪拆成两组可调的参数,并且让它们同步变化。

先把情绪拆成三个档位

情绪是连续的,但落到实现上必须离散化成可配置的档位,否则调不动。常用的划分是专注、放松、紧张三档,覆盖绝大多数内容型场景。

专注。需要用户长时间投入,目标是减少干扰、维持清醒。视觉上避免高饱和和强动效,声音上避免有旋律、有歌词的内容。

放松。目标是降低唤醒水平,让用户从紧绷状态松弛下来。视觉偏暖、偏柔,声音节奏慢、音色暖、带一点混响。

紧张。目标是提升警觉、制造推进感。视觉对比强、动效快,声音节奏快、高频突出、混响少(干声更贴身)。

这三个档位不是情绪的全部,但它们是坐标轴上的锚点,中间态可以通过参数插值得到。

光与声音各自管什么

把两类媒介的可调维度列清楚,才知道协同要从哪里对齐。

光影这边:

  • 色温:暖(偏黄橙)→ 放松;冷(偏蓝)→ 专注、理性。
  • 亮度:整体明度,决定画面的"重量"。
  • 对比度:明暗差异,决定画面紧不紧张。
  • 渐变方向与柔和度:硬边渐变锋利、紧张;大范围柔和渐变安静。
  • 动效时长:200ms 以内显得利落、紧张;600ms 以上显得舒缓。
  • 模糊与景深:虚化范围大、景深深,留白多,更容易放松。

声音这边:

  • 音量:整体响度。
  • 音色:低频厚实 = 稳、暖;高频明亮 = 清醒、紧张。
  • 节奏(BPM):60~80 偏放松,90~120 偏专注,120 以上偏紧张。
  • 混响 / 距离:混响多 = 远、空、放松;混响少 = 近、贴身、紧张。
  • 空间感:声场宽、有方位 = 沉浸;单点居中 = 聚焦。

协同的关键是同向。要放松,光和声都往"慢、暖、远"走;要紧张,两者都往"快、冷、近"走。一个往东一个往西,用户只会觉得别扭。

建立映射:场景 → 情绪 → 参数

映射方法分三步。

  1. 明确场景要用户处在什么状态。读书页要专注,冥想页要放松,限时任务页要适度紧张。
  2. 从情绪推导两组参数的目标值。
  3. 落到具体 API。光影用 animateTo 做参数过渡,声音用 AudioRenderer.setVolume 及相关播放参数。

下面这张氛围配置表可以作为起点,实际产品按品牌调性微调。

场景目标情绪主色调亮度对比度动效时长音量音色节奏空间感
阅读专注冷灰 / 米白中中高200ms低中频干净无 / 白噪居中
冥想放松暖橙 / 深青低低800ms很低低频厚慢宽、环绕
音乐播放沉浸跟随封面中低中400ms中全频跟随曲目宽、有方位
限时挑战紧张高饱和红橙高高120ms中高高频亮快贴身
夜间模式放松深褐 / 暗红很低低600ms低柔慢居中

代码:可切换的氛围状态

把三档氛围抽象成一组参数,切换时用属性动画平滑过渡。核心是光影用 animateTo 缓存一次状态变更,声音用 setVolume 同步调整。

import { audio } from '@kit.AudioKit';

// 氛围档位:光影与声音参数打包在一起,保证同向变化
interface Atmosphere {
  bgTop: ResourceColor;      // 背景渐变起始色
  bgBottom: ResourceColor;   // 背景渐变结束色
  textColor: ResourceColor;
  duration: number;          // 动效时长,映射"快/慢"
  volume: number;            // 音频音量
  ambientBlur: number;       // 背景模糊半径,映射"远/近"
}

@Entry
@Component
struct AtmospherePage {
  // 三档氛围配置,一一对应专注 / 放松 / 紧张
  private presets: Record<string, Atmosphere> = {
    '专注': {
      bgTop: '#F2F4F8', bgBottom: '#E2E6EE', textColor: '#1A1A1A',
      duration: 200, volume: 0.3, ambientBlur: 0
    },
    '放松': {
      bgTop: '#3A2E28', bgBottom: '#6B4F3A', textColor: '#F5EDE4',
      duration: 800, volume: 0.15, ambientBlur: 20
    },
    '紧张': {
      bgTop: '#2A0A0A', bgBottom: '#8A1F1F', textColor: '#FFF0F0',
      duration: 120, volume: 0.6, ambientBlur: 0
    }
  };

  @State presetKey: string = '专注';
  private current: Atmosphere = this.presets['专注'];
  private renderer: audio.AudioRenderer | undefined = undefined;

  // 切换氛围:光影与声音在同一时刻朝同一方向变化
  private switchAtmosphere(key: string): void {
    const next = this.presets[key];
    // 光影参数用 animateTo 过渡,时长取自当前氛围的 duration
    this.getUIContext().animateTo({ duration: next.duration, curve: Curve.EaseInOut }, () => {
      this.presetKey = key;
      this.current = next;
    });
    // 声音同步调整音量,避免光影已经切换、声音还停在上一档
    this.applyVolume(next.volume);
  }

  private applyVolume(v: number): void {
    if (this.renderer === undefined) {
      return;
    }
    // setVolume 用于调整播放响度,和光影亮度保持同向
    this.renderer.setVolume(v);
  }

  build() {
    Column() {
      Text(`当前氛围:${this.presetKey}`)
        .fontSize(20)
        .fontColor(this.current.textColor)

      Row({ space: 12 }) {
        Button('专注').onClick(() => this.switchAtmosphere('专注'))
        Button('放松').onClick(() => this.switchAtmosphere('放松'))
        Button('紧张').onClick(() => this.switchAtmosphere('紧张'))
      }
      .margin({ top: 24 })
    }
    .width('100%')
    .height('100%')
    .justifyContent(FlexAlign.Center)
    // 背景用渐变承载色温与明度,模糊承载景深
    .linearGradient({
      angle: 180,
      colors: [
        [this.current.bgTop as string, 0.0],
        [this.current.bgBottom as string, 1.0]
      ]
    })
    .animation({ duration: this.current.duration, curve: Curve.EaseInOut })
  }
}

这段代码里有个取舍:linearGradient 的 colors 变化无法被 animation 直接插值,实际过渡要么走 animateTo 包裹的状态更新,要么用两层渐变叠透明度做交叉淡化。上面同时用了两种方式,真实项目里选一种即可,避免效果重复。

案例:冥想页的氛围组合

冥想页目标是把用户从"刚放下手机"的紧绷状态带到放松。光影和声音要一起往下走。

光影侧:

  • 背景从暖橙过渡到深青的垂直渐变,亮度压到很低,避免强光刺激;
  • 进入时用 800ms 的缓动,页面像"沉"下来而不是"弹"出来;
  • 背景图片叠一层模糊,制造景深,把注意力从背景收进内容。

声音侧:

  • 环境音用低频厚实的音色,音量很低(0.1~0.2 区间),存在感弱;
  • 领读语音或引导音从正前方、近处来,和环境音拉开距离,让引导"贴身"、环境"包住";
  • 如果设备支持空间音频,环境音铺满声场,引导音有明确方位。
import { audio } from '@kit.AudioKit';
import { BusinessError } from '@kit.BasicServicesKit';
import { common } from '@kit.AbilityKit';

// 冥想页:低音量 + 空间化环境音 + 近距离引导音
async function startMeditationAmbient(context: common.UIAbilityContext): Promise<audio.AudioRenderer | undefined> {
  const audioManager = audio.getAudioManager();
  const spatial = audioManager.getSpatializationManager();

  // 环境音走低音量,配合光影的低亮度
  const options: audio.AudioRendererOptions = {
    streamInfo: {
      samplingRate: audio.AudioSamplingRate.SAMPLE_RATE_48000,
      channels: audio.AudioChannel.CHANNEL_2,
      sampleFormat: audio.AudioSampleFormat.SAMPLE_FORMAT_S16LE,
      encodingType: audio.AudioEncodingType.ENCODING_TYPE_RAW
    },
    rendererInfo: {
      usage: audio.StreamUsage.STREAM_USAGE_MUSIC, // 环境音,可与其他音乐并发
      rendererFlags: 0
    }
  };

  // 只有设备支持时才主打空间化,否则退化为普通立体声
  const routing = audioManager.getRoutingManager();
  const output = routing.getDevicesSync(audio.DeviceFlag.OUTPUT_DEVICES_FLAG);
  const spatialReady = output.some((d) => d.spatializationSupported)
    && spatial.isSpatializationEnabledForCurrentDevice();

  return new Promise((resolve) => {
    audio.createAudioRenderer(options, (err: BusinessError, renderer: audio.AudioRenderer) => {
      if (err) {
        console.error(`createAudioRenderer failed: ${err.code}, ${err.message}`);
        resolve(undefined);
        return;
      }
      renderer.setVolume(0.15); // 与环境光的低亮度同向
      console.info(`meditation ambient ready, spatial=${spatialReady}`);
      resolve(renderer);
    });
  });
}

氛围切换的时序

光影和声音必须一起变,否则会有"画面已经换了、声音还没跟上"的割裂感。用一个统一的切换入口,让两组参数在同一帧开始变化。

音频层光影层氛围页面用户音频层光影层氛围页面用户两组参数的过渡时长需一致或成比例选择氛围档位查预置参数表animateTo 更新渐变与模糊setVolume 同步音量背景色温与景深过渡响度与声场过渡

几条经验哦

  • 先定情绪,再定参数。不要从"我想加个橙色"出发,要从"这里要让人放松"出发。
  • 光影和声音的参数要打包成一份配置。分成两个地方维护,迟早会出现方向不一致。
  • 过渡时长是情绪表达的一部分。200ms 的切换利落,800ms 的切换舒缓,同一组颜色换时长,感觉就不同。
  • 氛围配置要能整体替换。品牌换色、节日主题、夜间模式,本质都是换一份氛围表。
  • 留一个"静音 / 关动效"的开关。用户对光和声的敏感度差异很大,把控制权交出去。
  • 只在视觉上做氛围,忽略音频。用户戴耳机时,声场对情绪的影响不比画面小。
  • 用高音量表达"沉浸"。沉浸来自声场和层次,不来自响度,音量大只会让人疲劳。
  • 动效时长和音频淡入淡出不对齐,切换时出现明显的"抢拍"。
  • 把氛围参数写死在多处组件里,改一次要翻十几个文件。集中成配置表,页面只引用。
  • 夜间场景忘了压亮度,深色背景配高亮强调色,反差过大依然刺眼。
Logo

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

更多推荐