Start native apps faster with the Composables CLI ->
Compose Component

ScrollField

ScrollField's can be used to provide a more interactive way to select a time or other numerical value.

ScrollField social preview

ScrollFieldSample

@Composable
@Preview
fun ScrollFieldSample() {
    val minVal = 1000
    val maxVal = 2000
    val itemCount = (maxVal - minVal) + 1
    val state = rememberScrollFieldState(itemCount = itemCount, index = 0)
    var selectedValue by remember { mutableIntStateOf(minVal) }
    Row(
        modifier =
            Modifier.background(
                    MaterialTheme.colorScheme.surfaceContainerHighest,
                    RoundedCornerShape(28.dp),
                )
                .padding(12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        ScrollField(
            state = state,
            modifier = Modifier.size(width = 192.dp, height = 160.dp),
            contentDescription = "Select value between 1000 and 2000",
            fieldAccessibilityDescription = { index -> (minVal + index).toString() },
            field = { index, isSelected ->
                val valueToShow = minVal + index
                Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) {
                    Text(
                        text = valueToShow.toString(),
                        style =
                            if (isSelected) {
                                MaterialTheme.typography.displayLargeEmphasized
                            } else {
                                MaterialTheme.typography.displayMedium
                            },
                        color =
                            if (isSelected) {
                                MaterialTheme.colorScheme.onSurface
                            } else {
                                MaterialTheme.colorScheme.outline
                            },
                    )
                }
            },
        )
    }
}

TimeScrollFieldSample

@Composable
@Preview
fun TimeScrollFieldSample() {
    val hourCount = 24
    val minuteCount = 60
    val hourState = rememberScrollFieldState(itemCount = hourCount, index = 12)
    val minuteState = rememberScrollFieldState(itemCount = minuteCount, index = 30)
    var selectedHour by remember { mutableIntStateOf(12) }
    var selectedMinute by remember { mutableIntStateOf(30) }
    Row(
        modifier =
            Modifier.background(
                    MaterialTheme.colorScheme.surfaceContainerHighest,
                    RoundedCornerShape(28.dp),
                )
                .padding(12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        ScrollField(
            state = hourState,
            modifier = Modifier.size(width = 100.dp, height = 120.dp),
            contentDescription = "Select hour",
            fieldAccessibilityDescription = { index ->
                if (index == 1) "$index hour" else "$index hours"
            },
        )
        Text(
            text = ":",
            style = MaterialTheme.typography.displayLarge,
            color = MaterialTheme.colorScheme.onSurfaceVariant,
            textAlign = TextAlign.Center,
            modifier = Modifier.offset(y = (-4).dp).semantics { hideFromAccessibility() },
        )
        ScrollField(
            state = minuteState,
            modifier = Modifier.size(width = 100.dp, height = 120.dp),
            contentDescription = "Select minute",
            fieldAccessibilityDescription = { index ->
                if (index == 1) "$index minute" else "$index minutes"
            },
        )
    }
}

UnitScrollFieldSample

@Composable
@Preview
fun UnitScrollFieldSample() {
    val amountCount = 100 // 0.1 to 10.0
    val unitCount = 2 // L, oz
    val amountState = rememberScrollFieldState(itemCount = amountCount, index = 26) // 2.7
    val unitState = rememberScrollFieldState(itemCount = unitCount, index = 0) // L
    val units = listOf("L", "oz")
    Row(
        modifier =
            Modifier.background(
                    MaterialTheme.colorScheme.surfaceContainerHighest,
                    RoundedCornerShape(28.dp),
                )
                .padding(12.dp),
        verticalAlignment = Alignment.CenterVertically,
        horizontalArrangement = Arrangement.spacedBy(8.dp),
    ) {
        ScrollField(
            state = amountState,
            modifier = Modifier.size(width = 120.dp, height = 160.dp),
            contentDescription = "Select amount",
            fieldAccessibilityDescription = { index -> ((index + 1) / 10.0).toString() },
            field = { index, isSelected ->
                val amount = (index + 1) / 10.0
                Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) {
                    Text(
                        text = "${(amount * 10).toInt() / 10.0}",
                        style =
                            if (isSelected) {
                                MaterialTheme.typography.displayLargeEmphasized
                            } else {
                                MaterialTheme.typography.displayMedium
                            },
                        color =
                            if (isSelected) {
                                MaterialTheme.colorScheme.onSurface
                            } else {
                                MaterialTheme.colorScheme.outline
                            },
                    )
                }
            },
        )
        ScrollField(
            state = unitState,
            modifier = Modifier.size(width = 120.dp, height = 160.dp),
            contentDescription = "Select unit",
            fieldAccessibilityDescription = { index -> units[index] },
            field = { index, isSelected ->
                Box(modifier = Modifier.fillMaxHeight(), contentAlignment = Alignment.Center) {
                    Text(
                        text = units[index],
                        style =
                            if (isSelected) {
                                MaterialTheme.typography.displayLargeEmphasized
                            } else {
                                MaterialTheme.typography.displayMedium
                            },
                        color =
                            if (isSelected) {
                                MaterialTheme.colorScheme.onSurface
                            } else {
                                MaterialTheme.colorScheme.outline
                            },
                    )
                }
            },
        )
    }
}

Last updated: