一、应用概述调色板Color Picker是一款实用的颜色选择工具广泛应用于设计、绘画、主题定制等场景。本项目基于 HarmonyOS ArkTS 框架实现了一个功能完善的调色板应用包含 20 种预设颜色、RGB 三通道滑块、HEX 色值实时显示以及颜色预览区域。本文将从色彩模型原理、滑块联动算法、声明式 UI 设计、HarmonyOS 特性等角度进行全面深度分析。二、架构设计2.1 整体架构┌──────────────────────────────────────────┐ │ ColorPickerView.ets │ │ (主视图组合所有子组件) │ ├──────────────────────────────────────────┤ │ ┌─────────────┐ ┌─────────────────┐ │ │ │ ColorPreview │ │ PresetGrid │ │ │ │ (颜色预览) │ │ (20 预设色块) │ │ │ └─────────────┘ └─────────────────┘ │ │ ┌──────────────────────────────────┐ │ │ │ RGBSliderGroup │ │ │ │ (R 滑块 ────●─── 255) │ │ │ │ (G 滑块 ────●─── 255) │ │ │ │ (B 滑块 ────●─── 255) │ │ │ └──────────────────────────────────┘ │ │ ┌──────────────────────────────────┐ │ │ │ HexDisplay │ │ │ │ (#FF5733) │ │ │ └──────────────────────────────────┘ │ └──────────────────────────────────────────┘2.2 数据流预设点击 / 滑块拖动 → 更新 RGB 数值 → 更新 HEX 色值 → 更新预览区域背景色 → UI 响应式重渲染三、核心技术实现3.1 颜色数据模型// ColorModel.ets export class RGBColor { red: number; // 0-255 green: number; // 0-255 blue: number; // 0-255 constructor(red: number 0, green: number 0, blue: number 0) { this.red this.clamp(red); this.green this.clamp(green); this.blue this.clamp(blue); } // 值域限定 private clamp(value: number): number { return Math.max(0, Math.min(255, Math.round(value))); } // RGB → HEX 转换 toHex(): string { const r this.red.toString(16).padStart(2, 0); const g this.green.toString(16).padStart(2, 0); const b this.blue.toString(16).padStart(2, 0); return #${r}${g}${b}.toUpperCase(); } // HEX → RGB 静态工厂 static fromHex(hex: string): RGBColor { const clean hex.replace(#, ); if (clean.length ! 6) return new RGBColor(0, 0, 0); const r parseInt(clean.substring(0, 2), 16); const g parseInt(clean.substring(2, 4), 16); const b parseInt(clean.substring(4, 6), 16); return new RGBColor(r, g, b); } // 获取 CSS 颜色字符串 toCssString(): string { return rgb(${this.red}, ${this.green}, ${this.blue}); } }3.2 预设颜色数据// PresetColors.ets export const PRESET_COLORS: RGBColor[] [ // 红色系 new RGBColor(255, 51, 51), // 亮红 new RGBColor(204, 0, 0), // 深红 new RGBColor(255, 102, 102), // 粉红 // 橙色系 new RGBColor(255, 153, 51), // 橙 new RGBColor(255, 204, 102), // 杏黄 // 黄色系 new RGBColor(255, 255, 51), // 亮黄 new RGBColor(255, 255, 204), // 乳黄 // 绿色系 new RGBColor(51, 204, 51), // 亮绿 new RGBColor(0, 153, 0), // 深绿 new RGBColor(102, 204, 102), // 草绿 // 蓝色系 new RGBColor(51, 102, 255), // 亮蓝 new RGBColor(0, 0, 204), // 深蓝 new RGBColor(102, 178, 255), // 天蓝 // 紫色系 new RGBColor(153, 51, 255), // 紫 new RGBColor(204, 153, 255), // 淡紫 // 中性色 new RGBColor(255, 255, 255), // 白 new RGBColor(204, 204, 204), // 浅灰 new RGBColor(128, 128, 128), // 中灰 new RGBColor(64, 64, 64), // 深灰 new RGBColor(0, 0, 0), // 黑 ];3.3 RGB 转 HEX 算法详解RGB 转 HEX 的本质是将十进制颜色值转换为十六进制表示RGB(255, 87, 51) → HEX(#FF5733) 转换过程 R 255 → 0xFF → FF G 87 → 0x57 → 57 B 51 → 0x33 → 33 拼接 → #FF5733十进制与十六进制的对应关系十进制十六进制十进制十六进制000128801610192C03220240F06440255FF10064转换公式十六进制字符串 decimal.toString(16).padStart(2, 0).toString(16)将数字转换为十六进制字符串.padStart(2, 0)确保输出至少两位不足前面补零3.4 完整的调色板 UI// Index.ets import { RGBColor } from ./ColorModel; import { PRESET_COLORS } from ./PresetColors; Entry Component struct Index { State currentColor: RGBColor new RGBColor(255, 87, 51); State hexDisplay: string #FF5733; build() { Column() { // 标题 Text(调色板 ) .fontSize(28) .fontWeight(FontWeight.Bold) .margin({ top: 20, bottom: 5 }) Text(选择颜色或拖动滑块调整) .fontSize(14) .fontColor(Color.Gray) .margin({ bottom: 15 }) // 1. 颜色预览区域 Column() { Text() .width(100%) .height(120) .backgroundColor(this.currentColor.toCssString()) .borderRadius(16) .shadow({ radius: 8, color: #40000000, offsetY: 4 }) } .width(90%) .padding(10) // 2. HEX 色值显示 Row() { Text(HEX) .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor(Color.Gray) Text(this.hexDisplay) .fontSize(24) .fontWeight(FontWeight.Bold) .fontColor(this.currentColor.toCssString()) .margin({ left: 15 }) .fontFamily(monospace) } .width(90%) .justifyContent(FlexAlign.Start) .padding({ left: 10, top: 10, bottom: 10 }) // 3. RGB 三通道滑块 Column() { // R 通道 Row() { Text(R) .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor(#FF3333) .width(30) Slider({ value: this.currentColor.red, min: 0, max: 255, step: 1 }) .width(70%) .height(40) .blockColor(#FF3333) .trackThickness(6) .onChange((value: number) { this.currentColor.red value; this.updateDisplay(); }) Text(${this.currentColor.red}) .fontSize(16) .fontWeight(FontWeight.Medium) .width(40) .textAlign(TextAlign.End) } .width(90%) .padding(5) // G 通道 Row() { Text(G) .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor(#33CC33) .width(30) Slider({ value: this.currentColor.green, min: 0, max: 255, step: 1 }) .width(70%) .height(40) .blockColor(#33CC33) .trackThickness(6) .onChange((value: number) { this.currentColor.green value; this.updateDisplay(); }) Text(${this.currentColor.green}) .fontSize(16) .fontWeight(FontWeight.Medium) .width(40) .textAlign(TextAlign.End) } .width(90%) .padding(5) // B 通道 Row() { Text(B) .fontSize(16) .fontWeight(FontWeight.Bold) .fontColor(#3366FF) .width(30) Slider({ value: this.currentColor.blue, min: 0, max: 255, step: 1 }) .width(70%) .height(40) .blockColor(#3366FF) .trackThickness(6) .onChange((value: number) { this.currentColor.blue value; this.updateDisplay(); }) Text(${this.currentColor.blue}) .fontSize(16) .fontWeight(FontWeight.Medium) .width(40) .textAlign(TextAlign.End) } .width(90%) .padding(5) } .width(100%) // 分隔线 Divider() .width(90%) .margin({ top: 10, bottom: 10 }) // 4. 预设颜色网格 Text(预设颜色) .fontSize(16) .fontWeight(FontWeight.Bold) .width(90%) .margin({ bottom: 8 }) Grid() { ForEach(PRESET_COLORS, (color: RGBColor, index: number) { // 预设色块 Column() { Button() .width(44) .height(44) .backgroundColor(color.toCssString()) .borderRadius(8) .border({ width: color this.currentColor ? 3 : 1, color: color this.currentColor ? #2B6CB0 : #E2E8F0 }) .onClick(() { this.currentColor color; this.updateDisplay(); }) } .width(44) .height(44) }, (color: RGBColor) color.toHex()) } .columnsTemplate(1fr 1fr 1fr 1fr 1fr) .rowsTemplate(1fr 1fr 1fr 1fr) .width(90%) .padding(5) } .width(100%) .height(100%) .backgroundColor(#F7FAFC) .overflow(Overflow.Scroll) } // 更新显示 updateDisplay(): void { this.hexDisplay this.currentColor.toHex(); } }四、RGB 颜色模型深入4.1 色彩空间原理RGB红绿蓝是加色混色模型广泛应用于电子显示设备。其核心原理RRed红色波长 ~700nm0-255 表示从无到饱和GGreen绿色波长 ~546nm人眼最敏感的通道BBlue蓝色波长 ~435nm能量最高三个通道各 8 位0-255总计 24 位色可表示 16,777,216 种颜色。4.2 滑块联动算法当滑块拖动时需要实时更新三个数值并同步到 UI// 滑块值变化 → 更新颜色 → 刷新所有相关 UI onChange((value: number) { this.currentColor.red value; // 更新模型 this.updateDisplay(); // 触发 UI 刷新 })关键点在于State的响应式机制currentColor被State装饰修改currentColor.red后由于对象引用未变需通过方法调用触发变更检测updateDisplay()更新hexDisplay字符串该字符串是State变量触发重渲染注意ArkTS 的State是浅比较。修改对象的属性如currentColor.red value时如果对象引用不变框架可能检测不到变化。解决方案// 方案一重新赋值整个对象 this.currentColor new RGBColor(value, this.currentColor.green, this.currentColor.blue); // 方案二使用 Observed 装饰器使对象深度响应 Observed export class RGBColor { ... }4.3 颜色空间转换扩展除了 RGB ↔ HEX实际开发中可能还需要 HSV/HSL 转换// RGB → HSV 转换 function rgbToHsv(r: number, g: number, b: number): { h: number, s: number, v: number } { r / 255; g / 255; b / 255; const max Math.max(r, g, b); const min Math.min(r, g, b); const delta max - min; let h 0; if (delta ! 0) { if (max r) h ((g - b) / delta) % 6; else if (max g) h (b - r) / delta 2; else h (r - g) / delta 4; h Math.round(h * 60); if (h 0) h 360; } const s max 0 ? 0 : (delta / max) * 100; const v max * 100; return { h, s: Math.round(s), v: Math.round(v) }; }五、HarmonyOS 特性分析5.1 Slider 组件HarmonyOS 的Slider组件提供了丰富的自定义选项Slider({ value: this.currentColor.red, min: 0, max: 255, step: 1 }) .width(70%) .height(40) .blockColor(#FF3333) // 滑块按钮颜色 .trackThickness(6) // 轨道厚度 .onChange((value: number) { // 值变化回调 })Slider 关键属性min/max取值范围step步长颜色调整通常为 1blockColor滑块按钮颜色按通道区分trackThickness轨道粗细5.2 Grid 网格预设布局20 个预设颜色使用Grid组件排列为 5 列 4 行Grid() { ForEach(PRESET_COLORS, (color: RGBColor, index: number) { // 色块 }, (color: RGBColor) color.toHex()) } .columnsTemplate(1fr 1fr 1fr 1fr 1fr) // 5 列 .rowsTemplate(1fr 1fr 1fr 1fr) // 4 行5.3 响应式状态管理State currentColor当前选中的颜色驱动预览和 HEX 显示State hexDisplayHEX 字符串单独存储以便字体颜色同步5.4 阴影与圆角.shadow({ radius: 8, color: #40000000, offsetY: 4 }) .borderRadius(16)HarmonyOS 的shadowAPI 支持自定义阴影半径、颜色和偏移为 UI 添加层次感。六、UI/UX 设计6.1 交互反馈预设点击点击色块立即更新预览和滑块滑块拖动实时联动拖动滑块时颜色同步变化选中高亮当前选中预设加蓝色边框数值显示滑块右侧实时显示当前通道值6.2 视觉设计布局层次 标题 → 子标题 颜色预览区大块 HEX 色值等宽字体 RGB 滑块按通道颜色标识 —— 分隔线 —— 预设颜色5×4 网格预览区域使用大色块直观感受颜色HEX 值使用等宽字体monospace便于阅读滑块按通道着色红/绿/蓝视觉区分度高预设颜色覆盖主要色系6.3 色彩辅助功能// 颜色复制到剪贴板 function copyToClipboard(text: string): void { // HarmonyOS 剪贴板 API // pasteboard.createData(PasteType.PLAIN_TEXT, text) console.info(已复制颜色: ${text}); }七、最佳实践总结7.1 代码组织颜色模型与 UI 分离枚举预设颜色为独立模块工具函数RGB↔HEX 转换集中管理7.2 状态管理使用 Observed 装饰器实现深度响应式避免在组件内部修改 props使用单一数据源Single Source of Truth7.3 性能优化滑块 onChange 回调中避免复杂计算使用离散 stepstep1而非连续模式ForEach 使用稳定的 key颜色 HEX 值7.4 可扩展性支持 HSL/HSV 模式切换支持透明度Alpha通道支持颜色收藏和近期使用支持从图片取色八、扩展功能HSL 色相环选择器除了 RGB 滑块可以添加更直观的色相环选择器Component struct HueSlider { Prop hue: number; onChange?: (hue: number) void; build() { // 渐变轨道从红到紫跨越整个色相环 Slider({ value: this.hue, min: 0, max: 360, step: 1 }) .width(80%) .trackThickness(20) .blockColor(#FFFFFF) .onChange((value: number) { if (this.onChange) this.onChange(value); }) } }九、完整代码清单// ColorModel.ets Observed export class RGBColor { red: number; green: number; blue: number; constructor(red: number 0, green: number 0, blue: number 0) { this.red Math.max(0, Math.min(255, Math.round(red))); this.green Math.max(0, Math.min(255, Math.round(green))); this.blue Math.max(0, Math.min(255, Math.round(blue))); } toHex(): string { const r this.red.toString(16).padStart(2, 0); const g this.green.toString(16).padStart(2, 0); const b this.blue.toString(16).padStart(2, 0); return #${r}${g}${b}.toUpperCase(); } toCssString(): string { return rgb(${this.red}, ${this.green}, ${this.blue}); } }// PresetColors.ets import { RGBColor } from ./ColorModel; export const PRESET_COLORS: RGBColor[] [ new RGBColor(255, 51, 51), new RGBColor(204, 0, 0), new RGBColor(255, 102, 102), new RGBColor(255, 153, 51), new RGBColor(255, 204, 102), new RGBColor(255, 255, 51), new RGBColor(255, 255, 204), new RGBColor(51, 204, 51), new RGBColor(0, 153, 0), new RGBColor(102, 204, 102), new RGBColor(51, 102, 255), new RGBColor(0, 0, 204), new RGBColor(102, 178, 255), new RGBColor(153, 51, 255), new RGBColor(204, 153, 255), new RGBColor(255, 255, 255), new RGBColor(204, 204, 204), new RGBColor(128, 128, 128), new RGBColor(64, 64, 64), new RGBColor(0, 0, 0), ];// ColorUtils.ets import { RGBColor } from ./ColorModel; export function hexToRgb(hex: string): RGBColor { const clean hex.replace(#, ); if (clean.length ! 6) return new RGBColor(0, 0, 0); const r parseInt(clean.substring(0, 2), 16); const g parseInt(clean.substring(2, 4), 16); const b parseInt(clean.substring(4, 6), 16); return new RGBColor(r, g, b); } export function isLightColor(color: RGBColor): boolean { // 相对亮度公式 const luminance 0.299 * color.red 0.587 * color.green 0.114 * color.blue; return luminance 186; } export function getContrastTextColor(bgColor: RGBColor): string { return isLightColor(bgColor) ? #000000 : #FFFFFF; }十、总结通过本篇文章我们深入解析了 HarmonyOS 调色板应用的完整实现RGB 颜色模型—— 加色混色原理、24 位色空间RGB ↔ HEX 转换算法—— 十进制与十六进制互转滑块联动—— Slider 组件使用、三通道实时同步预设颜色选择—— 20 色覆盖主要色系、Grid 网格布局声明式 UI—— State/Observed 响应式编程、条件渲染调色板应用虽然功能看似简单但涵盖了颜色科学、UI 交互、响应式编程等多个重要领域。从颜色理论到具体实现每一步都体现了工程设计与用户体验的平衡。希望本文能为 HarmonyOS 开发者提供一个完整的技术参考。