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) }
)
}
}
}
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)
)
}
}
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()
)
}
}
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()}")
}
}
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)