DialState
DialState is the state holder class that manages the dial’s rotation state and provides information to custom thumb and track composables.
Class Definition
Section titled “Class Definition”@Stableclass DialState( initialDegree: Float, val degreeRange: ClosedFloatingPointRange<Float>, val interval: Float = 0f, val radiusMode: RadiusMode = RadiusMode.WIDTH, var onDegreeChangeFinished: (() -> Unit)? = null, val startDegrees: Float = 0f, val valueRange: ClosedFloatingPointRange<Float> = 0f..1f, val clockwise: Boolean = true,)Use rememberDialState() to create and remember a DialState in a composable.
Constructor Parameters
Section titled “Constructor Parameters”initialDegree
Section titled “initialDegree”Type: Float
The initial rotation angle in degrees.
degreeRange
Section titled “degreeRange”Type: ClosedFloatingPointRange<Float>
The allowed range of rotation. The dial will clamp values to this range. This is always 0f..sweepDegrees.
interval
Section titled “interval”Type: Float
Default: 0f
The degree interval between snap points. When 0f, rotation is continuous. When set to a value like 15f, the dial snaps every 15 degrees. The end of the range is always a valid snap point.
radiusMode
Section titled “radiusMode”Type: RadiusMode
Default: RadiusMode.WIDTH
How to calculate the radius from the dial’s constraints.
onDegreeChangeFinished
Section titled “onDegreeChangeFinished”Type: (() -> Unit)?
Default: null
Callback invoked when dragging ends.
startDegrees
Section titled “startDegrees”Type: Float
Default: 0f
The absolute starting angle for the dial arc in screen coordinates. Used internally for positioning the thumb and track.
valueRange
Section titled “valueRange”Type: ClosedFloatingPointRange<Float>
Default: 0f..1f
The range that mappedValue maps to. Use this to get values in a custom domain (e.g., 0f..100f for percentages).
clockwise
Section titled “clockwise”Type: Boolean
Default: true
When false, the dial rotates counterclockwise.
Properties
Section titled “Properties”degree
Section titled “degree”Type: Float (read/write)
The current rotation angle in degrees, relative to startDegrees. This value ranges from degreeRange.start to degreeRange.endInclusive (typically 0 to sweepDegrees).
val currentAngle = state.degreeabsoluteDegree
Section titled “absoluteDegree”Type: Float (read-only)
The absolute rotation angle in screen coordinates. For clockwise dials this is startDegrees + degree; for counterclockwise dials this is startDegrees - degree. Use this for drawing operations that need the actual screen angle.
// For a clockwise dial with startDegrees=270f and degree=90f:// absoluteDegree = 360fval screenAngle = state.absoluteDegreeType: Float (read-only)
A normalized value between 0 and 1 representing the position within the degree range.
- Returns
0fwhendegreeequalsdegreeRange.start - Returns
1fwhendegreeequalsdegreeRange.endInclusive
val percentage = state.value * 100 // 0 to 100Formula:
value = (degree - degreeRange.start) / (degreeRange.endInclusive - degreeRange.start)mappedValue
Section titled “mappedValue”Type: Float (read-only)
value mapped to valueRange. Useful when you want values in a custom unit:
// With valueRange = 0f..100fval temperature = state.mappedValue // 0.0 to 100.0Formula:
mappedValue = valueRange.start + value * (valueRange.endInclusive - valueRange.start)degreeRange
Section titled “degreeRange”Type: ClosedFloatingPointRange<Float> (read-only)
The allowed range for the degree property. This is always 0f..sweepDegrees.
val totalSweep = state.degreeRange.endInclusive - state.degreeRange.startval isAtEnd = state.degree == state.degreeRange.endInclusiveinterval
Section titled “interval”Type: Float (read-only)
The degree interval between snap points. When 0f, the dial rotates continuously.
radiusMode
Section titled “radiusMode”Type: RadiusMode (read-only)
The radius calculation mode, as specified during construction.
radius
Section titled “radius”Type: Float (read-only, internally set)
The calculated radius in pixels. Set by the Dial composable based on constraints and radiusMode.
val arcRadius = state.radius - 12.dp.toPx()thumbSize
Section titled “thumbSize”Type: Float (read-only, internally set)
The measured size of the thumb composable in pixels. Set after the thumb is measured.
clockwise
Section titled “clockwise”Type: Boolean (read-only)
Whether the dial rotates clockwise. Affects absoluteDegree and drag direction.
enabled
Section titled “enabled”Type: Boolean (read-only, set by Dial composable)
Whether the dial responds to drag input.
overshootDegrees
Section titled “overshootDegrees”Type: Float (read-only, computed)
The rendered overshoot offset when the user drags beyond the allowed range. This value is decay-adjusted (non-linear) for a natural rubber-band feel.
- Negative when dragging below
degreeRange.start - Positive when dragging above
degreeRange.endInclusive 0fwhen within range or after drag ends (springs back to zero)
// The track uses this to extend the active arc visually during overshootval overshoot = state.overshootDegreesovershootDecay
Section titled “overshootDecay”Type: Float
Default: 0.5f
Controls how strongly the raw drag overshoot is dampened. 0f means no dampening (linear); 1f means full dampening (no visible overshoot).
overshootAnimationSpec
Section titled “overshootAnimationSpec”Type: AnimationSpec<Float>
Default: spring()
The animation spec used when springing back from an overshoot position after the drag ends.
Methods
Section titled “Methods”animateTo
Section titled “animateTo”suspend fun animateTo( targetDegree: Float, animationSpec: AnimationSpec<Float> = spring(),)Animates degree to targetDegree. Must be called from a coroutine scope. The target is clamped to degreeRange.
val scope = rememberCoroutineScope()Button(onClick = { scope.launch { state.animateTo(180f) } }) { Text("Go to center")}calculateSnappedValue
Section titled “calculateSnappedValue”fun calculateSnappedValue(value: Float): FloatReturns the nearest snap position for a given degree value. Used internally by the Dial.
- If
interval == 0f, returns the value clamped to the range (no snapping) - Otherwise, returns the nearest snap position based on the interval
- The end of the range is always a valid snap point
Callbacks
Section titled “Callbacks”onValueChange
Section titled “onValueChange”Type: (Float) -> Unit
Internal callback used by the Dial to notify of value changes. Set to the onDegreeChange parameter.
onDegreeChangeFinished
Section titled “onDegreeChangeFinished”Type: (() -> Unit)?
Callback invoked when the user finishes dragging. Can be set via constructor or directly on the property.
Usage in Custom Composables
Section titled “Usage in Custom Composables”In Thumb Composable
Section titled “In Thumb Composable”thumb = { state -> Box( Modifier .size(32.dp) .graphicsLayer { alpha = if (state.overshootDegrees != 0f) 0.7f else 1f } .background(Color.Blue, CircleShape) )}In Track Composable
Section titled “In Track Composable”track = { state -> Box( Modifier .fillMaxSize() .drawBehind { val sweepAngle = state.degreeRange.endInclusive - state.degreeRange.start
// Background arc drawArc( color = Gray300, startAngle = state.startDegrees, sweepAngle = sweepAngle, radius = state.radius, )
// Progress arc drawArc( color = Blue500, startAngle = state.startDegrees, sweepAngle = state.degree, radius = state.radius, ) } )}See Also
Section titled “See Also”- Dial Basics - Learn how to use the Dial component
- RadiusMode - Understand radius calculation modes