Flutter中如何对比两种颜色的相似度与差异?
在Flutter中实现颜色对比与匹配工具
要开发类似颜色匹配工具的Flutter应用,核心是实现颜色色差计算,再搭配颜色选择、结果展示的UI组件即可。以下是具体实现步骤:
核心:颜色对比函数
专业的颜色对比通常采用CIEDE2000色差公式,它能准确反映人眼对颜色差异的感知,比简单的RGB差值更精准。下面是基于Dart实现的CIEDE2000计算函数:
import 'dart:math'; // 将RGB颜色转换为Lab色彩空间 List<double> rgbToLab(int r, int g, int b) { // 先转换为XYZ空间 double rLinear = r / 255.0; double gLinear = g / 255.0; double bLinear = b / 255.0; rLinear = rLinear > 0.04045 ? pow((rLinear + 0.055) / 1.055, 2.4) : rLinear / 12.92; gLinear = gLinear > 0.04045 ? pow((gLinear + 0.055) / 1.055, 2.4) : gLinear / 12.92; bLinear = bLinear > 0.04045 ? pow((bLinear + 0.055) / 1.055, 2.4) : bLinear / 12.92; double x = rLinear * 0.4124 + gLinear * 0.3576 + bLinear * 0.1805; double y = rLinear * 0.2126 + gLinear * 0.7152 + bLinear * 0.0722; double z = rLinear * 0.0193 + gLinear * 0.1192 + bLinear * 0.9505; // 转换为Lab空间 x /= 0.95047; y /= 1.0; z /= 1.08883; x = x > 0.008856 ? pow(x, 1/3) : (7.787 * x) + 16/116; y = y > 0.008856 ? pow(y, 1/3) : (7.787 * y) + 16/116; z = z > 0.008856 ? pow(z, 1/3) : (7.787 * z) + 16/116; double l = (116 * y) - 16; double a = 500 * (x - y); double b = 200 * (y - z); return [l, a, b]; } // CIEDE2000色差计算函数,返回两个颜色的差值,值越小越相似 double calculateColorDifference(Color color1, Color color2) { List<double> lab1 = rgbToLab(color1.red, color1.green, color1.blue); List<double> lab2 = rgbToLab(color2.red, color2.green, color2.blue); double l1 = lab1[0], a1 = lab1[1], b1 = lab1[2]; double l2 = lab2[0], a2 = lab2[1], b2 = lab2[2]; double c1 = sqrt(pow(a1, 2) + pow(b1, 2)); double c2 = sqrt(pow(a2, 2) + pow(b2, 2)); double cAvg = (c1 + c2) / 2; double g = 0.5 * (1 - sqrt(pow(cAvg, 7) / (pow(cAvg, 7) + pow(25, 7)))); double a1Prime = a1 * (1 + g); double a2Prime = a2 * (1 + g); double c1Prime = sqrt(pow(a1Prime, 2) + pow(b1, 2)); double c2Prime = sqrt(pow(a2Prime, 2) + pow(b2, 2)); double h1Prime = atan2(b1, a1Prime); h1Prime = h1Prime < 0 ? h1Prime + 2 * pi : h1Prime; double h2Prime = atan2(b2, a2Prime); h2Prime = h2Prime < 0 ? h2Prime + 2 * pi : h2Prime; double deltaLPrime = l2 - l1; double deltaCPrime = c2Prime - c1Prime; double deltaHPrime; if (c1Prime * c2Prime == 0) { deltaHPrime = 0; } else { if (abs(h2Prime - h1Prime) <= pi) { deltaHPrime = h2Prime - h1Prime; } else { deltaHPrime = h2Prime - h1Prime > 0 ? h2Prime - h1Prime - 2 * pi : h2Prime - h1Prime + 2 * pi; } } double deltaHPrimeSquared = pow(deltaHPrime, 2); double deltaH = 2 * sqrt(c1Prime * c2Prime) * sin(deltaHPrime / 2); double lAvgPrime = (l1 + l2) / 2; double cAvgPrime = (c1Prime + c2Prime) / 2; double hAvgPrime; if (c1Prime * c2Prime == 0) { hAvgPrime = h1Prime + h2Prime; } else { if (abs(h1Prime - h2Prime) <= pi) { hAvgPrime = (h1Prime + h2Prime) / 2; } else { hAvgPrime = (h1Prime + h2Prime) / 2 > pi ? (h1Prime + h2Prime) / 2 - pi : (h1Prime + h2Prime) / 2 + pi; } } double t = 1 - 0.17 * cos(hAvgPrime - pi/6) + 0.24 * cos(2 * hAvgPrime) + 0.32 * cos(3 * hAvgPrime + pi/30) - 0.20 * cos(4 * hAvgPrime - 63 * pi/180); double deltaTheta = 30 * pi/180 * exp(-pow((hAvgPrime - 275 * pi/180) / (25 * pi/180), 2)); double rC = 2 * sqrt(pow(cAvgPrime, 7) / (pow(cAvgPrime, 7) + pow(25, 7))); double rT = -sin(2 * deltaTheta) * rC; double sl = 1 + (0.015 * pow(lAvgPrime - 50, 2)) / sqrt(20 + pow(lAvgPrime - 50, 2)); double sc = 1 + 0.045 * cAvgPrime; double sh = 1 + 0.015 * cAvgPrime * t; double deltaE = sqrt( pow(deltaLPrime / sl, 2) + pow(deltaCPrime / sc, 2) + pow(deltaH / sh, 2) + rT * (deltaCPrime / sc) * (deltaH / sh) ); return deltaE; }
这个函数接收两个Color对象,返回CIEDE2000色差数值:
- 差值≤1:人眼几乎无法区分
- 1<差值≤3:轻微差异
- 3<差值≤10:明显差异
- 差值>10:显著差异
UI实现示例
结合颜色选择器和结果展示,快速搭建核心界面:
- 先在
pubspec.yaml中添加颜色选择器依赖:
dependencies: flutter: sdk: flutter flutter_colorpicker: ^1.0.3
- 实现主界面:
import 'package:flutter/material.dart'; import 'package:flutter_colorpicker/flutter_colorpicker.dart'; void main() => runApp(const ColorMatcherApp()); class ColorMatcherApp extends StatelessWidget { const ColorMatcherApp({super.key}); @override Widget build(BuildContext context) { return MaterialApp( title: '颜色匹配工具', theme: ThemeData(primarySwatch: Colors.blue), home: const ColorMatcherPage(), ); } } class ColorMatcherPage extends StatefulWidget { const ColorMatcherPage({super.key}); @override State<ColorMatcherPage> createState() => _ColorMatcherPageState(); } class _ColorMatcherPageState extends State<ColorMatcherPage> { Color _color1 = Colors.red; Color _color2 = Colors.blue; double _colorDifference = 0; @override void initState() { super.initState(); _updateDifference(); } void _updateDifference() { setState(() { _colorDifference = calculateColorDifference(_color1, _color2); }); } void _showColorPicker(Color currentColor, Function(Color) onChanged) { showDialog( context: context, builder: (context) => AlertDialog( title: const Text('选择颜色'), content: SingleChildScrollView( child: ColorPicker( pickerColor: currentColor, onColorChanged: onChanged, showLabel: true, pickerAreaHeightPercent: 0.8, ), ), actions: [ TextButton( onPressed: () => Navigator.pop(context), child: const Text('确定'), ), ], ), ).then((_) => _updateDifference()); } @override Widget build(BuildContext context) { return Scaffold( appBar: AppBar(title: const Text('颜色匹配工具')), body: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.stretch, children: [ // 颜色展示区域 Row( children: [ Expanded( child: GestureDetector( onTap: () => _showColorPicker(_color1, (color) => setState(() => _color1 = color)), child: Container( height: 150, color: _color1, child: Center( child: Text( '颜色1\n#${_color1.value.toRadixString(16).substring(2).toUpperCase()}', style: TextStyle( color: _color1.computeLuminance() > 0.5 ? Colors.black : Colors.white, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), ), ), const SizedBox(width: 16), Expanded( child: GestureDetector( onTap: () => _showColorPicker(_color2, (color) => setState(() => _color2 = color)), child: Container( height: 150, color: _color2, child: Center( child: Text( '颜色2\n#${_color2.value.toRadixString(16).substring(2).toUpperCase()}', style: TextStyle( color: _color2.computeLuminance() > 0.5 ? Colors.black : Colors.white, fontWeight: FontWeight.bold, ), textAlign: TextAlign.center, ), ), ), ), ), ], ), const SizedBox(height: 32), // 色差结果展示 Card( elevation: 4, child: Padding( padding: const EdgeInsets.all(16.0), child: Column( crossAxisAlignment: CrossAxisAlignment.start, children: [ const Text('色差分析', style: TextStyle(fontSize: 18, fontWeight: FontWeight.bold)), const SizedBox(height: 8), Text('CIEDE2000差值: ${_colorDifference.toStringAsFixed(2)}'), const SizedBox(height: 8), Text(_getDifferenceDescription(_colorDifference)), ], ), ), ), ], ), ), ); } String _getDifferenceDescription(double deltaE) { if (deltaE <= 1) return '人眼几乎无法区分这两种颜色'; if (deltaE <= 3) return '两种颜色存在轻微差异'; if (deltaE <= 10) return '两种颜色差异明显'; return '两种颜色差异显著'; } }
扩展功能建议
- 添加颜色和谐方案:计算互补色、类似色、分裂互补色等,可基于HSL色彩空间实现
- 支持颜色格式转换:在RGB、HEX、HSL之间切换显示
- 增加对比度检测:判断文本在背景色上的可读性(符合WCAG标准)
- 保存配色方案:允许用户收藏常用的颜色组合
内容的提问来源于stack exchange,提问作者Michal Ferrari
相关产品推荐
相关产品推荐

