Customization
ChromaDial’s power comes from its fully customizable thumb and track composables. Both receive a DialState object that provides all the information needed to create rich, interactive designs.
Custom Thumb
Section titled “Custom Thumb”The thumb is the draggable handle that users interact with. It’s positioned and rotated automatically by the Dial.
Basic Custom Thumb
Section titled “Basic Custom Thumb”Dial( degree = degree, onDegreeChanged = { degree = it }, thumb = { state -> Box( Modifier .size(32.dp) .background( brush = Brush.verticalGradient( colors = listOf(Color.Blue, Color.Cyan) ), shape = CircleShape ) .border(2.dp, Color.White, CircleShape) ) },)Invisible Thumb (Track-Only Dial)
Section titled “Invisible Thumb (Track-Only Dial)”For designs where the track shows all the visual feedback:
thumb = { Box(Modifier.fillMaxSize()) }Custom Track
Section titled “Custom Track”The track is the background that shows the dial’s path and can display progress, tick marks, or any custom graphics.
Simple Arc Track
Section titled “Simple Arc Track”track = { state -> Box( Modifier .fillMaxSize() .drawBehind { // Background arc drawArc( color = Color.Gray.copy(alpha = 0.3f), startAngle = state.degreeRange.start - 90f, sweepAngle = state.degreeRange.endInclusive - state.degreeRange.start, topLeft = Offset(16.dp.toPx(), 16.dp.toPx()), size = Size( state.radius * 2 - 32.dp.toPx(), state.radius * 2 - 32.dp.toPx() ), useCenter = false, style = Stroke(width = 32.dp.toPx(), cap = StrokeCap.Round) )
// Progress arc drawArc( color = Color.Blue, startAngle = state.degreeRange.start - 90f, sweepAngle = state.degree - state.degreeRange.start, topLeft = Offset(16.dp.toPx(), 16.dp.toPx()), size = Size( state.radius * 2 - 32.dp.toPx(), state.radius * 2 - 32.dp.toPx() ), useCenter = false, style = Stroke(width = 32.dp.toPx(), cap = StrokeCap.Round) ) } )}Track with Tick Marks
Section titled “Track with Tick Marks”Use the drawEveryStep utility to draw tick marks at step positions:
track = { state -> Box( Modifier .fillMaxSize() .drawBehind { drawEveryStep( dialState = state, steps = 12, padding = 16.dp, ) { position, degrees, inActiveRange -> rotate(degrees, pivot = position) { drawLine( color = if (inActiveRange) Color.Blue else Color.Gray, start = position, end = position + Offset(0f, 15f), strokeWidth = 2.dp.toPx() ) } } } )}Track with Value Display
Section titled “Track with Value Display”track = { state -> Box(Modifier.fillMaxSize()) { Text( text = "${(state.value * 100).toInt()}%", modifier = Modifier.align(Alignment.Center), fontSize = 24.sp, fontWeight = FontWeight.Bold ) }}Using DialState
Section titled “Using DialState”Both thumb and track receive a DialState object with these useful properties:
| Property | Type | Description |
|---|---|---|
degree | Float | Current rotation in degrees |
value | Float | Normalized 0-1 value based on position within range |
degreeRange | ClosedFloatingPointRange<Float> | Allowed rotation range |
radius | Float | Calculated radius in pixels |
thumbSize | Float | Measured thumb size in pixels |
overshotAngle | Float | Amount dragged beyond limits (for rubber-band effects) |
Using the value Property
Section titled “Using the value Property”The value property normalizes the current degree to a 0-1 range:
track = { state -> // state.value is 0.0 at degreeRange.start // state.value is 1.0 at degreeRange.endInclusive val percentage = (state.value * 100).toInt()}Using overshotAngle
Section titled “Using overshotAngle”Create rubber-band effects when users drag beyond the limits:
thumb = { state -> val scale = 1f - (state.overshotAngle.absoluteValue / 180f).coerceIn(0f, 0.3f) Box( Modifier .size(32.dp) .graphicsLayer { scaleX = scale; scaleY = scale } .background(Color.Blue, CircleShape) )}StepContent Composable
Section titled “StepContent Composable”For placing composables at step positions around the dial, use StepContent:
track = { state -> StepContent( modifier = Modifier.fillMaxSize(), degreeRange = state.degreeRange, steps = 10, ) { index, position, degree, inActiveRange -> Text( text = "$index", color = if (inActiveRange) Color.Blue else Color.Gray, fontSize = 12.sp ) }}Animation Integration
Section titled “Animation Integration”Combine with Compose animations for smooth interactions:
var degree by remember { mutableFloatStateOf(0f) }val animatedDegree by animateFloatAsState( targetValue = degree, animationSpec = spring( stiffness = Spring.StiffnessHigh, dampingRatio = Spring.DampingRatioLowBouncy, ))
Dial( degree = animatedDegree, onDegreeChanged = { degree = it }, steps = 10, // Combine with steps for bouncy snapping)Complete Example: Gradient Arc Dial
Section titled “Complete Example: Gradient Arc Dial”@Composablefun GradientArcDial() { var degree by remember { mutableFloatStateOf(270f) }
Dial( degree = degree, onDegreeChanged = { degree = it }, modifier = Modifier.size(200.dp, 100.dp), startDegrees = 270f, sweepDegrees = 180f, thumb = { state -> Box( Modifier .size(24.dp) .background( brush = Brush.radialGradient( colors = listOf(Color.White, Color.Blue) ), shape = CircleShape ) .border(2.dp, Color.White, CircleShape) ) }, track = { state -> Box( Modifier .fillMaxSize() .drawBehind { val strokeWidth = 16.dp.toPx() val arcSize = Size( state.radius * 2 - strokeWidth, state.radius * 2 - strokeWidth )
// Background drawArc( color = Color.Gray.copy(alpha = 0.2f), startAngle = 180f, sweepAngle = 180f, topLeft = Offset(strokeWidth / 2, strokeWidth / 2), size = arcSize, useCenter = false, style = Stroke(strokeWidth, cap = StrokeCap.Round) )
// Progress val progress = state.value * 180f drawArc( brush = Brush.sweepGradient( colors = listOf(Color.Cyan, Color.Blue, Color.Magenta) ), startAngle = 180f, sweepAngle = progress, topLeft = Offset(strokeWidth / 2, strokeWidth / 2), size = arcSize, useCenter = false, style = Stroke(strokeWidth, cap = StrokeCap.Round) ) } ) { Text( text = "${(state.value * 100).toInt()}%", modifier = Modifier.align(Alignment.BottomCenter), fontSize = 20.sp, fontWeight = FontWeight.Bold ) } } )}Next Steps
Section titled “Next Steps”- DialState Reference - Complete state API documentation
- RadiusMode Reference - Understand radius calculation