【Jetpack Compose/Android】スクロール可能なViewを実装する方法!LazyColumn/LazyRow

【Jetpack Compose/Android】スクロール可能なViewを実装する方法!LazyColumn/LazyRow

この記事からわかること

  • Kotlin/Android Jetpack Composeスクロール可能なView実装方法
  • Column / RowverticalScroll / horizontalScroll
  • LazyColumn / LazyRow使い方違い

index

[open]

\ アプリをリリースしました /

みんなの誕生日

友達や家族の誕生日をメモ!通知も届く-みんなの誕生日-

posted withアプリーチ

環境

Jetpack Compose自体の基本的な使用方法に関しては以下の記事を参考にしてください。

Composeでスクロール可能なViewを実装する方法

Jetpack Composeでスクロール可能なViewを実装する方法は2つあります。

  1. Column / Row + verticalScroll / horizontalScroll・・・一度に全て描画
  2. LazyColumn / LazyRow・・・必要な部分だけ描画

両者の違いは描画のタイミングです。スクロールで表示したい要素が多数存在する場合にColumn / Rowで実装すると一度に対象の描画処理が走ってしまうのでパフォーマンスの問題が発生します。LazyColumn / LazyRowではビューポートに表示されるアイテムのみをComposeして描画することができるので多数のデータがあっても必要な箇所のみに抑えることができるためパフォーマンスを維持することができます。

少量や定型のリストであればColumn / Row大量のデータリストであればLazyColumn / LazyRowを使用することが公式からも推奨されています。

Column / Row + verticalScroll / horizontalScroll

公式リファレンス:Scroll

Column / Rowスクロール機能を実装するためにはverticalScroll / horizontalScrollモディファイアを使用します。

例えば縦スクロールを可能にしたい場合はColumnmodifierverticalScrollを指定しrememberScrollStateをインスタンス化して渡します。rememberScrollStateスクロール位置をピクセル単位のInt型で保持しています。

@Composable
fun ScrollExample() {
    val scrollState = rememberScrollState()
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scrollState)
    ) {
        repeat(20) { index ->
            Text(
                text = "Item $index",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            )
        }
    }
}

スクロール位置をコードから制御する

スクロール位置をコードから制御するにはanimateScrollToを使用します。animateScrollToで指定したピクセル位置に画面をスクロールさせることが可能です。

val scrollState = rememberScrollState()
LaunchedEffect(Unit) { scrollState.animateScrollTo(500) }

Column(
    modifier = modifier
        .fillMaxSize()
        .verticalScroll(scrollState)
)....

animateScrollTosuspend関数になっているのでButtonの中などで呼び出す場合はrememberCoroutineScopeなどを使用してください。

  val scrollState = rememberScrollState()
val scope = rememberCoroutineScope()

Button(
    onClick = {
        scope.launch { scrollState.animateScrollTo(500) }
    }
) {
    Text(
        text = "Button",
        modifier = Modifier
            .fillMaxWidth()
            .padding(16.dp)
    )
}

スクロールバーを実装する

スクロールバーを実装したい場合はVerticalScrollbarを使用します。

Box(modifier = Modifier.fillMaxSize()) {
    Column(
        modifier = Modifier
            .fillMaxSize()
            .verticalScroll(scrollState)
    ) {
        // items...
    }

    VerticalScrollbar(
        modifier = Modifier.align(Alignment.CenterEnd).fillMaxHeight(),
        adapter = rememberScrollbarAdapter(scrollState)
    )
}

LazyColumn / LazyRow

公式リファレンス:LazyList

LazyColumn / LazyRowスクロール機能を実装するためには特にモディファイアの指定は入りません。

例えば縦スクロールを可能にしたい場合はLazyColumnで対象のデータViewをラップするだけです。子要素はitem {} / items {}を使用して定義します。

@Composable
fun LazyScrollExample() {
    LazyColumn(
        modifier = Modifier.fillMaxSize()
    ) {
        items(100) { index ->
            Text(
                text = "Item $index",
                modifier = Modifier
                    .fillMaxWidth()
                    .padding(16.dp)
            )
        }
    }
}

スクロール位置をコードから制御する

スクロール位置をコードから制御するにはrememberLazyListStateでスクロール状態を管理するようにし、animateScrollToItemを使用します。animateScrollToItemで指定したアイテム位置に画面をスクロールさせることが可能です。

val listState = rememberLazyListState()
val scope = rememberCoroutineScope()

LazyColumn(state = listState) {
    items(100) { index ->
        Text(
            text = "Item $index",
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
    }
}

// 先頭へスクロール
Button(onClick = { scope.launch { listState.animateScrollToItem(0) } }) {
    Text("先頭へ")
}

またitemsの引数keyにはユニークなキーを指定することでパフォーマンスがさらに向上します。

items(
    items = users,
    // ユニークなキーを指定
    key = { user -> user.id } 
) { task ->
    Text(user.name)
}

固定ヘッダーを実装する

スクロール対象ではない固定ヘッダーを実装したい場合stickyHeaderを使用します。

LazyColumn {
    stickyHeader {
        Text(
            "固定ヘッダー",
            Modifier
                .fillMaxWidth()
                .background(Color.Gray)
                .padding(8.dp)
        )
    }
    items(100) { index ->
        Text(
            text = "Item $index",
            modifier = Modifier
                .fillMaxWidth()
                .padding(16.dp)
        )
    }
}

スクロール機能を自作する

公式リファレンス:Scrollable

スクロール機能を自作したい場合はscrollableを使用します。scrollableverticalScroll / horizontalScroll内部でも使用されている例レベル層のAPIです。「ジェスチャー検知」と「スクロール量の伝達」を行うことができるので対象のViewに対して明示的にオフセットを指定することでスクロールしているような挙動を実装することが可能です。

var offset by remember { mutableFloatStateOf(0f) }
Column(
    modifier = modifier
        .fillMaxSize()
        .scrollable(
            orientation = Orientation.Vertical,
            // Scrollable state: describes how to consume
            // scrolling delta and update offset
            state = rememberScrollableState { delta ->
                offset += delta
                Log.d("スクロール変化量", "${offset}")
                delta
            }
        )
).... 

ERROR:Vertically scrollable component was measured with an infinity maximum height constraints

スクロール機能を実装していると以下のようなjava.lang.IllegalStateException: Vertically scrollable component...というエラーが発生することがあります。

java.lang.IllegalStateException: Vertically scrollable component was measured with an infinity maximum height constraints, which is disallowed. One of the common reasons is nesting layouts like LazyColumn and Column(Modifier.verticalScroll()). If you want to add a header before the list of items please add a header as a separate item() before the main items() inside the LazyColumn scope. There could be other reasons for this to happen: your ComposeView was added into a LinearLayout with some weight, you applied Modifier.wrapContentSize(unbounded = true) or wrote a custom layout. Please try to remove the source of infinite constraints in the hierarchy above the scrolling container.

これは無限の高さでスクロールレイアウトを測定しようとした際に発生するエラーLazyColumnColumn(modifier.verticalScroll())ネストした時に発生します。スクロール機能が二重で指定されるような構造になると発生するので注意してください。

まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。

ご覧いただきありがとうございました。

Search Box

Sponsor

ProFile

ame

趣味:読書,プログラミング学習,サイト制作,ブログ

IT嫌いを克服するためにITパスを取得しようと勉強してからサイト制作が趣味に変わりました笑今はCMSを使わずこのサイトを完全自作でサイト運営中〜

New Article

index