実践
@OptIn(ExperimentalFoundationApi::class)
@Composable
fun ScrollPicker(
items: List<String>,
modifier: Modifier = Modifier,
onSelected: (String) -> Unit
) {
val listState = rememberLazyListState()
val itemHeight = 50.dp
val itemHeightPx = with(LocalDensity.current) { itemHeight.toPx() }
val flingBehavior = rememberSnapFlingBehavior(listState)
LaunchedEffect(listState.firstVisibleItemIndex, listState.firstVisibleItemScrollOffset) {
val index = listState.firstVisibleItemIndex +
if (listState.firstVisibleItemScrollOffset > itemHeightPx / 2) 1 else 0
if (index in items.indices) {
onSelected(items[index])
}
}
Box(
modifier = modifier
.height(itemHeight * 3) // 中央表示のため3行分
.fillMaxWidth()
) {
LazyColumn(
state = listState,
flingBehavior = flingBehavior,
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
items(items.size) { i ->
Box(
modifier = Modifier
.height(itemHeight)
.fillMaxWidth(),
contentAlignment = Alignment.Center
) {
Text(
text = items[i],
fontSize = 20.sp
)
}
}
}
// 中央の選択ライン
Box(
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()
.height(48.dp)
.border(1.dp, Color.Gray, RectangleShape)
)
}
}
使用例
@Composable
fun ExampleScreen() {
var selected by remember { mutableStateOf("0") }
ScrollPicker(
items = (0..59).map { it.toString().padStart(2, '0') },
modifier = Modifier.fillMaxWidth(),
onSelected = { selected = it }
)
Text("選択中: $selected")
}
説明
flingBehavior
でスナップショット(ピタッと止まる)の挙動を管理してます。
// 中央の選択ライン
Box(
modifier = Modifier
.align(Alignment.Center)
.fillMaxWidth()
.height(48.dp)
.border(1.dp, Color.Gray, RectangleShape)
)
}
Compose側で真ん中にあるものを選択状態にしてくれるわけではないので、真ん中にある数字を選択中に見えるようにしてあります。