はじめに
スクロールして、最後まで行ったら再度はじめに戻るドラムロールのようなRollPickerをComposeで実装してみました
コード
@Composable
fun <T> InfiniteCircularList(
width: Dp,
itemHeight: Dp,
numberOfDisplayedItems: Int = 3,
items: List<T>,
initialItem: T,
itemScaleFact: Float = 1.5f,
textStyle: TextStyle,
textColor: Color,
selectedTextColor: Color,
onItemSelected: (index: Int, item: T) -> Unit = { _, _ -> },
) {
val itemHalfHeight = LocalDensity.current.run { itemHeight.toPx() / 2f }
val scrollState = rememberLazyListState(0)
var lastSelectedIndex by remember {
mutableIntStateOf(0)
}
var itemsState by remember {
mutableStateOf(items)
}
LaunchedEffect(items) {
var targetIndex = items.indexOf(initialItem) - 1
targetIndex += ((Int.MAX_VALUE / 2) / items.size) * items.size
itemsState = items
lastSelectedIndex = targetIndex
scrollState.scrollToItem(targetIndex)
}
LazyColumn(
modifier = Modifier
.width(width)
.height(itemHeight * numberOfDisplayedItems),
state = scrollState,
flingBehavior = rememberSnapFlingBehavior(
lazyListState = scrollState,
),
) {
items(
count = Int.MAX_VALUE,
itemContent = { i ->
val item = itemsState[i % itemsState.size]
Box(
modifier = Modifier
.height(itemHeight)
.fillMaxWidth()
.onGloballyPositioned { coordinates ->
val y = coordinates.positionInParent().y - itemHalfHeight
val parentHalfHeight =
(coordinates.parentCoordinates?.size?.height ?: 0) / 2f
val isSelected =
(y > parentHalfHeight - itemHalfHeight && y < parentHalfHeight + itemHalfHeight)
if (isSelected && lastSelectedIndex != i) {
onItemSelected(i % itemsState.size, item)
lastSelectedIndex = i
}
},
contentAlignment = Alignment.Center,
) {
Text(
text = item.toString(),
style = textStyle,
color = if (lastSelectedIndex == i) {
selectedTextColor
} else {
textColor
},
fontSize = if (lastSelectedIndex == i) {
textStyle.fontSize * itemScaleFact
} else {
textStyle.fontSize
},
)
}
},
)
}
}
最後に
選択肢が多かったりすると戻るのが億劫になるのでこういったドラムロール式の部品も便利かなと思い実装してみました