当前位置: 首页 > news >正文

OpenHarmony 项目统一全局样式、尺寸、色彩主题封装 ThemeUtil(API23)

摘要

项目中文字大小、颜色、圆角、间距、按钮尺寸、阴影等样式值分散写在各个页面,出现多处硬编码 #007DFF、fontSize (18)、padding (12),后期产品统一换肤、调整主色调需要全局逐行修改,维护成本极高。封装全局主题常量 ThemeUtil,统一管理主色 / 辅助色 / 文字色、间距、圆角、字体大小、通用阴影,支持一键切换深色 / 浅色两套主题,所有页面、组件统一引用主题变量,一键全局换色。API23 优化资源常量响应式同步、主题状态联动 AppStorage,修复深色模式切换页面局部样式不刷新、颜色硬编码无法批量替换、组件样式重复冗余等问题。

关键词

OpenHarmony;ArkTS;主题封装;全局色彩;尺寸常量;深色浅色切换;样式统一;Theme

一、引言

1.1 原生硬编码样式开发痛点

  1. 颜色、字号、间距分散在上千行页面代码,改版换色需全局检索替换,极易遗漏;
  2. 浅色、深色两套页面颜色手动分别写,切换时大量判断分支;
  3. 无统一规范:有的页面间距 10,有的 12,UI 视觉割裂不统一;
  4. 按钮、卡片、弹窗圆角大小各处不一致,缺乏设计规范约束;
  5. 无法统一管理阴影、边框、分割线等通用样式;
  6. 新增页面需要反复复制颜色值,开发效率低。

1.2 主题分层结构

  1. 色彩体系:主色、辅助色、成功 / 警告 / 错误色、一级 / 二级 / 占位文字、分割线、背景色(区分深浅两套)
  2. 间距体系:小间距、标准间距、大间距、超大边距
  3. 圆角体系:小圆角、标准卡片圆角、大弹窗圆角、全圆角按钮
  4. 字号体系:标题、正文、辅助小字、提示文字
  5. 通用样式:卡片阴影、输入框边框、分割线

API23 主题相关升级

  1. 支持主题常量配合 GlobalStore 全局深色状态自动动态取值;
  2. 样式常量可全局缓存,减少重复创建资源对象;
  3. 组件 @Builder 样式提取复用,减少重复布局代码;
  4. 修复深色模式切换后部分组件颜色不实时刷新问题。

二、全局主题常量 model/theme.ets

ets

import GlobalStore from '../utils/global_store' import { GLOBAL_KEY } from './GlobalState' // 尺寸、圆角、间距 固定常量(深浅共用) export const SizeConstant = { // 间距 gapXs: 4, gapSm: 8, gapMd: 12, gapLg: 16, gapXl: 24, pagePadding: 12, // 圆角 radiusXs: 4, radiusSm: 8, radiusMd: 12, radiusLg: 16, radiusFull: 999, // 字号 fontTip: 12, fontAux: 14, fontMain: 16, fontSubTitle: 18, fontTitle: 22, fontBigTitle: 26, // 通用控件高度 btnHeightSm: 36, btnHeightMd: 44, inputHeight: 48 } // 浅色主题色彩 const LightColor = { primary: "#007DFF", success: "#00B42A", warning: "#FF7D00", danger: "#F53F3F", text1: "#1D2129", text2: "#4E5969", text3: "#86909C", textPlaceholder: "#C9CDD4", bgPage: "#F5F5F5", bgCard: "#FFFFFF", line: "#E5E6EB" } // 深色主题色彩 const DarkColor = { primary: "#36A3FF", success: "#37D05B", warning: "#FF9500", danger: "#FF5C5C", text1: "#F2F3F5", text2: "#C9CDD4", text3: "#86909C", textPlaceholder: "#4E5969", bgPage: "#121212", bgCard: "#1E1E1E", line: "#333333" } // 主题统一出口,自动判断深浅模式 export function ThemeColor() { const isDark = GlobalStore.get(GLOBAL_KEY.DARK_MODE) return isDark ? DarkColor : LightColor } // 通用卡片阴影样式 export function CardShadow() { const color = ThemeColor().line return { shadowColor: color, shadowBlur: 6, shadowOffsetX: 0, shadowOffsetY: 2 } }

三、主题工具辅助封装 utils/theme_util.ets

ets

import { SizeConstant, ThemeColor, CardShadow } from '../model/theme' class ThemeUtil { private static instance: ThemeUtil static getInstance() { if (!ThemeUtil.instance) ThemeUtil.instance = new ThemeUtil() return ThemeUtil.instance } // 获取当前整套色彩 getColor() { return ThemeColor() } // 获取尺寸常量 getSize() { return SizeConstant } // 获取卡片阴影 getCardShadow() { return CardShadow() } // 快速生成标准卡片通用修饰符 getCardCommon() { const size = this.getSize() const color = this.getColor() return { borderRadius: size.radiusMd, backgroundColor: color.bgCard, padding: size.gapMd, shadow: this.getCardShadow() } } } export default ThemeUtil.getInstance()

四、页面实战全局主题调用示例

4.1 笔记列表卡片页面完整使用

ets

import ThemeUtil from '../utils/theme_util' import RefreshListView from '../components/common/RefreshListView' import StateView, { PageState } from '../components/common/StateView' import { Note } from '../utils/rdb_util' @Entry @Component struct NoteListPage { @State pageState: PageState = PageState.LOADING @State noteList: Note[] = [] build() { const theme = ThemeUtil.getColor() const size = ThemeUtil.getSize() const cardStyle = ThemeUtil.getCardCommon() Column({ space: size.gapMd }) { Row() { Text("我的笔记") .fontSize(size.fontTitle) .fontColor(theme.text1) .layoutWeight(1) Button("新增笔记") .height(size.btnHeightMd) .borderRadius(size.radiusFull) .backgroundColor(theme.primary) } .width("95%") StateView({ state: this.pageState }) { RefreshListView({ list: this.noteList }) { (item: Note) => { Row() .width("100%") .padding(cardStyle.padding) .borderRadius(cardStyle.borderRadius) .backgroundColor(cardStyle.backgroundColor) .shadow(cardStyle.shadow) { Column({ space: size.gapXs }).layoutWeight(1) { Text(item.title) .fontSize(size.fontMain) .fontColor(theme.text1) Text(item.content) .fontSize(size.fontAux) .fontColor(theme.text3) } Button("删除") .height(size.btnHeightSm) .backgroundColor(theme.danger) .borderRadius(size.radiusSm) } } } } } .width("100%") .height("100%") .padding(size.pagePadding) .backgroundColor(theme.bgPage) } }

4.2 设置页深色模式切换实时变色

ets

import GlobalStore from '../utils/global_store' import ThemeUtil from '../utils/theme_util' @Entry @Component struct SettingPage { @State dark: boolean = false aboutToAppear() { this.dark = GlobalStore.get(GLOBAL_KEY.DARK_MODE) } build() { const color = ThemeUtil.getColor() const size = ThemeUtil.getSize() Column() .width("100%") .height("100%") .padding(size.pagePadding) .backgroundColor(color.bgPage) { Row() .width("100%") .padding(size.gapLg) .borderRadius(size.radiusMd) .backgroundColor(color.bgCard) { Text("深色模式") .fontSize(size.fontMain) .fontColor(color.text1) .layoutWeight(1) Toggle({ isOn: this.dark }) .onChange((v) => GlobalStore.toggleDarkMode(v)) } } } }

五、通用业务组件规范改造(StateView 示例)

ets

@Component export struct StateView { @Param state: PageState = PageState.LOADING @BuilderParam contentBuilder: () => void @Param onRetry: () => void @State rotateAngle: number = 0 build() { const color = ThemeUtil.getColor() const size = ThemeUtil.getSize() Column() { if (this.state === PageState.LOADING) { Column({ space: size.gapMd }) { Text("⟳").fontSize(42).rotate({ angle: this.rotateAngle }) Text("加载中...") .fontSize(size.fontAux) .fontColor(color.text3) } } else if (this.state === PageState.EMPTY) { Column({ space: size.gapMd }) { Image($r("sys.media.ohos_ic_public_empty")).width(80).fillColor(color.text3) Text("暂无数据") .fontSize(size.fontAux) .fontColor(color.text3) } } else if (this.state === PageState.ERROR) { Column({ space: size.gapLg }) { Image($r("sys.media.ohos_ic_public_fail")).width(80).fillColor(color.text3) Text("加载失败,请点击重试") .fontSize(size.fontAux) .fontColor(color.text3) Button("重试") .width(140) .height(size.btnHeightMd) .backgroundColor(color.primary) .borderRadius(size.radiusSm) .onClick(this.onRetry) } } else { this.contentBuilder() } } .width("100%") .layoutWeight(1) } }

六、主题开发编码规范

6.1 常量分层规范

  1. 尺寸 / 圆角 / 字号:写入 SizeConstant,深浅模式共用固定数值,统一设计规范;
  2. 色彩:分 LightColor / DarkColor 两套,通过 ThemeColor () 动态获取;
  3. 复合样式:卡片阴影、通用卡片修饰符封装成函数,多处复用。

6.2 页面使用规范

  1. 页面 build 顶部统一解构const color = ThemeUtil.getColor(); const size = ThemeUtil.getSize()
  2. 禁止页面直接写 #xxxxxx、12、16 等硬编码数字与色值;
  3. 页面背景、卡片背景、文字颜色全部使用主题变量,深浅自动切换。

6.3 换肤维护规范

  1. 产品改版仅修改 theme.ets 内主色值,全局页面自动同步;
  2. 新增业务色(如会员紫、积分橙)统一在 Light/Dark 两套色彩同步新增;
  3. 统一设计尺寸修改仅调整 SizeConstant 一处,全部页面间距、字号同步变化。

6.4 深色模式联动规范

  1. ThemeColor () 内部读取 GlobalStore 深色全局状态,无需页面额外判断;
  2. 切换 Toggle 修改全局 darkMode 后,页面 @State 自动刷新,颜色实时切换;
  3. 弹窗、列表、按钮等公共组件全部接入主题,深浅无视觉断层。

6.5 组件复用规范

  1. 所有公共基础组件(StateView、RefreshListView、Dialog 弹窗)全部接入 ThemeUtil;
  2. 禁止组件内部写死颜色、尺寸,完全依赖主题入参。

七、高频问题解决方案

问题 1:切换深色模式页面颜色不刷新 解决:页面 UI 依赖 ThemeColor () 动态函数取值,搭配 GlobalStore 响应式状态,自动触发重绘。

问题 2:改版主色调需要修改上百处代码 解决:全部页面引用主题常量,仅修改 theme.ets 中 primary 字段全局生效。

问题 3:各页面卡片圆角、间距大小不统一 解决:强制所有页面使用 SizeConstant 内固定尺寸,禁止手写数字。

问题 4:深色模式分割线、背景发白刺眼 解决:分开维护 LightColor 与 DarkColor 两套独立背景 / 分割线色值,不共用颜色。

问题 5:按钮高度、输入框尺寸到处不一致 解决:统一使用 btnHeightMd、inputHeight 尺寸常量约束控件高度。

八、总结

ThemeUtil 全局主题体系分离固定尺寸常量与深浅两套动态色彩,统一管控项目全部视觉规范,彻底消除颜色、字号、间距硬编码,实现一键全局换肤、深浅模式自动适配。 所有页面、公共 UI 组件统一接入主题工具,和前文 GlobalStore 全局深色状态联动,一套规范贯穿整个 HAR/HSP 分层工程,极大降低 UI 改版、设计规范调整的维护成本,是标准化企业级鸿蒙项目必备视觉底层封装。

http://www.cnnetsun.cn/news/3563099.html

相关文章:

  • 如何在5分钟内用Shotcut制作专业视频:开源视频编辑的完整指南
  • 如何快速掌握TypeScript数据验证:Zod的完整实战指南
  • 如何用audioMotion-analyzer打造专业级音频可视化效果
  • Earthworm:如何通过游戏化英语学习系统让英语学习效率提升300%?
  • 深度解析SKkeeper:Blender形状键保留插件的技术实现
  • 无人机地面站终极指南:如何快速掌握QGroundControl核心功能
  • 终极指南:如何快速创建专属AI数字人?Duix-Avatar本地部署完全教程
  • GitHub Copilot SDK错误恢复:构建容错AI系统的10个关键策略
  • 从实验室到生产环境:AI输出可信度验证体系搭建(含NIST SP 800-218合规对照表)
  • 嵌入式视觉系统像素打包与DMA配置:原理、实践与优化
  • 嵌入式寄存器编程精要:从I2C原子操作到LCDC显示驱动实战
  • 数据科学家面试本质是可信度传递系统
  • 【lucene】impacts与帕累托最优
  • 数字孪生落地核心:数据契约、三层架构与时间同步
  • Minmea:嵌入式系统中的高效GPS NMEA解析库解决方案
  • 粉笔APP错题本功能全解析:不只是收集错题,更是智能复习系统
  • CocosCreator UI框架深度解析:5步掌握高效层级管理
  • OkoBot窃密框架实战分析:攻击链拆解、检测查杀与钱包防御指南
  • 50个Dify工作流模板:AI新手的终极自动化指南
  • 终极指南:如何用MemTorch框架加速忆阻器深度学习仿真
  • 如何用Obsidian-skills实现智能项目管理:3大突破性技能实战指南
  • Python 类型注解实战:让 mypy 在上线前帮你抓 bug
  • 解锁Linux下罗技设备的全部潜能:LogiOps终极配置指南 [特殊字符]
  • 如何用Video2X实现AI视频增强:从模糊到高清的终极实战指南
  • Mapbox Studio Classic完整指南:快速掌握专业地图设计终极方案
  • DataEase技术选型深度解析:从社区版到企业版的架构演进路径
  • 终极指南:如何实现Blender与CAD软件的无缝协同工作
  • 如何让你的 AI Agent 拥有实时“看盘”能力?用 DeepSeek Function Calling 动态调用 QuantDash API 构建智能投研助理
  • 开源法律AI推理引擎:革命性技术赋能企业法务智能决策体系
  • 降AI工具会不会泄露文章?我上周踩了个实打实的坑