【共创稿事节】HarmonyOS 7氛围设计:光影与声音协同的情绪传递
单独调光,画面会亮但缺口气;单独调声,耳朵有反应但眼睛没跟上。氛围是光影和声音一起作用的结果——用户说不出哪里变了,但整个人就是更专注、更放松,或者更警觉了。空间计算里,氛围是可以被设计、被配置、被复用的,前提是把情绪拆成两组可调的参数,并且让它们同步变化。
先把情绪拆成三个档位
情绪是连续的,但落到实现上必须离散化成可配置的档位,否则调不动。常用的划分是专注、放松、紧张三档,覆盖绝大多数内容型场景。
专注。需要用户长时间投入,目标是减少干扰、维持清醒。视觉上避免高饱和和强动效,声音上避免有旋律、有歌词的内容。
放松。目标是降低唤醒水平,让用户从紧绷状态松弛下来。视觉偏暖、偏柔,声音节奏慢、音色暖、带一点混响。
紧张。目标是提升警觉、制造推进感。视觉对比强、动效快,声音节奏快、高频突出、混响少(干声更贴身)。
这三个档位不是情绪的全部,但它们是坐标轴上的锚点,中间态可以通过参数插值得到。
光与声音各自管什么
把两类媒介的可调维度列清楚,才知道协同要从哪里对齐。
光影这边:
- 色温:暖(偏黄橙)→ 放松;冷(偏蓝)→ 专注、理性。
- 亮度:整体明度,决定画面的"重量"。
- 对比度:明暗差异,决定画面紧不紧张。
- 渐变方向与柔和度:硬边渐变锋利、紧张;大范围柔和渐变安静。
- 动效时长:200ms 以内显得利落、紧张;600ms 以上显得舒缓。
- 模糊与景深:虚化范围大、景深深,留白多,更容易放松。
声音这边:
- 音量:整体响度。
- 音色:低频厚实 = 稳、暖;高频明亮 = 清醒、紧张。
- 节奏(BPM):60~80 偏放松,90~120 偏专注,120 以上偏紧张。
- 混响 / 距离:混响多 = 远、空、放松;混响少 = 近、贴身、紧张。
- 空间感:声场宽、有方位 = 沉浸;单点居中 = 聚焦。
协同的关键是同向。要放松,光和声都往"慢、暖、远"走;要紧张,两者都往"快、冷、近"走。一个往东一个往西,用户只会觉得别扭。
建立映射:场景 → 情绪 → 参数
映射方法分三步。
- 明确场景要用户处在什么状态。读书页要专注,冥想页要放松,限时任务页要适度紧张。
- 从情绪推导两组参数的目标值。
- 落到具体 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);
});
});
}
氛围切换的时序
光影和声音必须一起变,否则会有"画面已经换了、声音还没跟上"的割裂感。用一个统一的切换入口,让两组参数在同一帧开始变化。
几条经验哦
- 先定情绪,再定参数。不要从"我想加个橙色"出发,要从"这里要让人放松"出发。
- 光影和声音的参数要打包成一份配置。分成两个地方维护,迟早会出现方向不一致。
- 过渡时长是情绪表达的一部分。200ms 的切换利落,800ms 的切换舒缓,同一组颜色换时长,感觉就不同。
- 氛围配置要能整体替换。品牌换色、节日主题、夜间模式,本质都是换一份氛围表。
- 留一个"静音 / 关动效"的开关。用户对光和声的敏感度差异很大,把控制权交出去。
- 只在视觉上做氛围,忽略音频。用户戴耳机时,声场对情绪的影响不比画面小。
- 用高音量表达"沉浸"。沉浸来自声场和层次,不来自响度,音量大只会让人疲劳。
- 动效时长和音频淡入淡出不对齐,切换时出现明显的"抢拍"。
- 把氛围参数写死在多处组件里,改一次要翻十几个文件。集中成配置表,页面只引用。
- 夜间场景忘了压亮度,深色背景配高亮强调色,反差过大依然刺眼。
更多推荐


所有评论(0)