Jetpack composeは、アプリ開発に必要な一通りのUIコンポーネントをライブラリで提供しています。
そのライブラリ中のLazyRowについて、構成や使用方法などをまとめます。
※環境:Android Studio Koala | 2024.1.1 Patch 1
Kotlin 1.9.0
Compose Compiler 1.5.1
androidx.compose.foundation:foundation 1.6.8
目次
UIの概要
LazyRowは「複数のデータやオブジェクト」または「コレクション」を一覧(リスト)表示するUIです。
1つのデータや要素をアイテムとして、横(行:Row)方向に並べます。
LazyRow( modifier = Modifier.fillMaxSize(), horizontalArrangement = Arrangement.spacedBy(2.dp), verticalAlignment = Alignment.CenterVertically ) { @OptIn(ExperimentalFoundationApi::class) stickyHeader { // ← 実験的な実装 headerItem() } items(memberList) { item -> memberItem(item.imgId, item.name, item.group) } }
関数の引数
LayzyRowの状態(装飾、演出など)は、引数により指定できます。
@Composable fun LazyRow( modifier: Modifier = Modifier, state: LazyListState = rememberLazyListState(), contentPadding: PaddingValues = PaddingValues(0.dp), reverseLayout: Boolean = false, horizontalArrangement: Arrangement.Horizontal = if (!reverseLayout) Arrangement.Start else Arrangement.End, verticalAlignment: Alignment.Vertical = Alignment.Top, flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), userScrollEnabled: Boolean = true, content: LazyListScope.() -> Unit ) { ... }
引数 | 概要 | |
---|---|---|
modifier | Modifier | UI全般のCompose修飾子 |
state | LazyListState | スクロールの状態を監視・制御 |
contentPadding | PaddingValue | コンテンツ(リスト)の周りの空白 |
reverseLayout | Boolean | アイテムの並びを反転 false:正転(上から下へ並べる)、デフォルト true:反転(下から上へ並べる) |
horizontalArrangement | Arrangement.Horizontal | コンテンツ(リスト)の配置、水平方向 ※詳細は「配置パラメータ」を参照 ※詳細は「リストの配置」を参照 |
verticalAlignment | Alignment.Vertical | コンテンツ(リスト)の配置、垂直方向 ※詳細は「配置パラメータ」を参照 ※詳細は「リストの配置」を参照 |
flingBehavior | FlingBehavior | スクロールの動きを定義 |
userScrollEnabled | Boolean | ジェスチャーによるスクロールの許可 false:不許可 true:許可、デフォルト ※ジェスチャー:画面のスワイプ、フリック |
content | LazyListScope.() -> Unit | コンテンツ(リスト)のスロット ・LazyListScope DSLが利用可能 ・DSLコマンドでアイテムを並べて配置 |
配置パラメータ | 概要 | |
---|---|---|
Alignment | CenterVertical | 垂直方向の中央 |
Top | 上端 | |
Bottom | 下端 | |
Arrangement | Center | 水平方向の中央 |
Start | 左端 | |
End | 右端 | |
SpaceAround | 空きスペースの均一化 ※詳細は「特殊な配置1」を参照 |
|
SpaceBetween | ||
SpaceEvenly | ||
SpaceBy | アイテム間のスペース(後述) ※詳細は「特殊な配置2」を参照 |
関数の構成
LazyRow(横に並べる)はLazyList関数を呼び出す構成になっています。
LazyList関数はLazyListScope DSLを実行するエンジンです。コマンドに従ってアイテムを並べる処理(レイアウトフェーズで配置)を行います。
縦に並べる処理の原理も同じであるため、LazyColumn(縦に並べる)も同じエンジンを用いています。ただし、LazyColumnはフラグが異なります(isVertical = true)。
@Composable fun LazyColumn( ... content: LazyListScope.() -> Unit ) { LazyList( modifier = modifier, state = state, contentPadding = contentPadding, flingBehavior = flingBehavior, horizontalAlignment = horizontalAlignment, verticalArrangement = verticalArrangement, isVertical = true, reverseLayout = reverseLayout, userScrollEnabled = userScrollEnabled, content = content ) }
@Composable fun LazyRow( ... content: LazyListScope.() -> Unit ) { LazyList( modifier = modifier, state = state, contentPadding = contentPadding, verticalAlignment = verticalAlignment, horizontalArrangement = horizontalArrangement, isVertical = false, flingBehavior = flingBehavior, reverseLayout = reverseLayout, userScrollEnabled = userScrollEnabled, content = content ) }
リストの実装
リストの実装は、「LazyListScope DSL」を用いて行います。
実装方法
LazyRow関数の最後の引数contentは、LazyListScopeレシーバーを持つスロットになっています。
ここにDSLコマンドを使って、アイテムの並びを記述します。
「記述順=並び順」になるので、直感的に判り易いと思います。
LazyListScope DSL
表に挙げるDSLコマンドが用意されています。
コマンド | 概要 | |
---|---|---|
item( ) | 標準関数 | 単体のアイテムを追加 |
items(count: Int) | カウント値のアイテムを追加 | |
stickyHeader( ) | リストの先頭に固定されるヘッダーを追加 | |
items(items: Array | 拡張関数 | Arrayで列挙されたアイテムを追加 |
items(items: List | Listで列挙されたアイテムを追加 | |
itemsIndexed(items: Array | Arrayで列挙されたIndex付きアイテムを追加 | |
itemsIndexed(items: List | Listで列挙されたIndex付きアイテムを追加 |
拡張関数のitemsは最終的に標準関数のitemsに落とし込まれて処理されます。
inline fun <T> LazyListScope.items( items: List<T>, noinline key: ((item: T) -> Any)? = null, noinline contentType: (item: T) -> Any? = { null }, crossinline itemContent: @Composable LazyItemScope.(item: T) -> Unit ) = items( count = items.size, key = if (key != null) { index: Int -> key(items[index]) } else null, contentType = { index: Int -> contentType(items[index]) } ) { itemContent(items[it]) }
※各DSLの使い方は「Compose LazyColumn:LazyListScope DSL」を参照
リストの配置
「表示可能な範囲(LazyRowのサイズ)>リスト(コンテンツ)のサイズ」の時、verticalAlignmentとhorizontalArrangementを使って、リストの配置を指定できます。
図は代表して3箇所の配置を示しています。表示可能な箇所は全部で9カ所あります。
LazyRow( modifier = Modifier.fillMaxSize(), // Vertical verticalAlignment = Alignment.Top, // verticalAlignment = Alignment.CenterVertically, // verticalAlignment = Alignment.Bottom, // Horizontal horizontalArrangement = Arrangement.Start // horizontalArrangement = Arrangement.Center // horizontalArrangement = Arrangement.End ) { items(3) { Box( modifier = Modifier .size(60.dp, 80.dp) .background(_containerColor), contentAlignment = Alignment.Center ) { Text(text = "item %02d".format(it)) } } }
特殊は配置1(空きスペースの均一化)
アイテムを配置した時に生まれる空きスペースを均一化することが出来ます。
LazyRow( modifier = Modifier.fillMaxSize(), // Vertical verticalAlignment = Alignment.Top, // verticalAlignment = Alignment.CenterVertically, // verticalAlignment = Alignment.Bottom, // Horizontal horizontalArrangement = Arrangement.SpaceAround // horizontalArrangement = Arrangement.SpaceBetween // horizontalArrangement = Arrangement.SpaceEvenly ) { items(3) { Box( modifier = Modifier .size(60.dp, 80.dp) .background(_containerColor), contentAlignment = Alignment.Center ) { Text(text = "item %02d".format(it)) } } }
特殊な配置2(アイテム間のスペース)
隣り合うアイテム間にスペースを設けることが出来ます。
LazyRow( modifier = Modifier.fillMaxSize(), // Vertical verticalAlignment = Alignment.Top, // verticalAlignment = Alignment.CenterVertically, // verticalAlignment = Alignment.Bottom, // Horizontal horizontalArrangement = Arrangement.spacedBy(2.dp) // horizontalArrangement = Arrangement.spacedBy(4.dp, Alignment.CenterHorizontally) // horizontalArrangement = Arrangement.spacedBy(8.dp, Alignment.End) ) { items(3) { Box( modifier = Modifier .size(60.dp, 80.dp) .background(_containerColor), contentAlignment = Alignment.Center ) { Text(text = "item %02d".format(it)) } } }
アイテムの並びを反転(reverseLayout)
reverseLayout引数は、アイテムの並びを反転させます。
並びが反転するだけでなく、配置の開始も下からになります。
スクロールの状態を監視・制御(state)
LazyListStateはスクロールの状態を監視・制御するクラスです。引数stateに指定します。
デフォルトはremenberLazyListState( )になっているので、監視・制御の必要がなければ、指定を省略できます。
LazyListStateのサンプル
以下は、リストの先頭へ移動するボタンを、スクロールの状況に合わせて表示・非表示するサンプルです。
val _listState = rememberLazyListState() val _coroutineScope = rememberCoroutineScope() val _visible = remember { derivedStateOf { _listState.firstVisibleItemIndex > 1 } // ↑↑ 先頭アイテムのインデックス ↑↑ } LazyRow(state = _listState) { items(24) { ItemBox { Text(text = "item\n %02d ".format(it)) } } } AnimatedVisibility( // ButtonのVisibilityをOn/Off modifier = Modifier.align(Alignment.BottomEnd).padding(10.dp), visible = _visible.value ) { Button( onClick = { _coroutineScope.launch { _listState.scrollToItem(index = 0) // ↑↑ リストの先頭へスクロール ↑↑ } } ) { Text(text = "Top") } }
LazyListState#firstVisibleItemIndexは先頭アイテムのインデックスです。このインデックスの大小を用いて、ボタンのVisibilityを切り替えています。
LazyListState#scrollToItem( )は指定したアイテムへスクロールで移動します。
スクロールの動きを定義(flingBehavior)
FlingBehaviorはスクロールの動きを定義するクラスです。引数flingBehaviorに指定します。
デフォルトはScrollableDefaults.flingBehavior( )になっているので、変更の必要がなければ、指定を省略できます。
FlingBehaviorのサンプル
以下は、スクロールの停止位置を表示範囲の中心へスナップ(ぴったりと入る)させるサンプルです。
val _listState = rememberLazyListState() val _flingBehavior = rememberSnapFlingBehavior( lazyListState = _listState ) // ↑↑ foundation 1.3.0で追加 ↑↑ LazyRow( modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically, state = _listState, flingBehavior = _flingBehavior ) { items(100) { ItemBox { Text(text = "item %02d".format(it)) } } }
ライブラリで提供される既成の動きは2つのみです。
ScrollableDefaults.flingBehavior( )は、フリングの強さに合わせてスクロール距離が変化する動きです(動画の上)。
rememberSnapFlingBehavior( )は、スクロールの停止位置をリストの中心へスナップさせます(動画の下)。
ちなみに、次期APIの「foundation 1.7.0」は、スナップする停止位置を表示範囲の先頭(Start)もしくは末尾(End)にできます。
val _listState = rememberLazyListState() val _flingBehavior = rememberSnapFlingBehavior( lazyListState = _listState, // snapPosition = SnapPosition.Start // snapPosition = SnapPosition.Center // デフォルト snapPosition = SnapPosition.End ) // ↑↑ foundation 1.7.0で追加 ↑↑ LazyRow( modifier = Modifier.fillMaxSize(), verticalAlignment = Alignment.CenterVertically, state = _listState, flingBehavior = _flingBehavior ) { items(100) { ItemBox { Text(text = "item %02d".format(it)) } } }
関連記事: