标签 WithTheme 下的文章

在 ArkUI 里,做主题和平时做样式是两件事:

  • 样式:某个组件单独改 fontColorbackgroundColor
  • 主题:一整块区域里的组件,整体按一套规则变色

API Version 12 开始,ArkUI 提供了一个专门做「局部主题」的组件:WithTheme
它不负责画 UI,只负责一件事:给作用域里的组件套一层主题/深浅色规则

这篇文章就是一份可以直接上手的 WithTheme 自学指南,适合发社区、做笔记或带项目里落地。


一、WithTheme 是什么?

官方定义很简单:

  • WithTheme 是一个主题作用域容器
  • 只接受一个子组件(可以是 Column / Row / 自定义组件);
  • 只负责两件事:

    • 配置这一块区域用哪套 自定义主题颜色theme);
    • 控制这一块区域的 深色 / 浅色模式colorMode)。

基础信息:

  • 支持版本:从 API Version 12 开始;
  • 系统能力SystemCapability.ArkUI.ArkUI.Full
  • 元服务:从 API 12 开始支持元服务 API;
  • 不支持通用属性、不支持通用事件(它只是“包裹容器”,样式写在子组件上)。

二、WithTheme 能影响哪些组件?

不是所有组件都会响应 WithTheme,这点很关键。当前支持的系统组件包括:

  • 输入类:TextInputSearch
  • 按钮 & 徽标:ButtonBadgeCounter
  • 轮播 & 选择类:SwiperSelectMenu
  • 文本类:Text
  • 选择器类:

    • TimePickerDatePickerTextPicker
    • CheckboxCheckboxGroupRadio
    • Slider
  • 状态展示类:

    • ProgressTogglePatternLockQRCode
  • 分隔类:Divider
简单记:表单控件 + 按钮 + 文本 + 分隔线,大部分能跟着 WithTheme 一起变。

三、核心接口与配置项

3.1 WithTheme 基本接口

WithTheme(options: WithThemeOptions) {
  // 只能有一个子组件
  // 这个子组件里面可以再写 Column/Row/自定义组件
}
注意:WithTheme 不支持通用属性和通用事件,需要把布局、点击等逻辑写在内部组件上。

3.2 WithThemeOptions 结构

interface WithThemeOptions {
  theme?: CustomTheme        // 自定义主题配色
  colorMode?: ThemeColorMode // 深浅色模式
}
  • theme?: CustomTheme

    • 用于指定 WithTheme 作用域内组件的缺省配色
    • 默认:undefined,表示跟随系统 token 默认样式。
  • colorMode?: ThemeColorMode

    • 控制作用域内组件的深色/浅色模式
    • 默认:ThemeColorMode.SYSTEM(跟随系统)。

3.3 CustomTheme 类型

type CustomTheme = CustomTheme
  • CustomTheme 实际上是一个接口;
  • 搭配 CustomColors 一起使用,用来描述一整套颜色体系(比如一套绿色主题、一套红色主题)。

四、局部深浅色:colorMode 实战

很多页面希望做到:

  • 整体跟随系统;
  • 但某一块区域 强制深色(比如顶部 Banner)或 强制浅色(比如活动卡片)。

这时可以用 WithTheme 搭配 colorMode

4.1 深浅色资源准备:dark.json

image.png

要让深浅色生效,先准备深色资源文件 dark.json,例如:

{
  "color": [
    {
      "name": "start_window_background",
      "value": "#000000"
    }
  ]
}

4.2 示例:同一页面展示默认、Dark、Light 三种区域

image.png

@Entry
@Component
struct Index {
  build() {
    Column() {
      // ① 系统默认区域
      Column() {
        Text('无WithTheme')
          .fontSize(40)
          .fontWeight(FontWeight.Bold)
      }
      .justifyContent(FlexAlign.Center)
      .width('100%')
      .height('33%')
      .backgroundColor($r('app.color.start_window_background'))

      // ② 局部强制深色模式
      WithTheme({ colorMode: ThemeColorMode.DARK }) {
        Column() {
          Text('WithTheme')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
          Text('DARK')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
        }
        .justifyContent(FlexAlign.Center)
        .width('100%')
        .height('33%')
        .backgroundColor($r('sys.color.background_primary'))
      }

      // ③ 局部强制浅色模式
      WithTheme({ colorMode: ThemeColorMode.LIGHT }) {
        Column() {
          Text('WithTheme')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
          Text('LIGHT')
            .fontSize(40)
            .fontWeight(FontWeight.Bold)
        }
        .justifyContent(FlexAlign.Center)
        .width('100%')
        .height('33%')
        .backgroundColor($r('sys.color.background_primary'))
      }
    }
    .height('100%')
    .expandSafeArea(
      [SafeAreaType.SYSTEM],
      [SafeAreaEdge.TOP, SafeAreaEdge.END, SafeAreaEdge.BOTTOM, SafeAreaEdge.START]
    )
  }
}

使用建议:

  • 想让某个模块始终深色:WithTheme({ colorMode: ThemeColorMode.DARK })
  • 想让底部工具条固定浅色:ThemeColorMode.LIGHT
  • 根节点跟系统,局部区域用 WithTheme 做反色/特殊效果,是比较推荐的实践。

五、自定义主题:CustomTheme + CustomColors 实战

除了深浅色,有时我们希望整块区域用一套品牌色,比如「绿色主题卡片」vs「红色活动卡片」。

这时用 CustomTheme 来定义一套颜色,然后交给 WithTheme

5.1 定义颜色集合 CustomColors

import { CustomTheme, CustomColors } from '@kit.ArkUI';

class GreenColors implements CustomColors {
  fontPrimary = '#ff049404';
  fontEmphasize = '#FF00541F';
  fontOnPrimary = '#FFFFFFFF';
  compBackgroundTertiary = '#1111FF11';
  backgroundEmphasize = '#FF00541F';
  compEmphasizeSecondary = '#3322FF22';
}

class RedColors implements CustomColors {
  fontPrimary = '#fff32b3c';
  fontEmphasize = '#FFD53032';
  fontOnPrimary = '#FFFFFFFF';
  compBackgroundTertiary = '#44FF2222';
  backgroundEmphasize = '#FFD00000';
  compEmphasizeSecondary = '#33FF1111';
}
实际项目里可以按照设计给的 token 表来映射,保持命名和 UI 视觉规范一致。

5.2 封装成 CustomTheme

class PageCustomTheme implements CustomTheme {
  colors?: CustomColors

  constructor(colors: CustomColors) {
    this.colors = colors
  }
}

5.3 使用 WithTheme 控制局部主题

下面这个例子展示了一个典型的用法:
上半部分使用系统默认按钮配色
下半部分被 WithTheme 包裹,使用可切换的自定义主题

@Entry
@Component
struct IndexPage {
  static readonly themeCount = 3;

  themeNames: string[] = ['System', 'Custom (green)', 'Custom (red)'];

  themeArray: (CustomTheme | undefined)[] = [
    undefined,                              // 系统默认主题
    new PageCustomTheme(new GreenColors()), // 绿色主题
    new PageCustomTheme(new RedColors())    // 红色主题
  ]

  @State themeIndex: number = 0;

  build() {
    Column() {
      // 区域一:未使用 WithTheme,系统默认配色
      Column({ space: '8vp' }) {
        Text('未使用WithTheme')

        // 点击切换下方 WithTheme 的配色
        Button(`切换theme配色:${this.themeNames[this.themeIndex]}`)
          .onClick(() => {
            this.themeIndex = (this.themeIndex + 1) % IndexPage.themeCount;
          })

        // 系统默认按钮配色
        Button('Button.style(NORMAL) with System Theme')
          .buttonStyle(ButtonStyleMode.NORMAL)
        Button('Button.style(EMP..ED) with System Theme')
          .buttonStyle(ButtonStyleMode.EMPHASIZED)
        Button('Button.style(TEXTUAL) with System Theme')
          .buttonStyle(ButtonStyleMode.TEXTUAL)
      }
      .margin({ top: '50vp' })

      // 区域二:使用 WithTheme,局部换肤
      WithTheme({ theme: this.themeArray[this.themeIndex] }) {
        Column({ space: '8vp' }) {
          Text('使用WithTheme')
          Button('Button.style(NORMAL) with Custom Theme')
            .buttonStyle(ButtonStyleMode.NORMAL)
          Button('Button.style(EMP..ED) with Custom Theme')
            .buttonStyle(ButtonStyleMode.EMPHASIZED)
          Button('Button.style(TEXTUAL) with Custom Theme')
            .buttonStyle(ButtonStyleMode.TEXTUAL)
        }
        .width('100%')
      }
    }
  }
}

效果:

  • 上半部分:始终采用系统默认主题;
  • 下半部分:随着按钮点击,在 System / Green / Red 三种主题间切换;
  • 完全局部生效,不影响其他页面和组件。

六、常见使用场景

结合上面的能力,WithTheme 很适合这些场景:

  1. 局部夜间模式

    • 例如:播放器底部控制条、评论区、侧边栏等;
    • 根页面跟系统,某个区域用深色:
    WithTheme({ colorMode: ThemeColorMode.DARK }) {
      // 播放控制区 / 评论列表
    }
  2. 卡片级换肤 / 品牌卡片

    • 营销活动卡片、会员卡片、小程序入口等:
    WithTheme({ theme: new PageCustomTheme(new GreenColors()) }) {
      // 活动卡片 / 会员卡片布局
    }
  3. 表单区域统一风格

    • 一个复杂表单里用到 Button / TextInput / Checkbox / Slider 等:
    • 全部丢在 WithTheme 里,做一套专门的表单主题。
  4. 多主题 Demo / 设置页

    • 设置页里提供「主题预览」;
    • 上方一个切换按钮,下面用了多个 WithTheme 区块分别展示效果。

七、容易踩的点 & 调试建议

  1. 子组件只能一个

    • WithTheme 的子节点只能是一个组件;
    • 如果有多个,请用 Column/Row/自定义组件包一层。
  2. 不是所有组件都响应主题

    • 自绘组件(Canvas、Shape 等)不会自动跟主题;
    • 自定义组件如果内部没用系统控件,也看不到效果。
  3. 内部写死颜色会覆盖部分主题

    • 比如你在 Button 上手动设置了 backgroundColor('#FF0000')
    • 这可能会盖住主题里本来给它配置的一些颜色表现;
    • 建议:尽量用 buttonStylefontColor + 主题,让主题主导,而不是全部手写 Hex。
  4. 深浅色看起来没变化?

    • 检查是否已经配置 dark.json 等资源;
    • 检查是不是本身背景就接近黑/白,导致肉眼不明显;
    • 可以临时多放一些 Text / Button 观察效果。

八、总结

WithTheme 的定位可以一句话概括:

内外解耦:全局主题搞整体,WithTheme 专门做“局部换肤 + 局部深浅色”。

掌握它之后,你可以在 ArkUI 里轻松实现:

  • 某一块区域固定深色 / 浅色;
  • 某类卡片、一段区域统一走品牌主题色;
  • 在一个页面里同时展示多套主题效果,而不影响全局。