公司动态

HarmonyOS开发实战:笔友-@Extend/@Styles 样式复用与主题系统

📅 2026/7/25 13:53:24
HarmonyOS开发实战:笔友-@Extend/@Styles 样式复用与主题系统
前言在 ArkUI 声明式开发范式中样式复用是提升代码可维护性和 UI 一致性的关键手段。Styles和Extend两个装饰器分别从“通用样式集合“和“组件特定扩展“两个维度为开发者提供了样式复用的能力。本文将以开源鸿蒙笔友通信应用 xiexin 的Constants.ets中的AppColors设计令牌和各页面中的样式代码为蓝本详细剖析Styles/Extend的语法、使用场景以及它们如何与设计令牌Design Tokens配合构建可维护的样式系统。提示本文假设你已经了解 ArkUI 声明式开发的基本概念。如果还不熟悉建议先阅读前十一篇文章。一、样式复用的两个维度ArkUI 提供了两种样式复用机制分别解决不同的问题装饰器作用范围适用场景Styles通用样式集合跨组件共享的样式规则Extend特定组件扩展针对特定组件类型的样式扩展1.1 它们的区别// Styles通用样式不限定组件类型 Styles function CommonShadow() { .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) } // Extend特定组件样式限定 Text 组件 Extend(Text) function TitleText() { .fontSize(16) .fontColor(AppColors.TEXT_PRIMARY) .fontWeight(FontWeight.Medium) }二、xiexin 的样式现状2.1 当前样式组织方式xiexin 当前没有使用Styles或Extend而是通过直接属性调用和设计令牌来管理样式// Constants.ets — AppColors 设计令牌 export class AppColors { static readonly PRIMARY_BG: string #FAF6F0; static readonly SECONDARY_BG: string #F0EBE3; static readonly PAPER_BG: string #FFFDF7; static readonly PRIMARY: string #8B6914; static readonly SECONDARY: string #C4956A; static readonly TEXT_PRIMARY: string #2D2A26; static readonly TEXT_SECONDARY: string #7A746B; static readonly ACCENT: string #B8860B; static readonly WAITING: string #6B8E6B; static readonly DISABLED: string #C8C2B8; static readonly DIVIDER: string #E8E2D8; static readonly WHITE: string #FFFFFF; static readonly CARD_BG: string #FFFFFF; static readonly AMBER_LIGHT: string #FFF8E7; static readonly SUCCESS: string #4CAF50; static readonly AMBER_OPACITY: string #1A8B6914; }2.2 直接属性调用的弊端// 反例重复的样式代码 // PenPalCard Row() .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) // LetterCard Row() .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 })这两段代码几乎完全相同却散落在两个不同的Builder函数中。这就是Styles和Extend要解决的问题。三、Styles 装饰器通用样式集合3.1 Styles 的基本用法Styles装饰器用于定义一组通用的样式规则可以被多个组件共享。全局 StylesStyles function CardShadow() { .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) } Styles function TextPrimary() { .fontSize(16) .fontColor(AppColors.TEXT_PRIMARY) .fontWeight(FontWeight.Medium) }组件内 StylesComponent struct MyComponent { Styles CardStyle() { .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) } build() { Row() { /* ... */ } .CardStyle() // 调用组件内 Styles } }3.2 Styles 的核心特性特性说明不限定组件任何组件都可以调用不可使用参数Styles函数不能带参数不可使用状态不能访问this的State变量全局可导出全局Styles可以被export导出3.3 Styles 在 xiexin 中的应用示例如果 xiexin 使用Styles可以对重复的卡片样式进行提取// 全局 Styles 函数 Styles function CardBase() { .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) } Styles function CardShadow() { .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) } Styles function SmallChip() { .fontSize(11) .borderRadius(8) .padding({ left: 6, right: 6, top: 2, bottom: 2 }) }然后在组件中使用Builder PenPalCard(pal: PenPal) { Row() { // 笔友卡片内容 } .CardBase() .CardShadow() .onClick(() { router.pushUrl({ url: pages/PenPalDetailPage, params: { penPalId: pal.id } }) }) } Builder LetterCard(letter: Letter) { Row() { // 信件卡片内容 } .CardBase() .CardShadow() .onClick(() { router.pushUrl({ url: pages/ReadLetterPage, params: { letterId: letter.id } }) }) }提示Styles函数不能带参数适用于“统一所有卡片外观“的场景。如果不同卡片需要不同的边距或颜色应该使用Extend。四、Extend 装饰器特定组件扩展4.1 Extend 的基本用法Extend装饰器用于针对特定组件类型扩展样式。// 针对 Text 组件的扩展 Extend(Text) function TitleText() { .fontSize(16) .fontColor(AppColors.TEXT_PRIMARY) .fontWeight(FontWeight.Medium) } // 针对 Text 组件的扩展带参数 Extend(Text) function StatusText(color: string) { .fontSize(11) .fontColor(color) .fontWeight(FontWeight.Medium) .borderRadius(10) .padding({ left: 8, right: 8, top: 3, bottom: 3 }) }4.2 Extend 的核心特性特性说明组件限定只能针对特定组件类型定义支持参数可以带参数动态生成样式全局函数只能定义为全局函数链式调用多个Extend可以链式调用4.3 Extend 在 xiexin 中的应用示例xiexin 中重复次数最多的样式是Text组件的各种排版样式// 针对 Text 组件的扩展 Extend(Text) function TitleText() { .fontSize(16) .fontColor(AppColors.TEXT_PRIMARY) .fontWeight(FontWeight.Medium) } Extend(Text) function SubtitleText() { .fontSize(12) .fontColor(AppColors.TEXT_SECONDARY) } Extend(Text) function PrimaryButton() { .fontSize(14) .fontColor(AppColors.WHITE) .backgroundColor(AppColors.PRIMARY) .borderRadius(24) .height(40) .width(140) } // 针对 Row 组件的扩展 Extend(Row) function CardContainer() { .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) }在组件中使用// 使用 Extend 简化样式代码 Builder PenPalCard(pal: PenPal) { Row() { AvatarComponent({ name: pal.name, avatarSize: 48, fontSize: 20 }) .margin({ right: 14 }) Column({ space: 4 }) { Text(pal.name).TitleText() // 调用 Extend(Text) 扩展 Text(认识 ${pal.daysSinceMet} 天 · 通信 ${pal.totalLetters} 封) .SubtitleText() } .layoutWeight(1) } .CardContainer() // 调用 Extend(Row) 扩展 .onClick(() { router.pushUrl({ url: pages/PenPalDetailPage, params: { penPalId: pal.id } }) }) }五、Styles vs Extend 对比5.1 选择决策树需要复用的样式是 ├── 跨多种组件类型共享 → Styles │ └── 示例卡片阴影、圆角、背景色 └── 针对特定组件类型 → Extend └── 示例Text 字号、Button 形状5.2 对比表格维度StylesExtend组件限定否是参数支持否是作用范围全局/组件内全局调用方式链式调用链式调用可导出是是典型场景卡片阴影、基础布局Text 字号、Button 样式5.3 组合使用Styles和Extend可以组合使用实现更精细的样式管理// 基础布局样式跨组件 Styles function FlexCenter() { .justifyContent(FlexAlign.Center) .alignItems(HorizontalAlign.Center) } Styles function FullWidth() { .width(100%) } // 特定组件样式 Extend(Text) function TitleText() { .fontSize(16) .fontColor(AppColors.TEXT_PRIMARY) .fontWeight(FontWeight.Medium) } // 组合使用 Row() .FullWidth() .FlexCenter() .height(48)六、stateStyles 多态样式6.1 stateStyles 的基本用法stateStyles是 ArkUI 提供的多态样式机制允许组件在不同状态下应用不同的样式Entry Component struct Index { Styles normalStyle() { .backgroundColor(AppColors.CARD_BG) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) } Styles pressedStyle() { .backgroundColor(AppColors.SECONDARY_BG) .shadow({ radius: 3, color: #0A000000, offsetX: 0, offsetY: 1 }) } build() { Row() .stateStyles({ normal: this.normalStyle, pressed: this.pressedStyle, // focused: this.focusedStyle // 可选 }) } }6.2 stateStyles 在 xiexin 中的应用如果 xiexin 使用stateStyles卡片按压效果可以这样实现Styles cardNormal() { .backgroundColor(AppColors.CARD_BG) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) } Styles cardPressed() { .backgroundColor(AppColors.SECONDARY_BG) .shadow({ radius: 3, color: #0A000000, offsetX: 0, offsetY: 1 }) } Builder PenPalCard(pal: PenPal) { Row() { // 内容 } .CardContainer() .stateStyles({ normal: this.cardNormal, pressed: this.cardPressed, }) .onClick(() { router.pushUrl({ url: pages/PenPalDetailPage, params: { penPalId: pal.id } }) }) }提示stateStyles支持normal、pressed、focused三种状态。pressed状态在用户按下时触发focused状态在组件获得焦点时触发。七、AnimatableExtend 可动画属性扩展7.1 AnimatableExtend 的基本用法AnimatableExtend装饰器用于定义可动画的属性可以在动画过程中平滑过渡AnimatableExtend(Text) function AnimatableOpacity() { .opacity() } AnimatableExtend(Row) function AnimatableScale() { .scale() }7.2 AnimatableExtend 与动画的配合AnimatableExtend(Text) function AnimatableFontSize() { .fontSize() } Entry Component struct AnimatedText { State fontSize: number 16; build() { Column() { Text(Hello) .AnimatableFontSize(this.fontSize) Button(放大) .onClick(() { animateTo({ duration: 300 }, () { this.fontSize 32; }) }) } } }八、设计令牌与样式系统的整合8.1 设计令牌 Styles/Extend 三层架构将设计令牌、Styles、Extend三层整合可以构建完整的样式系统graph TD subgraph 第1层 设计令牌 C[AppColors 颜色常量] S[字号常量] M[间距常量] end subgraph 第2层 通用样式 ST[全局 Styles 函数] EX[全局 Extend 函数] end subgraph 第3层 组件样式 CP[组件内 Styles] DS[stateStyles 多态样式] end C -- ST C -- EX ST -- CP EX -- DS8.2 样式文件组织建议对于 xiexin 项目建议将样式代码按以下方式组织entry/src/main/ets/ ├── common/ │ ├── AppColors.ets # 设计令牌颜色 │ ├── Typography.ets # 设计令牌字号、行高 │ └── Spacing.ets # 设计令牌间距 ├── styles/ │ ├── GlobalStyles.ets # 全局 Styles 函数 │ ├── TextStyles.ets # Extend(Text) 扩展 │ └── LayoutStyles.ets # Extend(Row/Column) 扩展 └── pages/ ├── Index.ets # 组件内 Styles └── ... # 其他页面8.3 设计令牌 Extend 的完整示例// 第1层设计令牌 export class FontSizes { static readonly TITLE: number 16; static readonly BODY: number 14; static readonly CAPTION: number 12; static readonly SMALL: number 11; } // 第2层Extend 扩展 Extend(Text) function TitleText() { .fontSize(FontSizes.TITLE) .fontColor(AppColors.TEXT_PRIMARY) .fontWeight(FontWeight.Medium) } Extend(Text) function BodyText() { .fontSize(FontSizes.BODY) .fontColor(AppColors.TEXT_PRIMARY) } Extend(Text) function CaptionText() { .fontSize(FontSizes.CAPTION) .fontColor(AppColors.TEXT_SECONDARY) } // 第3层组件中使用 Builder PenPalCard(pal: PenPal) { Row() { Column({ space: 4 }) { Text(pal.name).TitleText() // 笔友名标题字号 Text(pal.signature).BodyText() // 签名正文字号 Text(认识 ${pal.daysSinceMet} 天).CaptionText() // 天数注解字号 } } }九、主题切换样式9.1 深色模式适配通过Styles和Extend配合 AppColors 设计令牌可以方便地实现主题切换// 浅色模式 const lightColors { PRIMARY_BG: #FAF6F0, TEXT_PRIMARY: #2D2A26, // ... }; // 深色模式 const darkColors { PRIMARY_BG: #1C1B1F, TEXT_PRIMARY: #E6E1E5, // ... }; // 根据主题切换设计令牌 Styles function CardBase() { .backgroundColor(AppColors.CARD_BG) .borderRadius(16) }9.2 运行时主题切换Entry Component struct MyApp { StorageProp(darkMode) darkMode: boolean false; Styles cardBase() { .backgroundColor(this.darkMode ? #2D2D2D : #FFFFFF) .borderRadius(16) } build() { Row() { // 卡片内容 } .cardBase() } }提示Styles虽然不能带参数但可以通过StorageProp访问全局状态实现主题切换样式。十、样式系统的性能优化10.1 Styles 与 Extend 的渲染开销Styles和Extend在编译时会被展开为普通的属性调用不会带来额外的运行时开销// 编译前 Row() .CardBase() .CardShadow() // 编译后等价 Row() .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 })10.2 避免过度抽象// 过度抽象仅仅封装了一个属性 Styles function Width100() { .width(100%) } // 合理的使用封装了多个属性的组合 Styles function CardContainer() { .width(100%) .padding(16) .backgroundColor(AppColors.CARD_BG) .borderRadius(16) .shadow({ radius: 6, color: #0A000000, offsetX: 0, offsetY: 2 }) }总结本文详细剖析了 HarmonyOS ArkUI 的Styles/Extend样式复用机制重点讲解了它们与设计令牌AppColors的配合使用以及如何通过stateStyles实现多态样式。理解样式复用机制的关键是把握“三层架构“设计令牌AppColors→ 通用样式Styles→ 组件特定扩展Extend。这三层各司其职共同构成了可维护的样式系统。下一篇文章我们将深入Observed和ObjectLink装饰器剖析 xiexin 中如何通过嵌套对象响应式追踪实现高效的状态更新。如果这篇文章对你有帮助欢迎点赞、收藏⭐、关注你的支持是我持续创作的动力相关资源开源鸿蒙跨平台社区https://openharmonycrossplatform.csdn.netHarmonyOS Styles 装饰器https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-styleHarmonyOS Extend 装饰器https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-extendHarmonyOS stateStyles 多态样式https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-statestylesHarmonyOS AnimatableExtend 装饰器https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-animatable-extendHarmonyOS 资源分类与访问https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/resource-categories-and-accessHarmonyOS 深色模式适配https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-dark-light-adaptationHarmonyOS 组件扩展概述https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-extend-components-overviewHarmonyOS 自定义组件复用https://developer.huawei.com/consumer/cn/doc/harmonyos-guides/arkts-component-reusable