HarmonyOS 7 局部深色主题终于跟到弹窗了,写死的文字颜色却可能看不见

页面中间是一块深色工具区,点开菜单却一直是浅色。升级target后菜单终于跟着变深,原来写死的深色文字又可能和背景挤在一起。这个变化不应简单归类成“主题坏了”:一部分是框架修正了继承行为,另一部分是应用把前景和背景分开管理的隐患被暴露出来。

本文关注WithTheme在HarmonyOS 7的两条变化:弹出内容跟随宿主主题;动态添加的组件响应局部主题变化。给出的交付物是主题边界模型、测试组合生成器和一段动态列表复现页,不是把所有组件外面再套一层SYSTEM。

官方Beta1文档更新于2026年8月19日,本文9月28日核对。纯逻辑测试已在宿主执行,ArkUI页面尚未做API26编译和真机视觉验证。示意图不代表所有组件的实际默认配色。

先确认是不是这次变更

相关能力起始API12,本次行为只在targetSdkVersion大于等于26.0.0生效。官方列出的弹出类场景包括bindMenu、bindPopup、bindSheet和CalendarPicker。升级后弹出内容会跟随宿主节点的局部主题或深浅色样式。

动态添加场景也有具体支持范围,不应扩大成所有自定义内容都自动正确变色。官方列举了Button、Menu、多个Picker、Progress、Scroll、Tabs等组件;新加入支持的组件另有清单。项目要对照自己真正使用的组件逐项核对。

显式写入的颜色不会因为你希望“自动适配”就必然变成正确的前景背景组合。框架负责主题能力,应用仍负责固定样式与资源语义是否一致。

局部主题与系统主题应有明确边界

案例一:深色工具区里的菜单应跟谁

先做产品决定:菜单属于这块工具区,就应与宿主一致;它是需要遵循系统外观的独立内容,就明确系统边界。不要在截图失败时随机试DARK、LIGHT、SYSTEM直到“看起来差不多”。

官方建议,若需要保留跟随系统的行为,可将相关组件放入WithTheme({ colorMode: ThemeColorMode.SYSTEM })。这不是说SYSTEM永远是浅色,而是让它跟随系统当前模式。

下面的模型表达产品侧边界决策,用于测试配置,不是复刻WithTheme内部实现:

type Mode = 'light'|'dark';
type Scope = 'inherit'|'system'|Mode;
function resolveMode(scope:Scope, parent:Mode, system:Mode): Mode {
  if (scope === 'inherit') return parent;
  if (scope === 'system') return system;
  return scope;
}
type Palette = { background:string; foreground:string };
const palettes:Record<Mode,Palette> = {
  light:{background:'#FFFFFF',foreground:'#202124'},
  dark:{background:'#202124',foreground:'#F5F5F5'}
};
function popupPalette(scope:Scope,parent:Mode,system:Mode): Palette {
  return {...palettes[resolveMode(scope,parent,system)]};
}
function check(value:boolean): void { if (!value) throw new Error('assertion failed'); }
check(resolveMode('inherit','dark','light') === 'dark');
check(resolveMode('system','dark','light') === 'light');
check(resolveMode('system','light','dark') === 'dark');
const popup = popupPalette('inherit','dark','light');
check(popup.background === '#202124' && popup.foreground === '#F5F5F5');

调色板是本文自定义测试值,不是HarmonyOS默认主题色。它把前景和背景作为一对返回,用来避免“背景跟随主题,文字固定为深色”的配置裂缝。正式设计应使用合适的主题资源,并实际检查可读性,不要用简单的两色相等判断替代视觉或对比度验证。

对于菜单是否在打开期间动态刷新,不能从“弹出时跟随”进一步推断全部生命周期细节。应分别测试打开前切换主题、打开期间切换主题、关闭后再次打开,记录当前组件的真实行为。

案例二:主题切换后才加入列表的按钮

动态列表比静态页面更容易漏测。首次渲染的按钮正确,并不意味着后面ForEach新增的按钮也正确。官方此次补齐的正是相关动态场景。

下面的页面将两类意图同时展示:一类跟随外层局部模式,另一类明确跟随系统。它是对照复现页,不建议把两种模式无理由混在正式产品里。

@Entry
@Component
struct ThemeScopeCheck {
  @State dark: boolean = true;
  @State items: number[] = [];
  private nextId: number = 0;
  build() {
    Column({ space: 16 }) {
      Button('切换局部模式').onClick(() => { this.dark = !this.dark; })
      Button('增加一项').onClick(() => { this.items.push(this.nextId++); })
      WithTheme({ colorMode: this.dark ? ThemeColorMode.DARK : ThemeColorMode.LIGHT }) {
        List() {
          ForEach(this.items, (id: number) => {
            ListItem() {
              Column({ space: 8 }) {
                Button('局部主题 ' + id)
                WithTheme({ colorMode: ThemeColorMode.SYSTEM }) {
                  Button('系统主题 ' + id)
                }
              }
            }
          }, (id: number) => id.toString())
        }.width('100%').height(320)
      }
    }.padding(20)
  }
}

复现顺序要有区分:先增加再切换;先切换再增加;连续切换后增加。否则只点击“增加”一次,看见颜色正常就结束,会错过本次变更真正涉及的时间顺序。

下面生成测试组合,防止只测系统浅色与局部深色这一种。生成的是待执行清单,不能把清单数量当设备测试通过数量。

type Scenario = { system:Mode; local:Mode; order:'add-then-switch'|'switch-then-add'; scope:'inherit'|'system' };
function scenarios(): Scenario[] {
  const result:Scenario[] = [];
  const modes:Mode[] = ['light','dark'];
  const orders:Scenario['order'][] = ['add-then-switch','switch-then-add'];
  const scopes:Scenario['scope'][] = ['inherit','system'];
  for (const system of modes) for (const local of modes)
    for (const order of orders) for (const scope of scopes) result.push({system,local,order,scope});
  return result;
}
const matrix = scenarios();
check(matrix.length === 16);
check(new Set(matrix.map(x=>JSON.stringify(x))).size === 16);
check(matrix.filter(x=>x.scope==='system').every(x=>resolveMode(x.scope,x.local,x.system)===x.system));

不要用SYSTEM把问题全部盖住

在所有组件外面套SYSTEM确实可能让截图接近旧样子,但也取消了原本希望局部主题控制的效果。比较合理的顺序是先确认设计归属,再检查颜色来源,最后决定是否隔离。

意图处理方式风险
与工具区保持一致继承局部主题,成对检查颜色资源固定颜色可能破坏可读性
明确跟随系统设置局部SYSTEM边界与周围工具区不同是预期,不是bug
固定品牌色内容自己负责完整配色与状态不能依赖框架替你修复所有对比问题

我更倾向保留最少的主题边界。每多一层覆盖,都应该能回答为什么这块内容不跟父级。否则页面经过多次修补后,颜色由哪层决定会越来越难查。

发布前的视觉检查

同时检查普通、按压、禁用、选中状态,以及弹出层遮罩和文字。不要只截图一个静态按钮。对动态组件记录创建时的局部模式,再检查后续模式变化;对弹出层记录宿主位置和模式,不要只看弹窗自身。

如果某组件不在官方列举范围,去查当前组件文档和实际表现,不要靠“都在WithTheme里面”推断必然支持。把不支持、固定色覆盖和主题边界错误分开,修复才不会越做越乱。

这次升级更像一次主题债务检查:框架继承变完整后,应用之前藏在不一致行为下的固定颜色会暴露。把主题归属和前景背景一起管理,比添加更多硬编码颜色更耐用。

参考

Logo

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

更多推荐