【HarmonyOS NEXT】自定义样式复用

一、背景

在鸿蒙开发中,样式复用是提升开发效率,保证UI一致性的核心需求。常见的实现方式有@Styles@ExtendstateStyles,但面对多组件适配、多状态统一管理、跨模块复用场景时,官方更推荐使用AttributeModifier实现灵活可扩展的样式复用方案。

二、传统样式复用方案

2.1、@Styles装饰器:组件内/全局静态样式复用

@Styles装饰器可以将多条样式设置提炼成一个方法,直接在组件声明的位置调用。

2.1.1 核心规则与限制

  1. 支持组件内定义全局定义两种形式:
    • 组件内@Styles可访问组件内部状态(如this.heightValue),仅当前组件可用;
    • 全局@Styles不可访问组件状态,可在整个项目中复用。
  2. 不支持参数传递,样式固定,无法动态调整(如不能通过传参修改背景色、宽高等);
  3. 不支持导出 / 导入,全局@Styles虽可跨组件使用,但无法通过export/import在不同模块(如 Entry、HAR)间复用;
  4. 支持关联组件事件(如onClick),但事件逻辑与样式耦合,复用性受限。2.1.2、使用场景

适用于项目内样式固定、无动态调整需求的场景

// 定义在全局的@Styles封装的样式
@Styles
function globalFancy () {
  .width(150)
  .height(100)
  .backgroundColor(Color.Pink)
}

@Entry
@Component
struct FancyUse {
  @State heightValue: number = 100;
  // 定义在组件内的@Styles封装的样式
  @Styles fancy() {
    .width(200)
    .height(this.heightValue)
    .backgroundColor(Color.Yellow)
    .onClick(() => {
      this.heightValue = 200;
    })
  }

  build() {
    Column({ space: 10 }) {
      // 使用全局的@Styles封装的样式
      Text('FancyA')
        .globalFancy()
        .fontSize(30)
      // 使用组件内的@Styles封装的样式
      Text('FancyB')
        .fancy()
        .fontSize(30)
    }
  }
}

2.2、@Extend装饰器:组件专属的参数化样式复用

@Extend,用于扩展组件样式,支持参数传递,是@Styles的增强版,仅适用于单个组件的个性化复用

2.2.1、使用规则与限制

  1. 仅支持全局定义,不允许在组件内部定义;
  2. 支持参数传递(如颜色、尺寸、权重等),可动态调整样式;
  3. 与组件强绑定,仅能被目标组件使用(如@Extend(Text)定义的样式,只能用于 Text 组件);
  4. 不支持导出 / 导入,无法跨模块(如 HAR 包)复用;
  5. 支持结合stateStyles实现多态样式(如按压态、焦点态)

2.2.2、使用场景

适用于单个组件需要动态调整样式的场景(如不同权重的文本、不同颜色的按钮、带参数的图标样式)

@Extend(Text)
function fancyText(weightValue: number, color: Color) {
  .fontStyle(FontStyle.Italic)
  .fontWeight(weightValue)
  .backgroundColor(color)
}

@Entry
@Component
struct Index {
  @State label: string = 'Hello World';

  build() {
    Row({ space: 10 }) {
      Text(`${this.label}`)
        .fancyText(100, Color.Blue)
      Text(`${this.label}`)
        .fancyText(200, Color.Pink)
      Text(`${this.label}`)
        .fancyText(300, Color.Orange)
    }.margin('20%')
  }
}

2.3、stateStyles:多态样式

stateStyles是组件的属性方法,可根据组件内部状态(如按压、焦点、禁用)动态切换样式,类似于 CSS 伪类,适用于状态关联的样式复用。

2.3.1 支持的组件状态

ArkUI 提供 6 种核心状态,覆盖大部分交互场景:

状态名说明
normal组件默认状态(无交互、无焦点)
pressed组件被按压时的状态
focused组件获得焦点时的状态(如 Tab 键选中)
disabled组件被禁用时的状态(enabled=false
selected组件被选中时的状态(如单选框选中)
active组件处于激活态时的状态(如滑动组件激活)

2.3.2 核心规则与限制

  1. 仅支持在组件内部直接使用,或结合@Extend全局复用,不支持单独导出;
  2. 状态样式与组件强绑定,无法跨组件复用(如 Button 的stateStyles不能直接用于 Text);
  3. 不支持参数传递,样式需硬编码或引用全局资源。

    2.3.1、使用场景

    适用于组件需要根据交互状态切换样式的场景(如按钮按压变色、输入框获焦边框高亮、禁用态灰色显示)
    @Entry
    @Component
    struct StateStylesSample {
      build() {
        Column() {
          Button('Button1')
            .stateStyles({
              focused: {
                .backgroundColor('#ffffeef0')
              },
              pressed: {
                .backgroundColor('#ff707070')
              },
              normal: {
                .backgroundColor('#ff2787d9')
              }
            })
            .margin(20)
          Button('Button2')
            .stateStyles({
              focused: {
                .backgroundColor('#ffffeef0')
              },
              pressed: {
                .backgroundColor('#ff707070')
              },
              normal: {
                .backgroundColor('#ff2787d9')
              }
            })
        }.margin('30%')
      }
    }
    @Extend与stateStyles一起使用
    @Extend(Text)
    function bottomButtonStyle(btnBgColor: ResourceColor, enable: boolean, disableBgColor?: ResourceColor) {
      .width('100%')
      .borderRadius(8)
      .fontColor(Color.White)
      .fontSize(18)
      .enabled(enable)
      .textAlign(TextAlign.Center)
      .height('100%')
      .stateStyles({
        normal: {
          .backgroundColor(btnBgColor)
        },
        pressed: {
          .backgroundColor(disableBgColor ?? btnBgColor)
        }
      })
      .fontWeight(FontWeight.Medium)
    }
    
    @Entry
    @Component
    struct Index {
      build() {
        Row({ space: 10 }) {
          Text('hello word')
            .bottomButtonStyle('#0165b8', true, '#ff707070')
        }.margin('20%')
      }
    }

    三、AttributeModifier:通用样式复用方案

    AttributeModifier是鸿蒙提供的属性修改器接口,可将多组件、多状态的样式封装为独立类,支持配置化传递、跨模块复用,是复杂场景的最优解。

    3.1 实现原理

    通过实现AttributeModifier<T>接口(T为目标组件的属性类型,如ButtonAttributeTextAttribute),将不同状态的样式封装在对应方法中,最终通过组件的attributeModifier()方法应用样式。核心是 “样式与组件解耦、配置化动态传递”。

    3.2、接口定义

    declare interface AttributeModifier<T> {
      applyNormalAttribute?(instance: T): void; //默认态
      applyPressedAttribute?(instance: T): void; //按压态
      applyFocusedAttribute?(instance: T): void; //焦点态
      applyDisabledAttribute?(instance: T): void; //禁用态
      applySelectedAttribute?(instance: T): void; //选择态
    }

    3.3、开发步骤

    步骤 1:定义通用样式接口(可选,优化配置传递)

    定义统一接口规范样式字段,支持配置化传递(避免硬编码,提升灵活性)。

    实际开发过程中,可以替换为真实的数据接口

    export interface GlobalStyle {
      width?: number | string;
      height?: number | string;
      bgColor?: string;
      pressedBgColor?: string;
      disabledBgColor?: string;
      textColor?: string;
      disabledTextColor?: string;
      fontSize?: number;
      borderRadius?: number;
    }
    步骤 2:实现 AttributeModifier 接口

    封装多状态样式,支持从外部接收配置参数(如GlobalStyle对象),动态调整样式。

    import { GlobalStyle } from '../model/styleModel';
    
    export class ButtonStyleModifier implements AttributeModifier<ButtonAttribute> {
      private style: Required<GlobalStyle>; // 用 Required 确保所有字段都有值(无 undefined)
    
      // 构造函数:参数默认值设为 {},手动合并默认值与外部配置
      constructor(style: GlobalStyle = {}) {
        // 核心:逐个字段赋值,用 ?? 兜底默认值(无 ... 运算符)
        this.style = {
          width: style.width ?? '100%', // 默认宽度:占满父容器
          height: style.height ?? 44,    // 默认高度:常用按钮高度
          bgColor: style.bgColor ?? '#1890ff', // 默认主题蓝
          pressedBgColor: style.pressedBgColor ?? '#096dd9', // 按压加深色
          disabledBgColor: style.disabledBgColor ?? '#cccccc', // 默认禁用背景
          textColor: style.textColor ?? '#ffffff', // 默认白色文字
          disabledTextColor: style.disabledTextColor ?? '#999999', // 默认禁用文字色
          fontSize: style.fontSize ?? 16, // 默认字号 16px
          borderRadius: style.borderRadius ?? 8 // 默认圆角 8px
        };
      }
    
      // 默认态:直接使用 this.style(已确保所有字段有值,无需非空断言)
      applyNormalAttribute(instance: ButtonAttribute): void {
        instance
          .width(this.style.width)
          .height(this.style.height)
          .backgroundColor(this.style.bgColor)
          .fontColor(this.style.textColor)
          .fontSize(this.style.fontSize)
          .borderRadius(this.style.borderRadius);
      }
    
      // 按压态:使用已兜底的 pressedBgColor
      applyPressedAttribute(instance: ButtonAttribute): void {
        instance.backgroundColor(this.style.pressedBgColor);
      }
    
      // 禁用态:支持自定义禁用色,无则用默认
      applyDisabledAttribute(instance: ButtonAttribute): void {
        instance
          .backgroundColor(this.style.disabledBgColor)
          .fontColor(this.style.disabledTextColor);
      }
    }
    步骤 3:组件中应用修饰器

    创建AttributeModifier实例,传递自定义样式配置,应用到目标组件。

    import { GlobalStyle } from '../model/styleModel';
    import { ButtonStyleModifier } from '../utils/MyButtonModifier';
    
    @Entry
    @Component
    struct Index {
      // 自定义样式:仅传需要修改的字段,其他用默认
      private submitBtnStyle: GlobalStyle = {
        width: 180,
        height: 48,
        bgColor: '#00b42a',
        pressedBgColor: '#00871c',
        fontSize: 18
      };
    
      // 自定义禁用态样式(示例)
      private disabledBtnStyle: GlobalStyle = {
        bgColor: '#1890ff',
        disabledBgColor: '#87ceeb', // 自定义禁用背景(天蓝色)
        disabledTextColor: '#ffffff' // 自定义禁用文字(白色)
      };
    
      build() {
        Column({ space: 12 }) {
          // 1. 默认样式按钮:无需传参,使用全部默认值
          Button('默认样式按钮')
            .attributeModifier(new ButtonStyleModifier())
    
          // 2. 自定义提交按钮:传递部分字段,覆盖默认值
          Button('自定义提交按钮')
            .attributeModifier(new ButtonStyleModifier(this.submitBtnStyle))
    
          // 3. 禁用按钮:传递自定义禁用态样式
          Button('禁用按钮')
            .attributeModifier(new ButtonStyleModifier(this.disabledBtnStyle))
            .enabled(false)
        }.padding(20)
      }
    }

    四、四种样式复用方案对比与选择建议

    对比维度@Styles@ExtendstateStylesAttributeModifier
    定义范围组件内 / 全局仅全局组件内 / 结合 @Extend 全局全局(支持导出)
    支持参数传递不支持支持不支持支持(配置化传递)
    跨组件复用全局可跨组件仅目标组件可用组件专属支持多组件(泛型适配)
    跨模块复用(导出 / 导入)不支持不支持不支持支持(HAR/Entry 间)
    多状态支持(按压 / 禁用等)需手动绑定事件可结合 stateStyles原生支持接口原生支持
    样式动态调整仅组件内可关联状态参数化动态调整硬编码 / 资源引用配置化动态调整
    ✅ 选择建议

    简单静态样式复用(如固定标题、卡片容器)单个组件动态样式(如不同颜色 / 尺寸的文本、按钮)组件多状态切换(如按压变色、禁用态)

    复杂场景复用(多组件适配、跨模块复用、配置化样式)

    多端适配 / 分层架构项目(如 Entry+HAR 分层)

    评论
    成就一亿技术人!
    拼手气红包6.0元
    还能输入1000个字符
     
    红包 添加红包
    表情包 插入表情
     条评论被折叠 查看
    添加红包

    请填写红包祝福语或标题

    红包个数最小为10个

    红包金额最低5元

    当前余额3.43前往充值 >
    需支付:10.00
    成就一亿技术人!
    领取后你会自动成为博主和红包主的粉丝 规则
    hope_wisdom
    发出的红包
    实付
    使用余额支付
    点击重新获取
    扫码支付
    钱包余额 0

    抵扣说明:

    1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
    2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

    余额充值