【Jetpack Compose/Android】スクロール可能なViewを実装する方法!LazyColumn/LazyRow
この記事からわかること
- Kotlin/Android Jetpack Composeでスクロール可能なViewの実装方法
- Column / RowでverticalScroll / horizontalScroll
- LazyColumn / LazyRowの使い方と違い
index
[open]
\ アプリをリリースしました /
環境
- Android Studio:Narwhal Feature Drop
- Kotlin:2.1.10
- Material3
- AGP:8.9.2
- Gradle:8.11.1
- Mac M1:Sequoia 15.6.1
Jetpack Compose自体の基本的な使用方法に関しては以下の記事を参考にしてください。
Composeでスクロール可能なViewを実装する方法
Jetpack Composeでスクロール可能なViewを実装する方法は2つあります。
- Column / Row + verticalScroll / horizontalScroll・・・一度に全て描画
- LazyColumn / LazyRow・・・必要な部分だけ描画
両者の違いは描画のタイミングです。スクロールで表示したい要素が多数存在する場合にColumn / Rowで実装すると一度に対象の描画処理が走ってしまうのでパフォーマンスの問題が発生します。LazyColumn / LazyRowではビューポートに表示されるアイテムのみをComposeして描画することができるので多数のデータがあっても必要な箇所のみに抑えることができるためパフォーマンスを維持することができます。
少量や定型のリストであればColumn / Row、大量のデータリストであればLazyColumn / LazyRowを使用することが公式からも推奨されています。
Column / Row + verticalScroll / horizontalScroll
Column / Rowでスクロール機能を実装するためにはverticalScroll / horizontalScrollモディファイアを使用します。
例えば縦スクロールを可能にしたい場合はColumnのmodifierでverticalScrollを指定し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)
)....
animateScrollToはsuspend関数になっているので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
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はverticalScroll / 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.
これは無限の高さでスクロールレイアウトを測定しようとした際に発生するエラーでLazyColumnとColumn(modifier.verticalScroll())をネストした時に発生します。スクロール機能が二重で指定されるような構造になると発生するので注意してください。
まだまだ勉強中ですので間違っている点や至らぬ点がありましたら教えていただけると助かります。
ご覧いただきありがとうございました。






