在这里插入图片描述

目录

  1. 概述
  2. 工具功能
  3. 核心实现
  4. Kotlin 源代码
  5. JavaScript 编译代码
  6. ArkTS 调用代码
  7. 实战案例
  8. 最佳实践

概述

本文档介绍如何在 Kotlin Multiplatform (KMP) 鸿蒙跨端开发中实现一个功能完整的颜色转换工具系统。颜色转换是现代应用开发中的常见需求,广泛应用于UI设计、图形处理、网页开发、数据可视化等领域。这个工具提供了对多种颜色格式的转换支持,包括RGB(红绿蓝)、HEX(十六进制)、HSL(色调饱和度亮度)、HSV(色调饱和度值)、CMYK(青洋红黄黑)等格式。

在实际应用中,颜色转换工具广泛应用于以下场景:UI设计工具、图形编辑器、网页设计应用、数据可视化平台、色彩搭配工具、无障碍设计工具等。通过 KMP 框架的跨端能力,我们可以在不同平台上使用相同的颜色转换逻辑,确保颜色表示的准确性和一致性。

工具的特点

  • 多格式支持:支持RGB、HEX、HSL、HSV、CMYK等多种颜色格式
  • 精确转换:使用标准的色彩空间转换算法
  • 双向转换:支持任意两种格式之间的相互转换
  • 颜色分析:提供颜色的亮度、饱和度、对比度等分析
  • 颜色搭配:生成互补色、类似色等颜色搭配建议
  • 跨端兼容:一份 Kotlin 代码可同时服务多个平台

工具功能

1. RGB 颜色格式

RGB 是最常见的颜色格式,由红、绿、蓝三个通道组成,每个通道的值范围是 0-255。RGB 格式在计算机显示、网页设计和图形处理中广泛使用。RGB 转换需要考虑通道值的有效范围和精度。

  • RGB 值范围:每个通道 0-255
  • RGB 字符串表示:rgb(r, g, b) 或 rgba(r, g, b, a)
  • RGB 对象表示:包含 r、g、b、a 四个属性
  • RGB 验证:检查通道值是否在有效范围内

2. HEX 颜色格式

HEX 是十六进制颜色表示法,使用 # 符号后跟六位十六进制数字。HEX 格式在网页设计和 CSS 中广泛使用。HEX 转换需要处理十六进制和十进制之间的转换。

  • HEX 格式:#RRGGBB 或 #RRGGBBAA
  • 短格式:#RGB(每个通道一位)
  • 大小写处理:支持大小写转换
  • 透明度支持:支持 Alpha 通道

3. HSL 颜色格式

HSL 是基于色调、饱和度、亮度的颜色表示法。HSL 格式对于颜色调整和搭配更加直观。HSL 转换涉及到复杂的数学计算。

  • 色调(Hue):0-360 度,表示颜色
  • 饱和度(Saturation):0-100%,表示颜色的纯度
  • 亮度(Lightness):0-100%,表示颜色的明暗
  • HSL 字符串表示:hsl(h, s%, l%) 或 hsla(h, s%, l%, a)

4. HSV 颜色格式

HSV 是基于色调、饱和度、值的颜色表示法。HSV 与 HSL 相似,但亮度的计算方式不同。HSV 在图像处理和颜色选择器中常见。

  • 色调(Hue):0-360 度
  • 饱和度(Saturation):0-100%
  • 值(Value):0-100%,表示颜色的亮度
  • HSV 应用:图像处理、颜色选择器

5. CMYK 颜色格式

CMYK 是用于印刷的颜色格式,由青、洋红、黄、黑四个通道组成。CMYK 转换用于将数字颜色转换为印刷颜色。

  • CMYK 值范围:每个通道 0-100%
  • 印刷应用:用于印刷和出版
  • RGB 到 CMYK:需要反演和黑色提取
  • CMYK 到 RGB:需要黑色还原和反演

6. 颜色分析

颜色分析提供颜色的各种属性和特征,包括亮度、饱和度、对比度等。颜色分析对于无障碍设计和颜色搭配很重要。

  • 亮度计算:计算颜色的相对亮度
  • 对比度计算:计算两种颜色的对比度
  • 颜色距离:计算两种颜色的距离
  • 无障碍检查:检查颜色对比度是否符合 WCAG 标准

7. 颜色搭配建议

根据给定的颜色,生成互补色、类似色、三角配色等搭配建议。颜色搭配建议对于 UI 设计和品牌设计很有用。

  • 互补色:色轮上相对的颜色
  • 类似色:色轮上相邻的颜色
  • 三角配色:色轮上均匀分布的三种颜色
  • 分裂互补色:互补色两侧的颜色

核心实现

1. RGB 到 HEX 转换

fun rgbToHex(r: Int, g: Int, b: Int, a: Int = 255): String {
    val r = r.coerceIn(0, 255)
    val g = g.coerceIn(0, 255)
    val b = b.coerceIn(0, 255)
    val a = a.coerceIn(0, 255)
    
    return if (a == 255) {
        String.format("#%02X%02X%02X", r, g, b)
    } else {
        String.format("#%02X%02X%02X%02X", r, g, b, a)
    }
}

fun hexToRgb(hex: String): Map<String, Int> {
    val cleanHex = hex.removePrefix("#")
    
    return when (cleanHex.length) {
        6 -> {
            mapOf(
                "r" to cleanHex.substring(0, 2).toInt(16),
                "g" to cleanHex.substring(2, 4).toInt(16),
                "b" to cleanHex.substring(4, 6).toInt(16),
                "a" to 255
            )
        }
        8 -> {
            mapOf(
                "r" to cleanHex.substring(0, 2).toInt(16),
                "g" to cleanHex.substring(2, 4).toInt(16),
                "b" to cleanHex.substring(4, 6).toInt(16),
                "a" to cleanHex.substring(6, 8).toInt(16)
            )
        }
        else -> mapOf("r" to 0, "g" to 0, "b" to 0, "a" to 255)
    }
}

代码说明: RGB 到 HEX 的转换使用十六进制格式化。HEX 到 RGB 的转换需要解析十六进制字符串。通过处理不同长度的 HEX 字符串,可以支持带或不带透明度的颜色。

2. RGB 到 HSL 转换

fun rgbToHsl(r: Int, g: Int, b: Int): Map<String, Double> {
    val r = r / 255.0
    val g = g / 255.0
    val b = b / 255.0
    
    val max = maxOf(r, g, b)
    val min = minOf(r, g, b)
    val l = (max + min) / 2.0
    
    if (max == min) {
        return mapOf("h" to 0.0, "s" to 0.0, "l" to l * 100)
    }
    
    val d = max - min
    val s = if (l > 0.5) d / (2.0 - max - min) else d / (max + min)
    
    val h = when (max) {
        r -> (60 * (((g - b) / d) % 6) + 360) % 360
        g -> (60 * (((b - r) / d) + 2) + 360) % 360
        b -> (60 * (((r - g) / d) + 4) + 360) % 360
        else -> 0.0
    }
    
    return mapOf("h" to h, "s" to s * 100, "l" to l * 100)
}

fun hslToRgb(h: Double, s: Double, l: Double): Map<String, Int> {
    val h = h % 360
    val s = s / 100.0
    val l = l / 100.0
    
    val c = (1 - kotlin.math.abs(2 * l - 1)) * s
    val x = c * (1 - kotlin.math.abs((h / 60) % 2 - 1))
    val m = l - c / 2
    
    val (r1, g1, b1) = when {
        h < 60 -> Triple(c, x, 0.0)
        h < 120 -> Triple(x, c, 0.0)
        h < 180 -> Triple(0.0, c, x)
        h < 240 -> Triple(0.0, x, c)
        h < 300 -> Triple(x, 0.0, c)
        else -> Triple(c, 0.0, x)
    }
    
    return mapOf(
        "r" to ((r1 + m) * 255).toInt(),
        "g" to ((g1 + m) * 255).toInt(),
        "b" to ((b1 + m) * 255).toInt()
    )
}

代码说明: RGB 到 HSL 的转换使用标准的色彩空间转换算法。HSL 到 RGB 的转换需要反向计算。这些转换涉及到复杂的数学计算,需要特别注意精度。

3. 颜色亮度计算

fun calculateLuminance(r: Int, g: Int, b: Int): Double {
    val r = r / 255.0
    val g = g / 255.0
    val b = b / 255.0
    
    val rs = if (r <= 0.03928) r / 12.92 else kotlin.math.pow((r + 0.055) / 1.055, 2.4)
    val gs = if (g <= 0.03928) g / 12.92 else kotlin.math.pow((g + 0.055) / 1.055, 2.4)
    val bs = if (b <= 0.03928) b / 12.92 else kotlin.math.pow((b + 0.055) / 1.055, 2.4)
    
    return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
}

fun calculateContrast(color1: Map<String, Int>, color2: Map<String, Int>): Double {
    val l1 = calculateLuminance(color1["r"] ?: 0, color1["g"] ?: 0, color1["b"] ?: 0)
    val l2 = calculateLuminance(color2["r"] ?: 0, color2["g"] ?: 0, color2["b"] ?: 0)
    
    val lighter = maxOf(l1, l2)
    val darker = minOf(l1, l2)
    
    return (lighter + 0.05) / (darker + 0.05)
}

代码说明: 颜色亮度计算使用 WCAG 标准的相对亮度公式。对比度计算用于检查颜色组合是否符合无障碍标准。

4. 颜色搭配建议

fun getComplementaryColor(h: Double): Double {
    return (h + 180) % 360
}

fun getAnalogousColors(h: Double): List<Double> {
    return listOf(
        (h - 30 + 360) % 360,
        h,
        (h + 30) % 360
    )
}

fun getTriadicColors(h: Double): List<Double> {
    return listOf(
        h,
        (h + 120) % 360,
        (h + 240) % 360
    )
}

fun getSplitComplementaryColors(h: Double): List<Double> {
    val comp = (h + 180) % 360
    return listOf(
        h,
        (comp - 30 + 360) % 360,
        (comp + 30) % 360
    )
}

代码说明: 颜色搭配建议基于色轮的几何关系。互补色相差 180 度,类似色相差 30 度,三角配色相差 120 度。


Kotlin 源代码

// ColorConverter.kt
class ColorConverter {
    
    fun rgbToHex(r: Int, g: Int, b: Int, a: Int = 255): String {
        val r = r.coerceIn(0, 255)
        val g = g.coerceIn(0, 255)
        val b = b.coerceIn(0, 255)
        val a = a.coerceIn(0, 255)
        
        return if (a == 255) {
            String.format("#%02X%02X%02X", r, g, b)
        } else {
            String.format("#%02X%02X%02X%02X", r, g, b, a)
        }
    }
    
    fun hexToRgb(hex: String): Map<String, Int> {
        val cleanHex = hex.removePrefix("#")
        
        return when (cleanHex.length) {
            6 -> {
                mapOf(
                    "r" to cleanHex.substring(0, 2).toInt(16),
                    "g" to cleanHex.substring(2, 4).toInt(16),
                    "b" to cleanHex.substring(4, 6).toInt(16),
                    "a" to 255
                )
            }
            8 -> {
                mapOf(
                    "r" to cleanHex.substring(0, 2).toInt(16),
                    "g" to cleanHex.substring(2, 4).toInt(16),
                    "b" to cleanHex.substring(4, 6).toInt(16),
                    "a" to cleanHex.substring(6, 8).toInt(16)
                )
            }
            else -> mapOf("r" to 0, "g" to 0, "b" to 0, "a" to 255)
        }
    }
    
    fun rgbToHsl(r: Int, g: Int, b: Int): Map<String, Double> {
        val r = r / 255.0
        val g = g / 255.0
        val b = b / 255.0
        
        val max = maxOf(r, g, b)
        val min = minOf(r, g, b)
        val l = (max + min) / 2.0
        
        if (max == min) {
            return mapOf("h" to 0.0, "s" to 0.0, "l" to l * 100)
        }
        
        val d = max - min
        val s = if (l > 0.5) d / (2.0 - max - min) else d / (max + min)
        
        val h = when (max) {
            r -> (60 * (((g - b) / d) % 6) + 360) % 360
            g -> (60 * (((b - r) / d) + 2) + 360) % 360
            b -> (60 * (((r - g) / d) + 4) + 360) % 360
            else -> 0.0
        }
        
        return mapOf("h" to h, "s" to s * 100, "l" to l * 100)
    }
    
    fun hslToRgb(h: Double, s: Double, l: Double): Map<String, Int> {
        val h = h % 360
        val s = s / 100.0
        val l = l / 100.0
        
        val c = (1 - kotlin.math.abs(2 * l - 1)) * s
        val x = c * (1 - kotlin.math.abs((h / 60) % 2 - 1))
        val m = l - c / 2
        
        val (r1, g1, b1) = when {
            h < 60 -> Triple(c, x, 0.0)
            h < 120 -> Triple(x, c, 0.0)
            h < 180 -> Triple(0.0, c, x)
            h < 240 -> Triple(0.0, x, c)
            h < 300 -> Triple(x, 0.0, c)
            else -> Triple(c, 0.0, x)
        }
        
        return mapOf(
            "r" to ((r1 + m) * 255).toInt(),
            "g" to ((g1 + m) * 255).toInt(),
            "b" to ((b1 + m) * 255).toInt()
        )
    }
    
    fun rgbToHsv(r: Int, g: Int, b: Int): Map<String, Double> {
        val r = r / 255.0
        val g = g / 255.0
        val b = b / 255.0
        
        val max = maxOf(r, g, b)
        val min = minOf(r, g, b)
        val d = max - min
        
        val h = when {
            d == 0.0 -> 0.0
            max == r -> (60 * (((g - b) / d) % 6) + 360) % 360
            max == g -> (60 * (((b - r) / d) + 2)) % 360
            else -> (60 * (((r - g) / d) + 4)) % 360
        }
        
        val s = if (max == 0.0) 0.0 else d / max
        val v = max
        
        return mapOf("h" to h, "s" to s * 100, "v" to v * 100)
    }
    
    fun calculateLuminance(r: Int, g: Int, b: Int): Double {
        val r = r / 255.0
        val g = g / 255.0
        val b = b / 255.0
        
        val rs = if (r <= 0.03928) r / 12.92 else kotlin.math.pow((r + 0.055) / 1.055, 2.4)
        val gs = if (g <= 0.03928) g / 12.92 else kotlin.math.pow((g + 0.055) / 1.055, 2.4)
        val bs = if (b <= 0.03928) b / 12.92 else kotlin.math.pow((b + 0.055) / 1.055, 2.4)
        
        return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs
    }
    
    fun calculateContrast(color1: Map<String, Int>, color2: Map<String, Int>): Double {
        val l1 = calculateLuminance(color1["r"] ?: 0, color1["g"] ?: 0, color1["b"] ?: 0)
        val l2 = calculateLuminance(color2["r"] ?: 0, color2["g"] ?: 0, color2["b"] ?: 0)
        
        val lighter = maxOf(l1, l2)
        val darker = minOf(l1, l2)
        
        return (lighter + 0.05) / (darker + 0.05)
    }
    
    fun getComplementaryColor(h: Double): Double {
        return (h + 180) % 360
    }
    
    fun getAnalogousColors(h: Double): List<Double> {
        return listOf(
            (h - 30 + 360) % 360,
            h,
            (h + 30) % 360
        )
    }
    
    fun getTriadicColors(h: Double): List<Double> {
        return listOf(
            h,
            (h + 120) % 360,
            (h + 240) % 360
        )
    }
    
    fun convert(value: String, fromFormat: String, toFormat: String): Map<String, Any> {
        return try {
            when {
                fromFormat == "hex" && toFormat == "rgb" -> {
                    val rgb = hexToRgb(value)
                    mapOf("success" to true, "result" to rgb)
                }
                fromFormat == "rgb" && toFormat == "hex" -> {
                    val parts = value.split(",").map { it.trim().toInt() }
                    val hex = rgbToHex(parts[0], parts[1], parts[2])
                    mapOf("success" to true, "result" to hex)
                }
                fromFormat == "rgb" && toFormat == "hsl" -> {
                    val parts = value.split(",").map { it.trim().toInt() }
                    val hsl = rgbToHsl(parts[0], parts[1], parts[2])
                    mapOf("success" to true, "result" to hsl)
                }
                fromFormat == "hsl" && toFormat == "rgb" -> {
                    val parts = value.split(",").map { it.trim().toDouble() }
                    val rgb = hslToRgb(parts[0], parts[1], parts[2])
                    mapOf("success" to true, "result" to rgb)
                }
                else -> mapOf("success" to false, "error" to "不支持的转换")
            }
        } catch (e: Exception) {
            mapOf("success" to false, "error" to e.message)
        }
    }
}

fun main() {
    val converter = ColorConverter()
    
    println("=== 颜色转换工具演示 ===\n")
    
    // RGB 到 HEX
    val hex = converter.rgbToHex(255, 128, 0)
    println("RGB(255, 128, 0) = $hex")
    
    // HEX 到 RGB
    val rgb = converter.hexToRgb("#FF8000")
    println("HEX #FF8000 = RGB${rgb.values.take(3)}")
    
    // RGB 到 HSL
    val hsl = converter.rgbToHsl(255, 128, 0)
    println("RGB(255, 128, 0) = HSL(${hsl["h"]?.toInt()}, ${hsl["s"]?.toInt()}%, ${hsl["l"]?.toInt()}%)")
    
    // 颜色对比度
    val color1 = mapOf("r" to 255, "g" to 255, "b" to 255)
    val color2 = mapOf("r" to 0, "g" to 0, "b" to 0)
    val contrast = converter.calculateContrast(color1, color2)
    println("白色和黑色的对比度: ${String.format("%.2f", contrast)}")
    
    // 互补色
    val complementary = converter.getComplementaryColor(30.0)
    println("30度的互补色: ${complementary.toInt()}度")
}

Kotlin 代码说明: 这个实现提供了完整的颜色转换功能。ColorConverter 类包含了 RGB、HEX、HSL、HSV 等多种格式的转换方法。通过使用标准的色彩空间转换算法,确保了转换的准确性。颜色分析和搭配建议方法基于色轮的几何关系。


JavaScript 编译代码

// ColorConverter.js
class ColorConverter {
    rgbToHex(r, g, b, a = 255) {
        r = Math.max(0, Math.min(255, r));
        g = Math.max(0, Math.min(255, g));
        b = Math.max(0, Math.min(255, b));
        a = Math.max(0, Math.min(255, a));
        
        if (a === 255) {
            return "#" + [r, g, b].map(x => x.toString(16).padStart(2, '0').toUpperCase()).join('');
        } else {
            return "#" + [r, g, b, a].map(x => x.toString(16).padStart(2, '0').toUpperCase()).join('');
        }
    }
    
    hexToRgb(hex) {
        const cleanHex = hex.replace('#', '');
        
        if (cleanHex.length === 6) {
            return {
                r: parseInt(cleanHex.substring(0, 2), 16),
                g: parseInt(cleanHex.substring(2, 4), 16),
                b: parseInt(cleanHex.substring(4, 6), 16),
                a: 255
            };
        } else if (cleanHex.length === 8) {
            return {
                r: parseInt(cleanHex.substring(0, 2), 16),
                g: parseInt(cleanHex.substring(2, 4), 16),
                b: parseInt(cleanHex.substring(4, 6), 16),
                a: parseInt(cleanHex.substring(6, 8), 16)
            };
        }
        return { r: 0, g: 0, b: 0, a: 255 };
    }
    
    rgbToHsl(r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        
        const max = Math.max(r, g, b);
        const min = Math.min(r, g, b);
        let l = (max + min) / 2;
        
        if (max === min) {
            return { h: 0, s: 0, l: l * 100 };
        }
        
        const d = max - min;
        const s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
        
        let h;
        if (max === r) h = (60 * (((g - b) / d) % 6) + 360) % 360;
        else if (max === g) h = (60 * (((b - r) / d) + 2) + 360) % 360;
        else h = (60 * (((r - g) / d) + 4) + 360) % 360;
        
        return { h: h, s: s * 100, l: l * 100 };
    }
    
    hslToRgb(h, s, l) {
        h = h % 360;
        s /= 100;
        l /= 100;
        
        const c = (1 - Math.abs(2 * l - 1)) * s;
        const x = c * (1 - Math.abs((h / 60) % 2 - 1));
        const m = l - c / 2;
        
        let r1, g1, b1;
        if (h < 60) { r1 = c; g1 = x; b1 = 0; }
        else if (h < 120) { r1 = x; g1 = c; b1 = 0; }
        else if (h < 180) { r1 = 0; g1 = c; b1 = x; }
        else if (h < 240) { r1 = 0; g1 = x; b1 = c; }
        else if (h < 300) { r1 = x; g1 = 0; b1 = c; }
        else { r1 = c; g1 = 0; b1 = x; }
        
        return {
            r: Math.round((r1 + m) * 255),
            g: Math.round((g1 + m) * 255),
            b: Math.round((b1 + m) * 255)
        };
    }
    
    calculateLuminance(r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        
        const rs = r <= 0.03928 ? r / 12.92 : Math.pow((r + 0.055) / 1.055, 2.4);
        const gs = g <= 0.03928 ? g / 12.92 : Math.pow((g + 0.055) / 1.055, 2.4);
        const bs = b <= 0.03928 ? b / 12.92 : Math.pow((b + 0.055) / 1.055, 2.4);
        
        return 0.2126 * rs + 0.7152 * gs + 0.0722 * bs;
    }
    
    calculateContrast(color1, color2) {
        const l1 = this.calculateLuminance(color1.r || 0, color1.g || 0, color1.b || 0);
        const l2 = this.calculateLuminance(color2.r || 0, color2.g || 0, color2.b || 0);
        
        const lighter = Math.max(l1, l2);
        const darker = Math.min(l1, l2);
        
        return (lighter + 0.05) / (darker + 0.05);
    }
    
    getComplementaryColor(h) {
        return (h + 180) % 360;
    }
    
    getAnalogousColors(h) {
        return [
            (h - 30 + 360) % 360,
            h,
            (h + 30) % 360
        ];
    }
    
    getTriadicColors(h) {
        return [
            h,
            (h + 120) % 360,
            (h + 240) % 360
        ];
    }
    
    convert(value, fromFormat, toFormat) {
        try {
            if (fromFormat === "hex" && toFormat === "rgb") {
                const rgb = this.hexToRgb(value);
                return { success: true, result: rgb };
            } else if (fromFormat === "rgb" && toFormat === "hex") {
                const parts = value.split(",").map(x => parseInt(x.trim()));
                const hex = this.rgbToHex(parts[0], parts[1], parts[2]);
                return { success: true, result: hex };
            } else if (fromFormat === "rgb" && toFormat === "hsl") {
                const parts = value.split(",").map(x => parseInt(x.trim()));
                const hsl = this.rgbToHsl(parts[0], parts[1], parts[2]);
                return { success: true, result: hsl };
            } else if (fromFormat === "hsl" && toFormat === "rgb") {
                const parts = value.split(",").map(x => parseFloat(x.trim()));
                const rgb = this.hslToRgb(parts[0], parts[1], parts[2]);
                return { success: true, result: rgb };
            }
            return { success: false, error: "不支持的转换" };
        } catch (e) {
            return { success: false, error: e.message };
        }
    }
}

// 使用示例
const converter = new ColorConverter();

console.log("=== 颜色转换工具演示 ===\n");

const hex = converter.rgbToHex(255, 128, 0);
console.log("RGB(255, 128, 0) =", hex);

const rgb = converter.hexToRgb("#FF8000");
console.log("HEX #FF8000 = RGB", [rgb.r, rgb.g, rgb.b]);

const hsl = converter.rgbToHsl(255, 128, 0);
console.log(`RGB(255, 128, 0) = HSL(${Math.round(hsl.h)}, ${Math.round(hsl.s)}%, ${Math.round(hsl.l)}%)`);

const color1 = { r: 255, g: 255, b: 255 };
const color2 = { r: 0, g: 0, b: 0 };
const contrast = converter.calculateContrast(color1, color2);
console.log("白色和黑色的对比度:", contrast.toFixed(2));

const complementary = converter.getComplementaryColor(30);
console.log("30度的互补色:", Math.round(complementary) + "度");

JavaScript 代码说明: JavaScript 版本是 Kotlin 代码的直接转译。由于 JavaScript 和 Kotlin 在语法上有差异,我们使用对象替代 Map,使用 Math 函数替代 kotlin.math 函数。整体逻辑和算法与 Kotlin 版本保持一致,确保跨平台的一致性。


ArkTS 调用代码

// ColorConverterPage.ets
import { ColorConverter } from './ColorConverter';

@Entry
@Component
struct ColorConverterPage {
    @State fromFormat: string = 'rgb';
    @State toFormat: string = 'hex';
    @State inputValue: string = '255,128,0';
    @State outputValue: string = '';
    @State showResult: boolean = false;
    @State isLoading: boolean = false;
    @State previewColor: string = '#FF8000';
    
    private converter: ColorConverter = new ColorConverter();
    
    private formatOptions = [
        { label: 'RGB', value: 'rgb' },
        { label: 'HEX', value: 'hex' },
        { label: 'HSL', value: 'hsl' },
        { label: 'HSV', value: 'hsv' }
    ];
    
    performConversion() {
        this.isLoading = true;
        try {
            const result = this.converter.convert(this.inputValue, this.fromFormat, this.toFormat);
            
            if (result.success) {
                if (typeof result.result === 'object') {
                    this.outputValue = JSON.stringify(result.result);
                    if (this.toFormat === 'hex') {
                        this.previewColor = result.result;
                    }
                } else {
                    this.outputValue = result.result;
                }
            } else {
                this.outputValue = '转换失败: ' + result.error;
            }
            this.showResult = true;
        } catch (error) {
            this.outputValue = '转换出错: ' + (error instanceof Error ? error.message : String(error));
            this.showResult = true;
        } finally {
            this.isLoading = false;
        }
    }
    
    build() {
        Column() {
            // 标题
            Text('颜色转换工具')
                .fontSize(28)
                .fontWeight(FontWeight.Bold)
                .margin({ top: 20, bottom: 20 })
                .textAlign(TextAlign.Center)
                .width('100%')
            
            // 颜色预览
            if (this.previewColor) {
                Row() {
                    Column()
                        .width(60)
                        .height(60)
                        .backgroundColor(this.previewColor)
                        .borderRadius(8)
                        .border({ width: 2, color: '#cccccc' })
                    
                    Text(this.previewColor)
                        .fontSize(16)
                        .fontColor('#333333')
                        .margin({ left: 15 })
                }
                .width('100%')
                .padding(15)
                .backgroundColor('#ffffff')
                .borderRadius(10)
                .border({ width: 1, color: '#eeeeee' })
                .margin({ bottom: 20 })
            }
            
            // 格式选择
            Row() {
                Column() {
                    Text('从')
                        .fontSize(12)
                        .fontColor('#666666')
                        .margin({ bottom: 8 })
                    
                    Select(this.formatOptions)
                        .value(this.fromFormat)
                        .onSelect((index: number, value?: string) => {
                            this.fromFormat = value || 'rgb';
                        })
                        .width('100%')
                        .height(40)
                }
                .width('45%')
                
                Text('→')
                    .fontSize(20)
                    .fontWeight(FontWeight.Bold)
                    .width('10%')
                    .textAlign(TextAlign.Center)
                
                Column() {
                    Text('到')
                        .fontSize(12)
                        .fontColor('#666666')
                        .margin({ bottom: 8 })
                    
                    Select(this.formatOptions)
                        .value(this.toFormat)
                        .onSelect((index: number, value?: string) => {
                            this.toFormat = value || 'hex';
                        })
                        .width('100%')
                        .height(40)
                }
                .width('45%')
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#ffffff')
            .borderRadius(10)
            .border({ width: 1, color: '#eeeeee' })
            .margin({ bottom: 20 })
            
            // 输入框
            Column() {
                Text('输入颜色值')
                    .fontSize(14)
                    .fontColor('#666666')
                    .margin({ bottom: 10 })
                
                TextInput({ placeholder: '输入颜色值' })
                    .value(this.inputValue)
                    .onChange((value: string) => {
                        this.inputValue = value;
                    })
                    .width('100%')
                    .height(45)
                    .padding({ left: 10, right: 10 })
                    .border({ width: 1, color: '#cccccc', radius: 8 })
                    .fontSize(14)
            }
            .width('100%')
            .padding(15)
            .backgroundColor('#ffffff')
            .borderRadius(10)
            .border({ width: 1, color: '#eeeeee' })
            .margin({ bottom: 20 })
            
            // 转换按钮
            Button('转换')
                .width('100%')
                .height(45)
                .fontSize(16)
                .fontWeight(FontWeight.Bold)
                .backgroundColor('#1B7837')
                .fontColor('#ffffff')
                .onClick(() => {
                    this.performConversion();
                })
                .margin({ bottom: 20 })
            
            // 结果显示
            if (this.showResult) {
                Column() {
                    Text('转换结果')
                        .fontSize(16)
                        .fontWeight(FontWeight.Bold)
                        .fontColor('#FFFFFF')
                        .width('100%')
                        .padding(12)
                        .backgroundColor('#1B7837')
                        .borderRadius(8)
                        .margin({ bottom: 12 })
                    
                    Scroll() {
                        Text(this.outputValue)
                            .fontSize(14)
                            .fontColor('#333333')
                            .width('100%')
                            .padding(12)
                            .backgroundColor('#f9f9f9')
                            .borderRadius(6)
                            .textAlign(TextAlign.Start)
                            .selectable(true)
                    }
                    .width('100%')
                    .height(120)
                }
                .width('100%')
                .padding(15)
                .backgroundColor('#ffffff')
                .borderRadius(10)
                .border({ width: 1, color: '#eeeeee' })
            }
            
            Blank()
        }
        .width('100%')
        .height('100%')
        .padding(15)
        .backgroundColor('#f5f5f5')
        .scrollable(ScrollDirection.Vertical)
    }
}

ArkTS 代码说明: ArkTS 调用代码展示了如何在 OpenHarmony 应用中使用颜色转换工具。页面包含颜色预览、格式选择、输入框、转换按钮和结果显示等功能。通过 @State 装饰器管理状态,实现了完整的用户交互流程。用户可以选择源格式和目标格式,输入颜色值,然后点击转换按钮获得转换结果。


实战案例

案例1:UI设计工具

在 UI 设计工具中,需要支持多种颜色格式的输入和转换。

fun updateColorInDesignTool(colorValue: String, format: String) {
    val converter = ColorConverter()
    
    // 转换为 RGB 以便在画布上显示
    val rgb = when (format) {
        "hex" -> converter.hexToRgb(colorValue)
        "hsl" -> {
            val parts = colorValue.split(",").map { it.trim().toDouble() }
            converter.hslToRgb(parts[0], parts[1], parts[2])
        }
        else -> mapOf("r" to 0, "g" to 0, "b" to 0)
    }
    
    updateCanvasColor(rgb)
}

案例2:无障碍检查工具

在无障碍检查工具中,需要验证颜色对比度是否符合标准。

fun checkColorAccessibility(foreground: Map<String, Int>, background: Map<String, Int>): Boolean {
    val converter = ColorConverter()
    val contrast = converter.calculateContrast(foreground, background)
    
    // WCAG AA 标准要求对比度至少为 4.5:1
    return contrast >= 4.5
}

案例3:颜色搭配建议应用

在颜色搭配建议应用中,根据用户选择的颜色生成搭配方案。

fun generateColorScheme(baseColor: String): Map<String, List<Double>> {
    val converter = ColorConverter()
    val rgb = converter.hexToRgb(baseColor)
    val hsl = converter.rgbToHsl(rgb["r"] ?: 0, rgb["g"] ?: 0, rgb["b"] ?: 0)
    val h = hsl["h"] ?: 0.0
    
    return mapOf(
        "complementary" to listOf(converter.getComplementaryColor(h)),
        "analogous" to converter.getAnalogousColors(h),
        "triadic" to converter.getTriadicColors(h)
    )
}

最佳实践

1. 输入验证

在转换前验证输入值的格式和范围。确保输入数据的有效性。

2. 精度管理

对于浮点数计算,需要考虑精度问题。使用适当的舍入和格式化。

3. 错误处理

提供清晰的错误消息,帮助用户理解转换失败的原因。

4. 性能优化

对于频繁的颜色转换,考虑缓存结果以提高性能。

5. 无障碍支持

提供颜色对比度检查功能,确保应用符合无障碍标准。

6. 用户体验

提供颜色预览功能,让用户直观地看到转换结果。


总结

颜色转换工具是现代应用开发中的重要组件。通过 KMP 框架,我们可以创建一个跨端的颜色转换系统,在 Kotlin、JavaScript 和 ArkTS 中使用相同的转换逻辑。这不仅提高了代码的可维护性,还确保了不同平台上转换结果的一致性。

在实际应用中,合理使用颜色转换工具可以提高应用的功能性和用户体验。无论是 UI 设计、图形处理还是无障碍设计,颜色转换工具都能发挥重要作用。通过支持多种颜色格式和提供颜色分析功能,我们可以帮助用户更好地处理和理解颜色信息。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

开源鸿蒙跨平台开发社区汇聚开发者与厂商,共建“一次开发,多端部署”的开源生态,致力于降低跨端开发门槛,推动万物智联创新。

更多推荐