03 · 氛围灯系统专题
氛围灯是 Light 模块最复杂、设计最精巧的子系统。本章是 Light 知识库的核心。
一、三层心智模型
氛围灯采用三层级联架构(总开关 → 效果模式 → 颜色/主题/动态参数):
Layer 0 总开关 MOOD_LIGHT_TRIGGER [OFF / ON / AUTO]
└─ dependKey 级联所有子项显隐 + 每个子 Controller 观察它判可用性
Layer 1 效果模式 MOOD_LIGHT_MODE_SETTINGS [常亮 STATIC_LIGHTING / 呼吸 BREATH / 音乐 MUSIC]
└─ ModeData ViewModel 决定 Layer 2/3 哪组显示
Layer 2 颜色配置
┌─ 非 26A(单层)──────────────────────────────────┐
│ DisplayAtmosphereTheme (6 圆形: THEME1-5 + CUSTOM) │
│ + ColorPickController (单层取色板) │
│ CUSTOM 选中时显示取色板 │
└──────────────────────────────────────────────────┘
┌─ 26A(双层)─────────────────────────────────────┐
│ AtmosphereColorMode (单色/双色/自定义) │
│ ├─ SingleColorTab (THEME1-5) │
│ ├─ DoubleColorTab (双色 THEME1-5) │
│ └─ DoubleColorPickController (双层取色板+同步) │
└──────────────────────────────────────────────────┘
Layer 3 动态参数(跟随 Layer 1 模式)
├─ 常亮 → AtmosphereBrightness (MOOD_LIGHT_BRIGHTNESS 1-100)
├─ 呼吸 → LightBreathState (慢/快)
└─ 音乐 → MusicRhythm (保持当前色/跟随封面)
独立项(跟随总开关 dependKey,不随模式切换)
├─ WelcomeLightSwitch (上下车迎宾)
├─ AmbientLightDimmingDown (夜间减弱)
└─ FootLight (照脚灯,无 dependKey 独立)
关键解耦:AtmosphereData ViewModel 用 6 个 LiveData(Theme/TabChange/ModeData/MusicRhythmType/ColorModeData/ThemeName)在 Controller 间传递状态,避免直接互调。
二、MOOD_LIGHT_TRIGGER 为何引用 27 次
总开关身兼三职:
- 父信号驱动子项显隐:
AtmosphereLightTabLayoutPrefController.getSubDependPropertyId()=TRIGGER,trigger=ON/AUTO 时updatePreferencesVisibleWhenStatusChange(true)级联所有子项。 - 每个子 Controller 观察它判可用性:几乎所有子 Controller 的
getObservedPropertyIdSet()都加 TRIGGER,isInnerLogicAvailable()里... && isPropAvailable(TRIGGER)。trigger 不可用 → 整条链路置灰。 - Helper 静态可见性判断:
LightPreferenceVisibleStatusHelper.isAtmosphereModePreferenceVisible()读 TRIGGER 缓存值,OFF 则隐藏整组(resume 重建时的”无信号快速判定”)。
三、两套并行 UI(非 26A vs 26A)
| 维度 | 非 26A(单层) | 26A(双层,Modena-26A) |
|---|---|---|
| 判定 | !isDualLayerAtmosphereLightEnable | isDualLayerAtmosphereLightEnable(多区色彩=ZONE_2_UPPER_LOWER_TUNING) |
| 颜色模式入口 | 无(直接主题) | AtmosphereColorModeTab(单/双/自定义) |
| 主题 | DisplayAtmosphereTheme(6 圆形:THEME1-5+CUSTOM) | SingleColorTab(5 圆形)/ DoubleColorTab(5 圆形双色) |
| 取色板 | ColorPickController(单层 1 个色盘) | DoubleColorPickController(上下 2 个色盘 + 同步按钮) |
| 色信号 | ATMOSPHERE_COLOR | ATMOSPHERE_COLOR(上) + ATMOSPHERE_COLOR_LOWER(下) |
| 主题信号 | MOOD_LIGHT_THEME_SETTINGS | MOOD_LIGHT_THEME_SETTINGS(单) + AMBIENT_LIGHT_BICOLOUR_THEME_STATUS(双) |
| XML 容器 | pk_settings_display_atmosphere_light_default | pk_settings_display_atmosphere_light_26A |
两组在 LightsSettingsFragment.getPreferenceKeyResIdsToRemove() 互斥裁剪。
四、256 色色盘机制
色盘是自定义 View ColorPickView(base/settingsBaseUi),核心机制:
- 256 色硬编码色表(
ColorTables.sColorList):ColorBeanRGB 数组,从纯白渐变到黑再循环色相。 - 相似度算法:用户触摸点取像素 RGB →
ColorTableUtils.findColor在 256 色表里找最接近的索引。 - 索引 ↔ 信号值:信号值 = 索引 + 1(范围 1-256)。下发
setVehicleProperty(ATMOSPHERE_COLOR, colorIndex + 1)。 - 索引 ↔ 屏幕坐标:
ColorTables.sColorPointMap维护 ARGB→dp 坐标,getColorPositionByIndex按 dp→px 放大。 - 主题预设 ↔ 索引:
sThemeColorIndexMap把 THEME1-5 映射到R.integer.atmosphere_theme_*_color_index(RRO 可覆盖);双色用sDoubleThemeColorIndexMap(Pair<upper, lower>)。
五、双拼色同步(精巧设计)
ColorPickManager(object 单例)是双拼色同步中枢,让上下两层取色板既能独立操作,又能联动。
三件套机制
- 按下标路由 propId:
SingleColorPickController不知道自己代表上层还是下层,所有差异(propId、profile key、View)由ColorPickManager注入。handleColorChange/handleActionUp按下标路由到正确 propId。 - 位掩码屏蔽上行:
ignorePropFlag按位记录每个板是否屏蔽上行信号(避免反馈环)。ignorePropertyChange()满足「同步且有任一板屏蔽 / 未同步且本板屏蔽 / 信号值=本板当前值」之一就忽略。 - 2s 延时回弹:用户拖动板 →
setIgnorePropFlag(this, true)屏蔽上行;onActionUp→ 发 2s 延时任务,2s 后setIgnorePropFlag(false)+resetPoint()主动读信号回弹。
同步触发路径
(1) 应用内点同步按钮(DoubleColorPickPreference):
点同步 → 翻转 mIsAssociate → ColorPickManager.setIsAssociate + onAssociateChanged
→ if(isAssociate) 把上层颜色灌到下层 + setIgnorePropFlag(lower, true)
→ setVehicleProperty(ATMOSPHERE_COLOR_SYNCHRONIZATION_SET, ON/OFF)
(2) ColorPickManager 同步路由(开启 isAssociate 后):
handleColorChange(sender)forEach 所有 controller 同步更新handleActionUp(sender)广播给所有setInteractingFlag:触摸一板时另一板setIsInteractable(false)防误触
(3) 跨进程广播(AtmosphereLightSyncReceiver):
监听 mi.car.settings.action.ATMOSPHERE_LIGHT_SYNCHRONIZE
→ 校验 colorIndexProperty == 当前 ATMOSPHERE_COLOR(防陈旧广播)
→ ColorPickManager.handleAtmosphereColorSync: 把上层 profile copy 到下层 + resetPoint
六、音乐律动真相(不监听音频)
关键事实:app 端无任何 AudioManager / MediaController / 录音逻辑(全模块 grep 证实)。
实际机制:
- 车端硬件做音频分析,结果以两个信号上报:
MUSIC_RHYTHM_STATE(Light.RHYTHM_FLAG) —— 是否处于律动态(MUSICAL_RHYTHM)ATMOSPHERE_COLOR_IN_RHYTHM(Light.MUSIC_ALBUM_COLOR) —— 当前专辑封面色 ARGB int
- App 仅消费这两个信号更新右栏车模预览颜色遮罩(
updateAtmosphereColor):- 律动态(
MUSIC_RHYTHM_STATE=1 && 模式=MUSIC && 律动=跟随封面)→ 用专辑色 - 否则 → 用主题色
- 律动态(
- 用户能配置的只有
MOOD_LIGHT_RHYTHM_TYPE(用自定义色 vs 跟随封面),实际律动效果完全由车端实现。
七、三主题信号的区别(易混)
| 信号 | 含义 | 范围 |
|---|---|---|
MOOD_LIGHT_MODE_SETTINGS | 效果模式(动态行为) | 常亮/呼吸/音乐(所有车型) |
AMBIENT_LIGHT_THEME_MODE_STATUS | 颜色模式(颜色怎么选) | 单色/双色/自定义(仅 26A) |
MOOD_LIGHT_THEME_SETTINGS | 单色主题预设编号 | THEME1-5 + CUSTOM(所有车型) |
AMBIENT_LIGHT_BICOLOUR_THEME_STATUS | 双色主题预设编号 | THEME1-5(仅 26A) |
逻辑层级:效果模式 ⊃ 颜色模式 ⊃ 主题。单色主题(THEME1-5)与双色主题(也是 THEME1-5)走不同 propId,因为双色每个”主题号”对应一对(上+下)配色。
八、动态效果三件套(BaseAtmosphereModeSubItemTabPrefController)
呼吸/律动/亮度都继承(或模仿)BaseAtmosphereModeSubItemTabPrefController,子类实现 getVisibleAtmosphereMode() 返回自己该出现的模式:
| Controller | 信号 | 可见模式 | 取值 |
|---|---|---|---|
| AtmosphereBrightness | MOOD_LIGHT_BRIGHTNESS | STATIC_LIGHTING | 1-100(@PropertyRange offset=-1) |
| LightBreathState | MOOD_LIGHT_BREATH_STATE | BREATH | SLOW_BREATH / FAST_BREATH |
| MusicRhythm | MOOD_LIGHT_RHYTHM_TYPE | MUSIC | FOLLOW_CUSTOM / FOLLOW_ALBUM_COVER |
基类提供:①集中订阅 ModeData LiveData ②统一可见性规则(总开关开 + 模式匹配)③接收父项可见性透传(IVisibleInterceptor)④默认隐藏防闪现 ⑤onHandlePropertyManagerPrepared 主动读模式校正。
异常:
AtmosphereBrightness因是 Progress 非 Tab,不能继承BaseAtmosphereModeSubItem,只能复制相同逻辑(代码异味)。
九、右栏车模氛围灯可视化
LightsRightFragment(滚动联动)按氛围灯状态渲染:
setAtmosphereMaskColor(color)—— 单层遮罩上色(ColorMatrixColorFilter)setUpperAtmosphereMaskColor/setLowerAtmosphereMaskColor—— 双层分别管理setAtmosphereAnim(propVal)—— 按 MOOD_LIGHT_BREATH_STATE 播呼吸动画(慢 2000ms/快 1000ms)updateAtmosphereModeSettings(value)—— 常亮/呼吸/音乐三模式控制呼吸动画开关 + 音乐律动 Lottie 可见性
颜色映射(LightsRightFragmentController.updateAtmosphereColor)用 ColorTableUtils 把 propIndex 转 Color:
- 双层:按 AMBIENT_LIGHT_THEME_MODE_STATUS 分 SINGLE/BICOLOR/CUSTOM,分别用主题/双色/上下层索引
- 单层:用 MOOD_LIGHT_THEME_SETTINGS(CUSTOM 用 ATMOSPHERE_COLOR)