HarmonyOS 鸿蒙Next中@Styles装饰器

HarmonyOS 鸿蒙Next中@Styles装饰器 [ArkUI 声明式 UI 中,@Styles 装饰器如何实现样式与主题的动态绑定?](https://segmentfault.com/q/1010000047526849)

5 回复

【解决方案】

开发者您好,您这边是否是设置应用内主题换肤,希望@Styles 装饰器设置的涉及主题色的属性也会随主题动态变换,可以通过设置theme.color.***值来实现@Styles跟随主题动态变化

如:

[@Styles](/user/Styles)
  fancy() {
    .width('200')
    .backgroundColor(this.myTheme.colors?.backgroundEmphasize);
  }

完整项目代码参考官方示例:主题颜色自定义

将src/main/ets/pages/DisplayPage.ets替换为以下代码:

/*
 *
 *  * Copyright (c) 2025 Huawei Device Co., Ltd.
 *  * Licensed under the Apache License, Version 2.0 (the "License");
 *  * you may not use this file except in compliance with the License.
 *  * You may obtain a copy of the License at
 *  *
 *  *     http://www.apache.org/licenses/LICENSE-2.0
 *  *
 *  * Unless required by applicable law or agreed to in writing, software
 *  * distributed under the License is distributed on an "AS IS" BASIS,
 *  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 *  * See the License for the specific language governing permissions and
 *  * limitations under the License.
 *
 */

import { CustomTheme, PromptAction, window } from '@kit.ArkUI';
import { common, ConfigurationConstant } from '@kit.AbilityKit';
import { gAppTheme, gCustomTheme1 } from '../common/AppColors';
import { ThemeItem } from '../components/ThemeItem';
import { StyleConstants, TOGGLE_LIST } from '../constants/StyleConstants';
import { EyeProtectionMode } from '../util/EyeProtectionMode';


@Entry
@Component
export struct DisplayPage {
  @StorageProp('topRectHeight') topRectHeight: number = StyleConstants.ZERO;
  brightnessValue: number = StyleConstants.INITIAL_BRIGHTNESS;
  brightnessMax: number = StyleConstants.BRIGHTNESS_MAX;
  toggleList: boolean[] = TOGGLE_LIST;
  fontPrimary: ResourceColor | undefined = gAppTheme?.colors?.fontPrimary;
  @State myTheme: CustomTheme = gCustomTheme1;
  @State isCustomTheme: boolean = false;
  context = this.getUIContext().getHostContext() as common.UIAbilityContext;
  windowStage: window.WindowStage = AppStorage.get('windowStage') as window.WindowStage;
  mainWin: window.Window = this.windowStage.getMainWindowSync();
  promptAction: PromptAction = this.getUIContext().getPromptAction();

  aboutToAppear(): void {
    this.topRectHeight = this.getUIContext().px2vp(AppStorage.get('topRectHeight'));
    // 修改brightness即可改变屏幕亮度
    let brightness = this.brightnessValue / StyleConstants.BRIGHTNESS_MAX;
    this.mainWin.setWindowBrightness(brightness);
  }

  @Builder
  myBuilder(title: string, hasToggle: boolean, index: number, message?: string) {
    Column() {
      Row() {
        Text(title)
          .fontSize($r('app.float.font_size_18'))
          .fontColor(Color.Black)
          .layoutWeight(1);
        if (hasToggle) {
          Toggle({ type: ToggleType.Switch, isOn: this.toggleList[index] })
            .alignSelf(ItemAlign.Center)
            .width($r('app.float.toggle_width'))
            .height($r('app.float.toggle_height'))
            .onClick(() => {
              this.toggleList[index] = !this.toggleList[index];
              this.handleOnclick(index);
            });
        }
      }
      .width(StyleConstants.FULL_WIDTH);

      if (message) {
        Row() {
          Text(message)
            .width($r('app.float.message_width'))
            .fontSize($r('app.float.font_size_12'))
            .fontColor($r('app.color.message_color'));
        }
        .width(StyleConstants.FULL_WIDTH)
        .margin({
          top: $r('app.float.margin_5')
        });
      }
    }
    .padding({
      top: $r('app.float.margin_18'),
      bottom: $r('app.float.margin_16'),
      left: $r('app.float.margin_16'),
      right: $r('app.float.margin_16')
    });
  }

  @Builder
  myDivider() {
    Divider()
      .strokeWidth(StyleConstants.DIVIDER_STROKE_WIDTH)
      .width(StyleConstants.DIVIDER_WIDTH)
      .color($r('app.color.divider_color'));
  }
  [@Styles](/user/Styles)
  fancy() {
    .width('200')
    .backgroundColor(this.myTheme.colors?.backgroundEmphasize);
  }
  build() {
    WithTheme({ theme: this.myTheme }) {
      Column({ space: StyleConstants.INDEX_SPACE }) {
        Button('测试主题色').fancy()
          .fontColor(Color.Black)
        Row() {
          Text($r('app.string.theme'))
            .fancy()
            .fontSize($r('app.float.font_size_26'))
            .fontWeight(FontWeight.Bold)
            .fontColor(Color.Black)
        }
        .width(StyleConstants.FULL_WIDTH)
        .height($r('app.float.theme_height'))
        .alignItems(VerticalAlign.Bottom);

        Row() {
          ThemeItem({
            img: $r('app.media.img_system'),
            title: StyleConstants.SYSTEM_THEME,
            color: this.isCustomTheme ? $r('app.color.unselect_color') : $r('app.color.system_color'),
            radio: this.isCustomTheme ? $r('app.media.dot') : $r('app.media.system_dot')
          })
            .margin({
              right: $r('app.float.theme_between')
            })
            .onClick(() => {
              this.isCustomTheme = false;
              this.myTheme = gCustomTheme1;
            });
          ThemeItem({
            img: $r('app.media.img_custom'),
            title: StyleConstants.CUSTOM_THEME,
            color: this.isCustomTheme ? $r('app.color.custom_color') : $r('app.color.unselect_color'),
            radio: this.isCustomTheme ? $r('app.media.custom_dot') : $r('app.media.dot')
          })
            .onClick(() => {
              this.isCustomTheme = true;
              this.myTheme = gAppTheme;
            });
        }
        .width(StyleConstants.FULL_WIDTH)
        // .backgroundColor(Color.White)
        .borderRadius(StyleConstants.BORDER_RADIUS)
        .padding({
          top: $r('app.float.margin_16'),
          bottom: $r('app.float.margin_16'),
          left: $r('app.float.theme_left_padding'),
          right: $r('app.float.theme_right_padding')
        });

        Column() {
          this.myBuilder(StyleConstants.BRIGHTNESS, false, StyleConstants.NO_TOGGLE);
          Row() {
            Image($r('app.media.bright'))
              .width($r('app.float.img_bright_small_width'))
              .height($r('app.float.img_bright_small_height'));
            Slider({ value: this.brightnessValue, max: this.brightnessMax })
              .trackThickness($r('app.float.slider_track_thickness'))
              .blockBorderWidth(StyleConstants.BLOCK_BORDER_WIDTH)
              .blockBorderColor(this.isCustomTheme ? $r('app.color.custom_color') : $r('app.color.system_color'))
              .blockSize({
                width: $r('app.float.slider_block_size_width'),
                height: $r('app.float.slider_block_size_height'),
              })
              .layoutWeight(1)
              .onChange((value) => {
                this.brightnessValue = value;
                this.mainWin.setWindowBrightness(value / StyleConstants.BRIGHTNESS_MAX);
              });
            Image($r('app.media.bright'))
              .width($r('app.float.img_bright_big_width'))
              .height($r('app.float.img_bright_big_height'));
          }
          .width(StyleConstants.FULL_WIDTH)
          .padding({
            left: $r('app.float.margin_16'),
            right: $r('app.float.margin_16'),
            bottom: $r('app.float.margin_5')
          })
          .margin({
            top: $r('app.float.slider_top_margin')
          });

          this.myDivider();
          this.myBuilder(StyleConstants.AUTO_BRIGHTNESS_TITLE, true, StyleConstants.AUTO_BRIGHTNESS);
        }
        .width(StyleConstants.FULL_WIDTH)
        // .backgroundColor(Color.White)
        .borderRadius(StyleConstants.BORDER_RADIUS);

        Column() {
          this.myBuilder(StyleConstants.FULL_SCREEN_APPS, false, StyleConstants.NO_TOGGLE);
          this.myDivider();
          this.myBuilder(StyleConstants.EYE_PROTECTION_TITLE, true, StyleConstants.EYE_PROTECTION);
          this.myDivider();
          this.myBuilder(StyleConstants.ADAPTIVE_TINT_TITLE, true, StyleConstants.ADAPTIVE_TINT,
            StyleConstants.ADAPTIVE_TINT_MESSAGE);
        }
        .width(StyleConstants.FULL_WIDTH)
        // .backgroundColor(Color.White)
        .borderRadius(StyleConstants.BORDER_RADIUS);

        Column() {
          this.myBuilder(StyleConstants.DISABLE_COLOR_MODE_TITLE, true, StyleConstants.DISABLE_COLOR_MODE);
        }
        .width(StyleConstants.FULL_WIDTH)
        // .backgroundColor(Color.White)
        .borderRadius(StyleConstants.BORDER_RADIUS);
      }
      // .backgroundColor($r('app.color.custom_color'))
      .width(StyleConstants.FULL_WIDTH)
      .height(StyleConstants.FULL_HEIGHT)
      .padding({
        left: $r('app.float.margin_17'),
        right: $r('app.float.margin_17'),
        top: this.topRectHeight
      });
    };
  }

  handleOnclick(id: number) {
    switch (id) {
      case StyleConstants.EYE_PROTECTION:
        if (this.toggleList[StyleConstants.EYE_PROTECTION]) {
          EyeProtectionMode.getInstance().createSubWithEyeWindow(StyleConstants.EYE_PROTECTION_COLOR);
        } else {
          EyeProtectionMode.getInstance().removeSubWithEyeWindow();
        }
        break;
      case StyleConstants.DISABLE_COLOR_MODE:
        if (this.toggleList[StyleConstants.DISABLE_COLOR_MODE]) {
          this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_LIGHT);
        } else {
          this.context.getApplicationContext().setColorMode(ConfigurationConstant.ColorMode.COLOR_MODE_NOT_SET);
          this.promptAction.showToast({ message: $r('app.string.close_color_mode') });
        }
        break;
      case StyleConstants.AUTO_BRIGHTNESS:
        this.promptAction.showToast({ message: $r('app.string.only_display') });
        break;
      case StyleConstants.ADAPTIVE_TINT:
        this.promptAction.showToast({ message: $r('app.string.only_display') });
    }
  }
}

可以看到@Styles配置的属性能够跟随主题变化。

更多关于HarmonyOS 鸿蒙Next中@Styles装饰器的实战系列教程也可以访问 https://www.itying.com/category-93-b0.html


(如果你问的主题色配置)

配置主题色的时候,不是已经手动在 CustomColors 的实现类里面配置了颜色了嘛。

使用 ThemeControl.setDefaultTheme() 方法切换主题色的之后会触发自定义组件的 onWillApplyTheme() 生命周期回调。

可以通过其中的 theme 参数来获取当前主题色的色值。用一个状态变量接受之后就可以在其他地方用了。

参考:onWillApplyTheme-自定义组件的生命周期

可以参考文档 深色模式适配 - 颜色资源适配@Styles 内使用 $r("") 去引入对于的颜色资源即可实现。

@Styles装饰器

@Styles装饰器用于定义可复用的组件样式集合。它支持通用属性和特定组件属性,通过@Styles装饰的函数内部使用this参数访问组件状态。在组件内定义的@Styles仅限该组件使用,全局@Styles需在全局样式文件中定义。支持继承和参数传递,可通过构造函数参数实现样式定制化。

在HarmonyOS Next的ArkUI声明式UI框架中,@Styles装饰器主要用于定义可复用的组件样式集,但它本身并不直接提供与系统主题(如浅色/深色模式)的动态绑定机制。@Styles定义的是静态样式规则。

要实现样式与主题的动态绑定,需要结合资源管理状态管理

  1. 核心方法:使用资源引用与@State

    • resources/base/element/resources/dark/element/等目录下,分别定义不同主题下的颜色、尺寸等资源值(如color.json)。
    • 在组件中使用$r('app.color.my_text_color')等形式引用这些资源。系统会根据当前主题自动匹配对应的资源值。
    • 对于需要运行时动态切换的样式(非系统主题切换),可以结合@State装饰的状态变量。在@Styles定义的样式中,可以使用this.状态变量名来引用动态值。
  2. 示例:动态颜色绑定

    // 在@Styles中引用状态变量
    @Styles function customStyle() {
      .backgroundColor(this.myDynamicColor) // this指向组件实例
      .width(100)
      .height(100)
    }
    
    @Entry
    @Component
    struct Index {
      @State myDynamicColor: Resource = $r('app.color.primary_color') // 初始值引用资源
    
      build() {
        Row() {
          // 应用样式,颜色可随myDynamicColor改变而动态更新
          Text('Hello')
            .customStyle()
        }
        .onClick(() => {
          // 点击切换颜色,触发UI更新
          this.myDynamicColor = $r('app.color.secondary_color')
        })
      }
    }
    
  3. 系统主题监听 若要响应系统主题(如深色模式)切换,需使用configuration模块的onConfigurationUpdate回调,在主题变化时更新相关的状态变量或资源引用。

总结@Styles负责封装样式规则,而动态绑定依赖于:

  • 资源系统:实现跨主题的静态资源适配。
  • 状态变量(如@State:将动态值传入@Styles
  • 配置监听:响应系统级主题变化。

因此,样式与主题的动态绑定是一个组合使用资源引用、状态管理和@Styles封装的过程,而非由@Styles独立完成。

回到顶部