Slider
A slider component with custom track and thumb slots.
View Markdownimport 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.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import com.composeunstyled.UnstyledSlider
import com.composeunstyled.demo.borderToken
import com.composeunstyled.demo.colors
import com.composeunstyled.demo.contentToken
import com.composeunstyled.theme.Theme
@Preview
@Composable
fun SliderDemo() {
val pillShape = RoundedCornerShape(100)
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(pillShape),
) {
// the 'not yet completed' part of the track
Box(
Modifier
.fillMaxHeight()
.fillMaxWidth()
.background(Theme[colors][borderToken]),
)
// the 'completed' part of the track
Box(
Modifier
.fillMaxHeight()
.fillMaxWidth(state.fraction)
.background(Theme[colors][contentToken]),
)
}
},
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) {
Theme[colors][contentToken].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(Theme[colors][contentToken]),
)
}
},
)
}
}
}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:2.9.2")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 |
|
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> |
|
steps |
Int |
|
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. |