DEV Community

myougaTheAxo
myougaTheAxo

Posted on

Color Picker in Compose — Canvas-Based HSV Color Selection UI

Color Picker in Compose — Canvas-Based HSV Color Selection UI

Building a color picker UI in Jetpack Compose is a great way to master Canvas drawing and gesture detection. Let me walk you through a complete implementation with preset colors, hue bar, and saturation/value sliders.

Preset Colors with FlowRow

Start with a simple preset color palette using FlowRow and colored Box circles:

@Composable
fun PresetColors(
    onColorSelected: (Color) -> Unit,
    modifier: Modifier = Modifier
) {
    val presets = listOf(
        Color.Red, Color.Green, Color.Blue,
        Color.Yellow, Color.Cyan, Color.Magenta,
        Color.Black, Color.White, Color.Gray
    )

    FlowRow(
        modifier = modifier.padding(16.dp),
        horizontalArrangement = Arrangement.spacedBy(8.dp)
    ) {
        presets.forEach { color ->
            Box(
                modifier = Modifier
                    .size(40.dp)
                    .background(color, shape = CircleShape)
                    .clickable { onColorSelected(color) }
            )
        }
    }
}
Enter fullscreen mode Exit fullscreen mode

Hue Bar with Canvas and Gesture Detection

Create an interactive hue bar using Canvas and detectDragGestures:

@Composable
fun HueBar(
    hue: Float,
    onHueChange: (Float) -> Unit,
    modifier: Modifier = Modifier
) {
    var dragOffset by remember { mutableStateOf(0f) }

    Canvas(
        modifier = modifier
            .fillMaxWidth()
            .height(40.dp)
            .pointerInput(Unit) {
                detectDragGestures { change, _ ->
                    dragOffset = change.position.x.coerceIn(0f, size.width.toFloat())
                    val newHue = (dragOffset / size.width) * 360f
                    onHueChange(newHue)
                    change.consume()
                }
            }
    ) {
        // Draw hue gradient
        val colors = (0..360 step 10).map { h ->
            Color.hsv(h.toFloat(), 1f, 1f)
        }
        drawRect(
            brush = LinearGradientShader(
                colors = colors,
                from = Offset.Zero,
                to = Offset(size.width, 0f)
            )
        )

        // Draw indicator circle
        val indicatorX = (hue / 360f) * size.width
        drawCircle(
            color = Color.White,
            radius = 8.dp.toPx(),
            center = Offset(indicatorX, size.height / 2f)
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Full Color Picker with Sliders

Combine everything with saturation and value sliders:

@Composable
fun FullColorPicker(
    selectedColor: Color,
    onColorChange: (Color) -> Unit,
    modifier: Modifier = Modifier
) {
    var hue by remember { mutableStateOf(0f) }
    var saturation by remember { mutableStateOf(1f) }
    var value by remember { mutableStateOf(1f) }

    Column(modifier = modifier.padding(16.dp)) {
        // Preview
        Box(
            modifier = Modifier
                .fillMaxWidth()
                .height(100.dp)
                .background(Color.hsv(hue, saturation, value))
                .border(2.dp, Color.Gray)
        )

        Spacer(modifier = Modifier.height(16.dp))

        // Hue
        Text("Hue: ${hue.toInt()}°")
        HueBar(hue = hue, onHueChange = { newHue ->
            hue = newHue
            onColorChange(Color.hsv(hue, saturation, value))
        })

        Spacer(modifier = Modifier.height(12.dp))

        // Saturation
        Text("Saturation: ${(saturation * 100).toInt()}%")
        Slider(
            value = saturation,
            onValueChange = { newSat ->
                saturation = newSat
                onColorChange(Color.hsv(hue, saturation, value))
            },
            modifier = Modifier.fillMaxWidth()
        )

        Spacer(modifier = Modifier.height(12.dp))

        // Value
        Text("Value: ${(value * 100).toInt()}%")
        Slider(
            value = value,
            onValueChange = { newVal ->
                value = newVal
                onColorChange(Color.hsv(hue, saturation, value))
            },
            modifier = Modifier.fillMaxWidth()
        )
    }
}
Enter fullscreen mode Exit fullscreen mode

Usage

@Composable
fun ColorPickerScreen() {
    var pickedColor by remember { mutableStateOf(Color.Red) }

    Column {
        FullColorPicker(
            selectedColor = pickedColor,
            onColorChange = { pickedColor = it }
        )

        Text("Selected: #${pickedColor.toArgb().toUInt().toString(16).uppercase()}")
    }
}
Enter fullscreen mode Exit fullscreen mode

This implementation gives you full control over HSV color selection with a visual hue gradient and smooth gesture handling.


8 Android app templates with custom UI designs available: Gumroad

Top comments (0)