LoginSignup
3
3

More than 1 year has passed since last update.

JetpackComposeのLazyRowでタップしたアイテムを中央にスクロールする

Last updated at Posted at 2022-03-25

animateScrollBy()を使用してLazyRowの中央にアイテムスクロールさせる

こんな感じの動き

Eをタップ → Gをタップ → Eをタップ
scroll.gif

コード全体

@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を簡単に計算する拡張関数

3
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
3
3