import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.foundation.background
import androidx.compose.foundation.hoverable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.interaction.collectIsFocusedAsState
import androidx.compose.foundation.interaction.collectIsHoveredAsState
import androidx.compose.foundation.interaction.collectIsPressedAsState
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import com.composeunstyled.UnstyledSlider
@Composable
fun SliderDemo() {
Box(
modifier = Modifier.fillMaxSize(),
contentAlignment = Alignment.Center,
) {
val interactionSource = remember { MutableInteractionSource() }
val isFocused by interactionSource.collectIsFocusedAsState()
val isPressed by interactionSource.collectIsPressedAsState()
var value by remember { mutableFloatStateOf(0.7f) }
Box(
modifier = Modifier
.padding(horizontal = 16.dp)
.widthIn(max = 480.dp)
.fillMaxWidth(),
) {
UnstyledSlider(
interactionSource = interactionSource,
value = value,
onValueChange = { value = it },
modifier = Modifier.fillMaxWidth(),
track = { state ->
Box(
Modifier
.fillMaxWidth()
.height(8.dp)
.padding(horizontal = 16.dp)
.clip(RoundedCornerShape(100.dp)),
) {
// the 'not yet completed' part of the track
Box(
Modifier
.fillMaxHeight()
.fillMaxWidth()
.background(Color(0xFFCACACA)),
)
// the 'completed' part of the track
Box(
Modifier
.fillMaxHeight()
.fillMaxWidth(state.fraction)
.background(Color.Black),
)
}
},
thumb = {
val thumbSize by animateDpAsState(targetValue = if (isPressed) 22.dp else 18.dp)
val thumbInteractionSource = remember { MutableInteractionSource() }
val isHovered by thumbInteractionSource.collectIsHoveredAsState()
val glowColor by animateColorAsState(
if (isFocused || isHovered) Color.Black.copy(0.16f) else Color.Transparent,
)
// keep the size fixed to ensure that the resizing animation is always centered
Box(
modifier = Modifier.size(36.dp).clip(CircleShape).background(glowColor),
contentAlignment = Alignment.Center,
) {
Box(
modifier = Modifier
.size(thumbSize)
.hoverable(thumbInteractionSource)
.clip(CircleShape)
.background(Color.Black),
)
}
},
)
}
}
}Features
- Horizontal and vertical sliders
- Discrete step support
- Custom track and thumb slots
- Keyboard and screen reader value changes
Installation
implementation("com.composables:composeunstyled-slider")
Anatomy
UnstyledSlider(
value = value,
onValueChange = onValueChange,
track = {
},
thumb = {
},
)
Concepts
UnstyledSliderrepresents the interactive range users can drag or adjust.SliderStateis passed to the track and thumb slots.- The
trackslot renders below the thumb. - The
thumbslot renders at the current slider offset.
Accessibility
UnstyledSlider exposes progress semantics and supports arrow keys, Page Up, Page Down, Home, and End.
Code Examples
Creating a stepped slider
Use the steps parameter to snap the slider value to discrete stops:
UnstyledSlider(
value = value,
onValueChange = { value = it },
steps = 4,
track = { state ->
BasicText("${state.value}")
},
thumb = { state ->
BasicText("${state.value}")
},
)
Using a custom value range
Use the valueRange parameter when the slider value is not from 0f to 1f:
UnstyledSlider(
value = volume,
onValueChange = { volume = it },
valueRange = 0f..100f,
track = { state ->
BasicText("${state.value}")
},
thumb = { state ->
BasicText("${state.value}")
},
)
Creating a vertical slider
Use the orientation parameter to make the slider vertical:
UnstyledSlider(
value = value,
onValueChange = { value = it },
orientation = Orientation.Vertical,
track = { state ->
BasicText("${state.value}")
},
thumb = { state ->
BasicText("${state.value}")
},
)
Running code after value changes finish
Use the onValueChangeFinished callback to react after drag, tap, keyboard, or screen reader changes finish:
UnstyledSlider(
value = value,
onValueChange = { value = it },
onValueChangeFinished = { save(value) },
track = { state ->
BasicText("${state.value}")
},
thumb = { state ->
BasicText("${state.value}")
},
)
API Reference
SliderState
| Parameter | Type | Description |
|---|---|---|
valueRange |
ClosedFloatingPointRange<Float> |
The range of values the slider can take. |
steps |
Int |
The number of discrete steps in the slider. |
enabled |
Boolean |
Whether the slider can receive user input. |
orientation |
Orientation |
Horizontal or vertical slider orientation. |
isRtl |
Boolean |
|
isDragging |
Boolean |
|
isPressed |
Boolean |
|
isFocused |
Boolean |
|
tickFractions |
FloatArray |
|
value |
Float |
The current value of the slider. |
fraction |
Float |
UnstyledSlider
| Parameter | Type | Description |
|---|---|---|
value |
Float |
The controlled value of the slider. |
onValueChange |
(Float) -> Unit |
Callback invoked when the user changes the value. |
modifier |
Modifier |
Modifier to be applied to the slider. |
enabled |
Boolean |
Whether the slider can receive user input. |
interactionSource |
MutableInteractionSource? |
Interaction source for press, focus, and drag interactions. |
valueRange |
ClosedFloatingPointRange<Float> |
The range of values the slider can take. |
steps |
Int |
The number of discrete stops between the start and end. |
onValueChangeFinished |
(() -> Unit)? |
|
orientation |
Orientation |
Horizontal or vertical slider orientation. |
reverseDirection |
Boolean |
Whether to reverse the visual and input direction. |
track |
(SliderState) -> Unit |
Composable function to define the track of the slider. |
thumb |
(SliderState) -> Unit |
Composable function to define the thumb of the slider. |