ScrollField

Functions summary

Unit
@Composable
ScrollField(
    state: ScrollFieldState,
    contentDescription: String?,
    modifier: Modifier,
    colors: ScrollFieldColors,
    fieldAccessibilityDescription: (index: Int) -> String,
    interactionSource: MutableInteractionSource?,
    field: @Composable (index: Int, selected: Boolean) -> Unit
)

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

Cmn

Functions

@Composable
fun ScrollField(
    state: ScrollFieldState,
    contentDescription: String?,
    modifier: Modifier = Modifier,
    colors: ScrollFieldColors = ScrollFieldDefaults.colors(),
    fieldAccessibilityDescription: (index: Int) -> String = { index -> index.toLocalString() },
    interactionSource: MutableInteractionSource? = null,
    field: @Composable (index: Int, selected: Boolean) -> Unit = { index, selected -> ScrollFieldDefaults.Item(index = index, selected = selected, colors = colors) }
): Unit

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

Generic ScrollField for scrollable numerical selection:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollField
import androidx.compose.material3.Text
import androidx.compose.material3.rememberScrollFieldState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.unit.dp

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
                        },
                )
            }
        },
    )
}

ScrollField for time selection:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.offset
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollField
import androidx.compose.material3.Text
import androidx.compose.material3.rememberScrollFieldState
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.semantics.hideFromAccessibility
import androidx.compose.ui.semantics.semantics
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp

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"
        },
    )
}

ScrollField for unit selection:

import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.ScrollField
import androidx.compose.material3.Text
import androidx.compose.material3.rememberScrollFieldState
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.semantics.contentDescription
import androidx.compose.ui.unit.dp

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
                        },
                )
            }
        },
    )
}
Parameters
state: ScrollFieldState

the state object to be used to control or observe the pager's state.

contentDescription: String?

text used by accessibility services to describe what this field selects. This should include the available range when it is not obvious from context (e.g., "Select year between 2000 and 2025"). Because the wheel wraps around endlessly, this description is the only way for an accessibility user to learn the field's bounds.

modifier: Modifier = Modifier

the Modifier to be applied to the ScrollField container.

colors: ScrollFieldColors = ScrollFieldDefaults.colors()

ScrollFieldColors that will be used to resolve the colors used for this ScrollField in different states.

fieldAccessibilityDescription: (index: Int) -> String = { index -> index.toLocalString() }

returns the text accessibility services (e.g. TalkBack) announced for the option at the given index. It should match the text rendered by field.

interactionSource: MutableInteractionSource? = null

MutableInteractionSource for observing and controlling the interactions with the scroll field.

field: @Composable (index: Int, selected: Boolean) -> Unit = { index, selected -> ScrollFieldDefaults.Item(index = index, selected = selected, colors = colors) }

the composable used to render each item in the wheel.