import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
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.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.layout.wrapContentHeight
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.BasicText
import androidx.compose.foundation.verticalScroll
import androidx.compose.runtime.Composable
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.text.TextStyle
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import com.composeunstyled.Thumb
import com.composeunstyled.ThumbVisibility
import com.composeunstyled.UnstyledHorizontalScrollbar
import com.composeunstyled.UnstyledVerticalScrollbar
import com.composeunstyled.rememberScrollbarState
import kotlin.time.Duration.Companion.seconds
@Composable
fun ScrollbarsDemo() {
VerticalScrollbarsDemo()
}
@Composable
fun VerticalScrollbarsDemo() {
Box(
modifier = Modifier.fillMaxSize()
.padding(vertical = 40.dp)
.padding(horizontal = 16.dp),
contentAlignment = Alignment.TopCenter,
) {
val desserts = listOf(
"Cupcake",
"Donut",
"Eclair",
"Froyo",
"Gingerbread",
"Honeycomb",
"Ice Cream Sandwich",
"Jelly Bean",
"KitKat",
"Lollipop",
"Marshmallow",
"Nougat",
"Oreo",
"Pie",
"Quince",
"Red Velvet Cake",
"Snow Cone",
"Tiramisu",
"Upside-down Cake",
"Vanilla Custard",
"Waffle",
"Xmas Pudding",
"Yogurt Parfait",
"Zabaglione",
)
val state = rememberScrollState()
val scrollbarState = rememberScrollbarState(state)
Box(
modifier = Modifier
.widthIn(max = 400.dp)
.background(Color(0xFFF8FAFC), RoundedCornerShape(8.dp))
.border(1.dp, Color(0xFFCACACA), RoundedCornerShape(8.dp))
.fillMaxSize(),
) {
Column(
Modifier.verticalScroll(state)
.padding(start = 4.dp, end = 16.dp)
.fillMaxWidth()
.padding(8.dp),
) {
BasicText(
"Deserts",
Modifier.padding(4.dp),
style = TextStyle(fontSize = 20.sp, fontWeight = FontWeight.Bold),
)
Spacer(Modifier.height(12.dp))
desserts.forEach { i ->
BasicText(i, Modifier.padding(4.dp).fillMaxWidth())
Spacer(Modifier.height(12.dp))
}
}
UnstyledVerticalScrollbar(
scrollbarState = scrollbarState,
modifier = Modifier
.align(Alignment.TopEnd)
.width(12.dp)
.fillMaxHeight(),
) {
Thumb(
modifier = Modifier
.fillMaxWidth()
.padding(2.dp)
.height(12.dp)
.background(Color.Black.copy(0.33f), RoundedCornerShape(100)),
thumbVisibility = ThumbVisibility.AlwaysVisible,
)
}
}
}
}
@Composable
fun HorizontalScrollbarsDemo() {
Box(
modifier = Modifier.fillMaxSize()
.padding(vertical = 40.dp),
contentAlignment = Alignment.TopCenter,
) {
val state = rememberScrollState()
val scrollbarState = rememberScrollbarState(state)
Box(
modifier = Modifier
.widthIn(max = 400.dp)
.background(Color(0xFFF8FAFC), RoundedCornerShape(8.dp))
.border(1.dp, Color(0xFFCACACA), RoundedCornerShape(8.dp))
.wrapContentHeight(),
) {
Row(
Modifier.horizontalScroll(state)
.systemBarsPadding()
.navigationBarsPadding()
.padding(start = 4.dp, end = 16.dp)
.fillMaxWidth()
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp),
) {
(1..100).forEach { i ->
Box(Modifier.size(90.dp).clip(CircleShape).background(Color.Black))
}
}
UnstyledHorizontalScrollbar(
scrollbarState = scrollbarState,
modifier = Modifier
.align(Alignment.BottomCenter)
.height(12.dp)
.fillMaxWidth(),
) {
Thumb(
modifier = Modifier
.fillMaxHeight()
.padding(2.dp)
.width(12.dp)
.background(Color.Black.copy(0.33f), RoundedCornerShape(100)),
thumbVisibility = ThumbVisibility.HideWhileIdle(
enter = fadeIn(),
exit = fadeOut(),
hideDelay = 1.seconds,
),
)
}
}
}
}Features
- ScrollState support
- LazyListState support
- LazyGridState support
- Draggable scrollbar thumbs
Installation
implementation("com.composables:composeunstyled-scrollbars")
Anatomy
val scrollbarState = rememberScrollbarState(scrollState)
UnstyledVerticalScrollbar(scrollbarState) {
Thumb()
}
Concepts
ScrollbarStaterepresents the scroll position used by a scrollbar.UnstyledVerticalScrollbarrenders a vertical scrollbar.UnstyledHorizontalScrollbarrenders a horizontal scrollbar.Thumbrenders the draggable thumb inside a scrollbar.
Code Examples
Adding scrollbars to LazyColumn
Use the rememberScrollbarState(LazyListState) function to connect a scrollbar to lazy list scroll position:
val listState = rememberLazyListState()
val scrollbarState = rememberScrollbarState(listState)
LazyColumn(state = listState) {
items(100) { index ->
BasicText("Item $index")
}
}
UnstyledVerticalScrollbar(scrollbarState) {
Thumb()
}
Adding scrollbars to LazyVerticalGrid
Use the rememberScrollbarState(LazyGridState) function to connect a scrollbar to lazy grid scroll position:
val gridState = rememberLazyGridState()
val scrollbarState = rememberScrollbarState(gridState)
LazyVerticalGrid(
columns = GridCells.Fixed(2),
state = gridState,
) {
items(100) { index ->
BasicText("Item $index")
}
}
UnstyledVerticalScrollbar(scrollbarState) {
Thumb()
}
Adding scrollbars to scrollable content
Use the rememberScrollbarState(ScrollState) function for content that uses a regular scroll state:
val scrollState = rememberScrollState()
val scrollbarState = rememberScrollbarState(scrollState)
Column(Modifier.verticalScroll(scrollState)) {
repeat(100) { index ->
BasicText("Item $index")
}
}
UnstyledVerticalScrollbar(scrollbarState) {
Thumb()
}
Hiding the scrollbar while idle
Use the thumbVisibility parameter to hide the thumb when the user is not interacting with the scrollable content:
UnstyledVerticalScrollbar(scrollbarState) {
Thumb(
thumbVisibility = ThumbVisibility.HideWhileIdle(
enter = fadeIn(),
exit = fadeOut(),
hideDelay = 500.milliseconds,
),
)
}
Supporting reverse layout
Use the reverseLayout parameter when the scrollable content uses reverse layout:
UnstyledVerticalScrollbar(
scrollbarState = scrollbarState,
reverseLayout = true,
) {
Thumb()
}
API Reference
rememberScrollbarState
| Parameter | Type | Description |
|---|---|---|
scrollState |
ScrollState |
|
lazyListState |
LazyListState |
|
lazyGridState |
LazyGridState |
UnstyledVerticalScrollbar
| Parameter | Type | Description |
|---|---|---|
scrollbarState |
ScrollbarState |
|
modifier |
Modifier |
|
enabled |
Boolean |
|
interactionSource |
MutableInteractionSource? |
|
reverseLayout |
Boolean |
|
thumb |
(ScrollbarScope.() -> Unit) |
UnstyledHorizontalScrollbar
| Parameter | Type | Description |
|---|---|---|
scrollbarState |
ScrollbarState |
|
modifier |
Modifier |
|
enabled |
Boolean |
|
interactionSource |
MutableInteractionSource? |
|
reverseLayout |
Boolean |
|
thumb |
(ScrollbarScope.() -> Unit) |
ScrollbarScope.Thumb
| Parameter | Type | Description |
|---|---|---|
modifier |
Modifier |
|
thumbVisibility |
ThumbVisibility |
|
enabled |
Boolean |