Flutter实战:打造专业级颜色选择器,色轮/HSV/RGB三模式切换

颜色选择器是设计工具和图形应用的核心组件。本文将用Flutter实现一款功能完善的颜色选择器,支持色轮、HSV、RGB三种选色模式,提供多种颜色格式输出,并支持颜色收藏功能。

效果预览图
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

功能特性

  • 🎨 色轮选择:直观的色相环,拖动选取颜色
  • 🌈 HSV模式:色相/饱和度/明度独立调节
  • 🔴🟢🔵 RGB模式:红/绿/蓝三通道滑块
  • 📋 多格式输出:HEX、RGB、RGBA、HSV、Flutter Color
  • 💾 颜色收藏:保存常用颜色,快速调用
  • 📝 HEX输入:直接输入十六进制颜色值

颜色模型基础

RGB 与 HSV

转换

HSV

色相 0-360°

饱和度 0-100%

明度 0-100%

RGB

红 0-255

绿 0-255

蓝 0-255

模型 维度 范围 特点
RGB 红、绿、蓝 0-255 计算机原生,加色混合
HSV 色相、饱和度、明度 H:0-360°, S/V:0-100% 更符合人类直觉
HEX 十六进制 #000000-#FFFFFF 网页常用格式

颜色转换

Flutter 内置了 HSVColor 类,可以方便地进行 RGB 和 HSV 之间的转换:

// RGB -> HSV
final hsv = HSVColor.fromColor(color);
double hue = hsv.hue;           // 0-360
double saturation = hsv.saturation;  // 0-1
double value = hsv.value;       // 0-1

// HSV -> RGB
final color = HSVColor.fromAHSV(1, hue, saturation, value).toColor();
int red = color.red;
int green = color.green;
int blue = color.blue;

// RGB -> HEX
String hex = '#${color.value.toRadixString(16).substring(2).toUpperCase()}';

核心实现

状态管理

颜色选择器需要同步维护 RGB 和 HSV 两套值:

class _ColorPickerAppState extends State<ColorPickerApp> {
  Color _currentColor = Colors.blue;

  // HSV值
  double _hue = 210;
  double _saturation = 1.0;
  double _value = 1.0;

  // RGB值
  int _red = 0;
  int _green = 0;
  int _blue = 255;

  // 从Color更新所有值
  void _updateFromColor(Color color) {
    final hsv = HSVColor.fromColor(color);
    setState(() {
      _currentColor = color;
      _hue = hsv.hue;
      _saturation = hsv.saturation;
      _value = hsv.value;
      _red = color.red;
      _green = color.green;
      _blue = color.blue;
    });
  }

  // 从HSV更新
  void _updateFromHSV() {
    setState(() {
      _currentColor = HSVColor.fromAHSV(1, _hue, _saturation, _value).toColor();
      _red = _currentColor.red;
      _green = _currentColor.green;
      _blue = _currentColor.blue;
    });
  }

  // 从RGB更新
  void _updateFromRGB() {
    setState(() {
      _currentColor = Color.fromRGBO(_red, _green, _blue, 1);
      final hsv = HSVColor.fromColor(_currentColor);
      _hue = hsv.hue;
      _saturation = hsv.saturation;
      _value = hsv.value;
    });
  }
}

色相环绘制

使用 CustomPainter 绘制色相环,通过绘制360个弧形片段实现渐变效果:

class ColorWheelPainter extends CustomPainter {
  final double selectedHue;

  ColorWheelPainter(this.selectedHue);

  
  void paint(Canvas canvas, Size size) {
    final center = Offset(size.width / 2, size.height / 2);
    final radius = size.width / 2;

    // 绘制360个色相片段
    for (int i = 0; i < 360; i++) {
      final paint = Paint()
        ..color = HSVColor.fromAHSV(1, i.toDouble(), 1, 1).toColor()
        ..style = PaintingStyle.stroke
        ..strokeWidth = 30;

      final startAngle = (i - 90) * pi / 180;  // 从顶部开始
      final sweepAngle = pi / 180;  // 1度

      canvas.drawArc(
        Rect.fromCircle(center: center, radius: radius - 15),
        startAngle,
        sweepAngle,
        false,
        paint,
      );
    }

    // 绘制选中指示器
    final indicatorAngle = (selectedHue - 90) * pi / 180;
    final indicatorX = center.dx + (radius - 15) * cos(indicatorAngle);
    final indicatorY = center.dy + (radius - 15) * sin(indicatorAngle);

    // 白色外圈
    canvas.drawCircle(
      Offset(indicatorX, indicatorY),
      12,
      Paint()..color = Colors.white,
    );
    // 黑色边框
    canvas.drawCircle(
      Offset(indicatorX, indicatorY),
      12,
      Paint()
        ..color = Colors.black
        ..style = PaintingStyle.stroke
        ..strokeWidth = 2,
    );
    // 当前颜色填充
    canvas.drawCircle(
      Offset(indicatorX, indicatorY),
      8,
      Paint()..color = HSVColor.fromAHSV(1, selectedHue, 1, 1).toColor(),
    );
  }
}

色相环交互

通过计算触摸点相对于圆心的角度来确定色相值:

GestureDetector(
  onPanUpdate: (details) {
    final center = const Offset(140, 140);  // 圆心
    final position = details.localPosition - center;
    
    // 计算角度(弧度)
    final angle = atan2(position.dy, position.dx);
    
    // 转换为色相值(0-360)
    final hue = (angle * 180 / pi + 360) % 360;
    
    setState(() {
      _hue = hue;
      _updateFromHSV();
    });
  },
  child: CustomPaint(
    painter: ColorWheelPainter(_hue),
  ),
)

饱和度-明度面板

HSV模式下的二维选择面板,水平方向是饱和度,垂直方向是明度:

class SaturationValuePainter extends CustomPainter {
  final double hue;

  SaturationValuePainter(this.hue);

  
  void paint(Canvas canvas, Size size) {
    final rect = Rect.fromLTWH(0, 0, size.width, size.height);

    // 饱和度渐变(水平:白色 -> 纯色)
    final saturationGradient = LinearGradient(
      colors: [
        Colors.white,
        HSVColor.fromAHSV(1, hue, 1, 1).toColor(),
      ],
    );

    // 明度渐变(垂直:透明 -> 黑色)
    final valueGradient = const LinearGradient(
      begin: Alignment.topCenter,
      end: Alignment.bottomCenter,
      colors: [Colors.transparent, Colors.black],
    );

    // 先绘制饱和度
    canvas.drawRect(rect, Paint()..shader = saturationGradient.createShader(rect));
    
    // 再叠加明度
    canvas.drawRect(rect, Paint()..shader = valueGradient.createShader(rect));
  }
}

饱和度-明度面板

左上: 白色
S=0, V=1

右上: 纯色
S=1, V=1

左下: 黑色
S=0, V=0

右下: 黑色
S=1, V=0

UI组件实现

颜色预览区

显示当前颜色,并根据亮度自动调整文字颜色:

Widget _buildColorPreview() {
  // 计算亮度,决定文字颜色
  final luminance = _currentColor.computeLuminance();
  final textColor = luminance > 0.5 ? Colors.black : Colors.white;

  return Container(
    height: 140,
    color: _currentColor,
    child: Column(
      mainAxisAlignment: MainAxisAlignment.center,
      children: [
        // HEX值(可点击复制)
        GestureDetector(
          onTap: () => _copyToClipboard(_hexColor),
          child: Text(
            _hexColor,
            style: TextStyle(
              color: textColor,
              fontSize: 24,
              fontWeight: FontWeight.bold,
            ),
          ),
        ),
        // RGB和HSV格式
        Row(
          mainAxisAlignment: MainAxisAlignment.center,
          children: [
            Text('RGB($_red, $_green, $_blue)', style: TextStyle(color: textColor)),
            Text('HSV(${_hue.toInt()}°, ${(_saturation*100).toInt()}%, ${(_value*100).toInt()}%)',
                 style: TextStyle(color: textColor)),
          ],
        ),
      ],
    ),
  );
}

RGB滑块

带颜色标识的RGB通道滑块:

Widget _buildColorSlider(String label, int value, Color color, ValueChanged<int> onChanged) {
  return Row(
    children: [
      // 颜色标识圆点
      Container(
        width: 32,
        height: 32,
        decoration: BoxDecoration(
          color: color,
          shape: BoxShape.circle,
        ),
        child: Center(
          child: Text(label, style: TextStyle(color: Colors.white)),
        ),
      ),
      // 滑块
      Expanded(
        child: SliderTheme(
          data: SliderTheme.of(context).copyWith(
            activeTrackColor: color,
            thumbColor: color,
          ),
          child: Slider(
            value: value.toDouble(),
            min: 0,
            max: 255,
            onChanged: (v) => onChanged(v.toInt()),
          ),
        ),
      ),
      // 数值显示
      Text(value.toString()),
    ],
  );
}

HEX输入框

支持直接输入十六进制颜色值:

Widget _buildHexInput() {
  return Row(
    children: [
      const Text('#', style: TextStyle(fontSize: 20)),
      Expanded(
        child: TextField(
          maxLength: 6,
          decoration: InputDecoration(hintText: 'FFFFFF'),
          onSubmitted: (value) {
            if (value.length == 6) {
              try {
                final color = Color(int.parse('FF$value', radix: 16));
                _updateFromColor(color);
              } catch (_) {}
            }
          },
        ),
      ),
    ],
  );
}

颜色格式输出

提供多种常用格式,一键复制:

Widget _buildColorFormats() {
  final formats = [
    {'name': 'HEX', 'value': _hexColor},
    {'name': 'RGB', 'value': 'rgb($_red, $_green, $_blue)'},
    {'name': 'RGBA', 'value': 'rgba($_red, $_green, $_blue, 1)'},
    {'name': 'HSV', 'value': 'hsv(${_hue.toInt()}, ${(_saturation*100).toInt()}%, ${(_value*100).toInt()}%)'},
    {'name': 'Flutter', 'value': 'Color(0xFF${_hexColor.substring(1)})'},
  ];

  return Column(
    children: formats.map((f) => Row(
      children: [
        Text(f['name']!),
        Expanded(child: Text(f['value']!)),
        IconButton(
          icon: Icon(Icons.copy),
          onPressed: () => _copyToClipboard(f['value']!),
        ),
      ],
    )).toList(),
  );
}

颜色收藏功能

final List<Color> _savedColors = [];

void _saveColor() {
  if (!_savedColors.contains(_currentColor)) {
    setState(() {
      _savedColors.insert(0, _currentColor);
      if (_savedColors.length > 20) _savedColors.removeLast();  // 最多保存20个
    });
  }
}

Widget _buildSavedColors() {
  return ListView.builder(
    scrollDirection: Axis.horizontal,
    itemCount: _savedColors.length,
    itemBuilder: (context, index) {
      final color = _savedColors[index];
      return GestureDetector(
        onTap: () => _updateFromColor(color),  // 点击应用
        onLongPress: () => setState(() => _savedColors.removeAt(index)),  // 长按删除
        child: Container(
          width: 44,
          height: 44,
          color: color,
        ),
      );
    },
  );
}

应用架构

UI组件

更新方法

状态

_currentColor

_hue/_saturation/_value

_red/_green/_blue

_updateFromColor

_updateFromHSV

_updateFromRGB

色轮

HSV滑块

RGB滑块

HEX输入

预设颜色

收藏颜色

颜色预览

格式输出

数据流程

视图 State 色轮/滑块 用户 视图 State 色轮/滑块 用户 拖动/调整 更新HSV/RGB值 同步另一套值 更新_currentColor setState() 刷新所有组件

扩展建议

  1. 透明度支持:添加Alpha通道滑块
  2. 调色板:提供Material/Cupertino等预设调色板
  3. 颜色历史:记录最近使用的颜色
  4. 颜色方案:生成互补色、类似色、三角色等
  5. 图片取色:从图片中提取颜色
  6. 导出功能:导出为CSS变量、Sass变量等格式

项目结构

lib/
└── main.dart
    ├── ColorPickerApp         # 主应用(Tab切换)
    ├── _buildColorWheel()     # 色轮选择器
    ├── _buildHSVPicker()      # HSV选择器
    ├── _buildRGBPicker()      # RGB选择器
    ├── ColorWheelPainter      # 色相环绘制器
    └── SaturationValuePainter # 饱和度-明度面板绘制器

颜色格式对照

颜色 HEX RGB HSV
🔴 红 #FF0000 rgb(255,0,0) hsv(0°,100%,100%)
🟢 绿 #00FF00 rgb(0,255,0) hsv(120°,100%,100%)
🔵 蓝 #0000FF rgb(0,0,255) hsv(240°,100%,100%)
🟡 黄 #FFFF00 rgb(255,255,0) hsv(60°,100%,100%)
🟣 紫 #800080 rgb(128,0,128) hsv(300°,100%,50%)
⚫ 黑 #000000 rgb(0,0,0) hsv(0°,0%,0%)
⚪ 白 #FFFFFF rgb(255,255,255) hsv(0°,0%,100%)

总结

这个颜色选择器展示了几个关键技术点:

  1. CustomPainter:使用Canvas绘制色相环和渐变面板
  2. 手势处理:通过角度计算实现色轮交互
  3. 颜色模型转换:RGB与HSV的双向同步
  4. 亮度感知:根据颜色亮度自动调整UI元素颜色

颜色选择器是一个很好的CustomPainter练习项目,涉及到数学计算(角度、坐标)、颜色理论(RGB/HSV)和手势交互等多个知识点。
欢迎加入开源鸿蒙跨平台社区:https://openharmonycrossplatform.csdn.net

Logo

有“AI”的1024 = 2048,欢迎大家加入2048 AI社区

更多推荐