一、多态样式是什么?为啥要用它?

想象一下你手机里的按钮 —— 没点的时候是蓝色,按下瞬间变橙色,禁用时变灰色,这种根据操作状态自动变样式的效果,就是多态样式的功劳。在 HarmonyOS 里,多态样式能让组件在不同状态下(比如按下、选中、禁用)自动切换样式,不用手动写一堆条件判断,开发效率蹭蹭涨,用户体验也更流畅。

从 API version 8 开始,HarmonyOS 就支持了多态样式,后来到 API version 11 又新增了 attributeModifier 写法,灵活性更强。不过要注意哦,多态样式只支持通用属性,像 TextInput 的 backgroundColor 这种私有属性就不管用,这时候就得用 attributeModifier 来救场啦。

重点总结:

  • 多态样式能让组件根据状态自动变样式,提升交互体验
  • 从 API 8 开始支持 stateStyles,API 11 新增 attributeModifier
  • 只支持通用属性,私有属性需用 attributeModifier 处理

二、核心概念:stateStyles 和 StateStyles

(一)stateStyles:给组件穿 "状态外衣"

stateStyles 就像给组件穿衣服的指令,你可以用它设置组件在不同状态下的样式。它的用法很简单,直接调用 stateStyles (value: StateStyles),其中 value 就是各种状态的样式配置。

// 基础用法示例
Text("按钮")
  .stateStyles({
    normal: () => { /* 正常状态样式 */ },
    pressed: () => { /* 按下状态样式 */ }
  })

(二)StateStyles:定义各种状态的 "颜值"

StateStyles 是具体的状态样式集合,支持这些状态:

  • normal:组件没任何状态时的样子
  • pressed:手指按下组件时的状态
  • disabled:组件禁用时的灰掉状态
  • focused:组件获焦时的高亮状态
  • clicked:点击后的反馈状态
  • selected(API 10+):组件选中时的状态(比如单选框选中)

重点总结:

  1. stateStyles 是设置状态样式的入口方法
  2. StateStyles 支持 6 种状态(normal/pressed/disabled/focused/clicked/selected)
  3. selected 状态从 API 10 开始支持,依赖组件的选中属性

三、selected 选中状态详解

选中状态是很多交互场景的刚需,比如单选框、复选框选中时要变色。但 selected 状态得配合组件的选中属性用,你可以用 onClick 修改属性值,或者用 $$ 双向绑定自动同步。

目前支持 selected 状态的组件有这些:

组件 支持的属性 起始 API
Checkbox select 10
CheckboxGroup selectAll 10
Radio checked 10
Toggle isOn 10
ListItem selected 10
GridItem selected 10
MenuItem selected 10

选中状态使用注意

  • 必须通过修改组件的选中属性来触发样式变化
  • 双向绑定 $$ 能自动同步选中状态和样式
  • 不同组件的选中属性名不同,别搞混啦

重点总结:

  1. selected 状态需配合组件的选中属性使用
  2. 支持 Checkbox/Radio 等 7 种组件
  3. 可通过 onClick 或 $$ 双向绑定控制选中状态

四、pressed 和 clicked 状态的 "爱恨情仇"

这两个状态都是点击相关的,但同时用的时候有个坑:后注册的状态会覆盖先注册的。比如你先写 pressed 再写 clicked,那只有 clicked 生效,反过来则 pressed 生效。

为啥会这样?因为组件的点击事件处理顺序是按注册顺序来的,后注册的会覆盖前面的逻辑。所以用的时候得注意顺序,或者只选一个适合的状态。

重点总结:

  • pressed 和 clicked 同时使用时,后注册的状态生效
  • 根据交互需求选择合适的状态,避免冲突

五、实战示例:从 Text 到 Builder 的多态样式应用

示例 1:Text 组件的状态变装秀

这个示例展示 Text 组件在 normal、pressed、disabled 三种状态下的样式变化。点击 "control disabled" 按钮能切换组件的启用状态,看看样式怎么变:

@Entry
@Component
struct StyleExample {
  @State isEnable: boolean = true
  
  // 定义按下状态样式
  @Styles pressedStyles(): void {
    .backgroundColor("#ED6F21")
    .borderRadius(10)
    .borderStyle(BorderStyle.Dashed)
    .borderWidth(2)
    .borderColor("#33000000")
    .width(120)
    .height(30)
    .opacity(1)
  }
  
  // 定义禁用状态样式
  @Styles disabledStyles(): void {
    .backgroundColor("#E5E5E5")
    .borderRadius(10)
    .borderStyle(BorderStyle.Solid)
    .borderWidth(2)
    .borderColor("#2a4c1919")
    .width(90)
    .height(25)
    .opacity(1)
  }
  
  // 定义正常状态样式
  @Styles normalStyles(): void {
    .backgroundColor("#0A59F7")
    .borderRadius(10)
    .borderStyle(BorderStyle.Solid)
    .borderWidth(2)
    .borderColor("#33000000")
    .width(100)
    .height(25)
    .opacity(1)
  }
  
  build() {
    Flex({ direction: FlexDirection.Column, alignItems: ItemAlign.Center }) {
      // normal状态的Text
      Text("normal")
        .fontSize(14)
        .fontColor(Color.White)
        .opacity(0.5)
        .stateStyles({
          normal: this.normalStyles,
        })
        .margin({ bottom: 20 })
        .textAlign(TextAlign.Center)
      
      // pressed状态的Text
      Text("pressed")
        .backgroundColor("#0A59F7")
        .borderRadius(20)
        .borderStyle(BorderStyle.Dotted)
        .borderWidth(2)
        .borderColor(Color.Red)
        .width(100)
        .height(25)
        .opacity(1)
        .fontSize(14)
        .fontColor(Color.White)
        .stateStyles({
          pressed: this.pressedStyles,
        })
        .margin({ bottom: 20 })
        .textAlign(TextAlign.Center)
      
      // 带启用状态控制的Text
      Text(this.isEnable ? "effective" : "disabled")
        .backgroundColor("#0A59F7")
        .borderRadius(20)
        .borderStyle(BorderStyle.Solid)
        .borderWidth(2)
        .borderColor(Color.Gray)
        .width(100)
        .height(25)
        .opacity(1)
        .fontSize(14)
        .fontColor(Color.White)
        .enabled(this.isEnable)
        .stateStyles({
          disabled: this.disabledStyles,
        })
        .textAlign(TextAlign.Center)
      
      // 控制按钮
      Text("control disabled")
        .onClick(() => {
          this.isEnable = !this.isEnable
          console.log(`${this.isEnable}`)
        })
    }
    .width(350).height(300)
  }
}

示例 1 重点:

  1. 用 @Styles 装饰器定义样式方法
  2. 通过 stateStyles ({状态:样式方法}) 绑定状态样式
  3. enabled (this.isEnable) 控制组件的启用状态,触发 disabled 样式

示例 2:Radio 组件的选中状态变身

这个示例展示 Radio 组件在选中和未选中时的样式变化,两个 Radio 分别用了不同的状态绑定方式:

@Entry
@Component
struct Index {
  @State value: boolean = false       // 普通状态绑定
  @State value2: boolean = false      // 双向绑定状态
  
  // 未选中状态样式
  @Styles normalStyles(): void {
    .backgroundColor("#E5E5E1")
  }
  
  // 选中状态样式
  @Styles selectStyles(): void {
    .backgroundColor("#ED6F21")
    .borderWidth(2)
  }
  
  build() {
    Flex({ direction: FlexDirection.Row, justifyContent: FlexAlign.Center, alignItems: ItemAlign.Center }) {
      Column() {
        Text('Radio1')
          .fontSize(25)
        Radio({ value: 'Radio1', group: 'radioGroup1' })
          .checked(this.value)          // 普通状态绑定
          .height(50)
          .width(50)
          .borderWidth(0)
          .borderRadius(30)
          .onClick(() => {
            this.value = !this.value    // 手动切换状态
          })
          .stateStyles({
            normal: this.normalStyles,
            selected: this.selectStyles,
          })
      }
      .margin(30)
      
      Column() {
        Text('Radio2')
          .fontSize(25)
        Radio({ value: 'Radio2', group: 'radioGroup2' })
          .checked($$this.value2)       // 双向绑定状态
          .height(50)
          .width(50)
          .borderWidth(0)
          .borderRadius(30)
          .stateStyles({
            normal: this.normalStyles,
            selected: this.selectStyles,
          })
      }
      .margin(30)
    }.padding({ top: 30 })
  }
}

示例 2 重点:

  1. Radio 组件通过 checked 属性控制选中状态
  2. 展示了两种状态绑定方式:普通绑定 (this.value) 和双向绑定 ($$this.value2)
  3. selected 状态样式在选中时自动应用

示例 3:Builder 组件的多态样式 "曲线救国"

Builder 组件有点特殊,因为它没有独立的自定义父节点,直接用多态样式不生效。这时候得把样式封装到自定义组件里,再放到 Builder 中:

import { ComponentContent } from '@kit.ArkUI';
import { BusinessError } from '@kit.BasicServicesKit';

// 自定义组件,封装多态样式
@Component
struct Child {
  build() {
    Row()
      .zIndex(10)
      .width(100)
      .height(200)
      .stateStyles({
        normal: {
          .backgroundColor(Color.Blue)
        },
        pressed: {
          .backgroundColor(Color.Black)
        }
      })
  }
}

// Builder函数,引用自定义组件
@Builder
function buildText() {
  Child()
}

@Entry
@Component
struct Index {
  private contentNode: ComponentContent<Object> =
    new ComponentContent(this.getUIContext(), wrapBuilder(buildText));
    
  build() {
    Button().onClick((event: ClickEvent) => {
      this.getUIContext()
        .getPromptAction()
        .openCustomDialog(this.contentNode)
        .then(() => {
          console.info('OpenCustomDialog complete.')
        })
        .catch((error: BusinessError) => {
          let message = (error as BusinessError).message;
          let code = (error as BusinessError).code;
          console.error(`OpenCustomDialog args error code is ${code}, message is ${message}`);
        })
    })
  }
}

示例 3 重点:

  1. Builder 组件不能直接用多态样式,需封装到自定义组件
  2. 通过 @Builder 定义组件构建函数
  3. 用 ComponentContent 和 openCustomDialog 展示 Builder 内容

六、进阶技巧:attributeModifier 的妙用

当多态样式不支持某些属性时(比如 TextInput 的 backgroundColor),可以用 attributeModifier 来动态设置属性。从 API version 11 开始支持,用法如下:

TextInput("输入框")
  .attributeModifier((attr) => {
    if (this.isFocused) {
      attr.backgroundColor = Color.Blue;
    } else {
      attr.backgroundColor = Color.White;
    }
  })

重点总结:

  • attributeModifier 可动态设置组件属性
  • 解决多态样式不支持私有属性的问题
  • API version 11 及以上可用

七、多态样式使用注意事项

  1. 状态优先级:pressed 和 clicked 同时存在时,后注册的生效
  2. Builder 限制:必须通过自定义组件间接使用多态样式
  3. 属性支持:只支持通用属性,私有属性用 attributeModifier
  4. API 版本:selected 状态从 API 10 开始,attributeModifier 从 API 11 开始
  5. 图片资源:样式中涉及图片时,确保路径正确且格式支持

八、总结:多态样式让交互体验更上一层楼

HarmonyOS 的多态样式就像给组件加了 "智能皮肤",不用写复杂的状态判断,就能让组件在不同操作下自动变样式。从简单的 Text 按钮到复杂的 Radio 组,再到特殊的 Builder 组件,多态样式都能搞定。

记住几个关键点:stateStyles 是入口,StateStyles 定义状态,selected 状态要配合组件属性,Builder 得用自定义组件绕个弯。遇到不支持的属性别慌,attributeModifier 来帮忙。

现在就试试给你的组件加上多态样式吧,让用户点击、选中的每一个操作都有丝滑的视觉反馈,交互体验直接拉满!

Logo

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

更多推荐