animateScrollBy()を使用してLazyRowの中央にアイテムスクロールさせる
こんな感じの動き
コード全体
@Composable
fun ScrollList(
horizontalPadding: Dp = 20.dp,
horizontalItemSpace: Dp = 10.dp,
itemWidth: Dp = 60.dp,
) {
BoxWithConstraints(
modifier = Modifier
.fillMaxSize()
.background(Color.Black),
) {
val horizontalPaddingPx = horizontalPadding.toPx()
val itemWidthPx = itemWidth.toPx()
val items = listOf("A", "B", "C", "D", "E", "F", "G", "H", "I", "J", "K")
val boxWidth = LocalDensity.current.run { constraints.maxWidth }
val state = rememberLazyListState()
val coroutineScope = rememberCoroutineScope()
LazyRow(
state = state,
modifier = Modifier
.fillMaxWidth()
.wrapContentHeight()
.align(Alignment.Center),
verticalAlignment = Alignment.CenterVertically,
horizontalArrangement = Arrangement.spacedBy(horizontalItemSpace),
contentPadding = PaddingValues(horizontal = horizontalPadding),
) {
itemsIndexed(items = items) { index, item ->
Text(
modifier = Modifier
.width(itemWidth)
.padding(vertical = 20.dp)
.background(Color.White)
.clickable {
coroutineScope.launch {
val itemInfo = state.layoutInfo.visibleItemsInfo.find {
// タップしたアイテムのLazyListItemInfoをindexをkeyにして取得
it.index == index
}
itemInfo?.let {
// Boxの横方向の中央とアイテムの中央との差分だけscrollさせる
state.animateScrollBy((it.offset + horizontalPaddingPx + (itemWidthPx / 2) - (boxWidth / 2)))
}
}
},
textAlign = TextAlign.Center,
fontSize = 20.sp,
text = item,
)
}
}
}
}
ポイント
なぜanimateScrollByを使用したか
animateScrollToItemでは指定したindexのitemをscrollのtop(今回のケースでは一番左)へスクロールする前提でそこからscrollOffsetでスクロール位置を調整することになります。
今回はマイナス方向(今回のケースでは右方向)へ調整したいためマイナスのoffsetとなりますが、animateScrollToItemでscrollOffsetに負数を渡すと以下のようなExceptionが発生しクラッシュするため今回はanimateScrollByを使用しています。
IllegalArgumentException: scrollOffset should be non-negative
解説
animateScrollByはsuspend functionなのでcoroutineScopeを使用しています
.clickable {
coroutineScope.launch {
// スクロール処理
}
},
animateScrollByに渡すスクロール量(value)は画面中央の位置からアイテムの中央の位置までの距離なので以下のような計算になりました。
今回のようにLazyRowにcontentPaddingが設定されている場合はその分も計算が必要になるのに気が付かずに微妙に中央にならないため少しハマりました。。
state.animateScrollBy((it.offset + horizontalPaddingPx + (itemWidthPx / 2) - (boxWidth / 2)))
内容と直接関係ないですが、DpをPxに変換しているtoPx()については以下で詳細を書いています
JetpackComposeでDpからPxを簡単に計算する拡張関数