Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
[shibuya.apk #41] Jetpack Composeでグリッドに柔軟にスペースを...
Search
syarihu
April 21, 2023
Technology
3.6k
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
[shibuya.apk #41] Jetpack Composeでグリッドに柔軟にスペースを入れたい
https://shibuya-apk.connpass.com/event/277598/
syarihu
April 21, 2023
More Decks by syarihu
See All by syarihu
Recap #io25 What's new in Google Play
syarihu
0
150
[DroidKaigi 2024] Android ViewからJetpack Composeへ 〜Jetpack Compose移行のすゝめ〜 / From Android View to Jetpack Compose: A Guide to Migration
syarihu
4
5.2k
[shibuya.apk #46] Composableの枠を超えてアニメーションする / Animation beyond Composable
syarihu
0
140
[DroidKaigi 2022] 詳解Google Playの新しい定期購入 ~オファーの活用や実装例を添えて~
syarihu
0
4k
5分で分かるGoogle Playの新しいサブスクリプション / Google Play new subscription 2022
syarihu
1
2k
[DroidKaigi 2021] Google Play 定期購入 比例配分モード完全攻略ガイド / Google Play Subscription Proration Mode Complete Guide
syarihu
1
8.2k
[Money Forward Tech Drive] What's new in Google Play [Recap #io21]
syarihu
0
660
[Android 11 Meetups] Google Play Commerce からのアップデート / Android 11 Meetups Google Play Commerce
syarihu
4
3.2k
[potatotips #70] license-list-pluginを使ってOSSライセンス画面を自動生成する / license-list-plugin
syarihu
4
5.2k
Other Decks in Technology
See All in Technology
あなたの知らないバージョン命名規則
sat
PRO
2
860
ClaudeCodeでセキュリティ監視業務を半自動化_1年の運用でわかったAIに任せる設計の5つのポイント
kintotechdev
3
910
All About Sansan – for New Global Engineers
sansan33
PRO
1
1.5k
AWSとGitHub Actionsの責任境界と 組織で安全に使用する取り組み
nealle
1
220
わたしが知り合いゼロの勉強会に 行けるようになるまで
r5ni4
2
840
internal/testlog で遊ぼう
rokuosan
0
380
AI駆動開発はどこまで来たのか? ファインディの最新実態調査で読み解く現在地 Devin Con Tokyo
akiratom
4
2k
Digital Credentials API × OpenID4VP ブラウザ完結型本人確認の実装知見(OAuth/OIDC Numa (Immersion) Workshop 2026)
oidfj
PRO
0
340
IHV like なユースケースへのOpenID Connect 関連仕様の適用事例
optim
0
360
形式手法特論:Hyperproperty とモデル検査 #kernelvm / Kernel VM Study Tokyo 19th
ytaka23
1
890
研究開発部の紹介 / Sansan R&D Profile
sansan33
PRO
4
24k
『自分で判断できるか』を基準に、プロダクトのハンズオン研修でAI利用の線を引いてみた / Where We Drew the Line on AI in Hands-on Training
honyanya
1
450
Featured
See All Featured
The Curious Case for Waylosing
cassininazir
1
480
The World Runs on Bad Software
bkeepers
PRO
72
12k
ReactJS: Keep Simple. Everything can be a component!
pedronauck
666
130k
What does AI have to do with Human Rights?
axbom
PRO
1
2.3k
Save Time (by Creating Custom Rails Generators)
garrettdimon
PRO
32
4.5k
First, design no harm
axbom
PRO
2
1.3k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
400
Unlocking the hidden potential of vector embeddings in international SEO
frankvandijk
0
900
Mobile First: as difficult as doing things right
swwweet
225
10k
Beyond borders and beyond the search box: How to win the global "messy middle" with AI-driven SEO
davidcarrasco
3
220
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
Gemini Prompt Engineering: Practical Techniques for Tangible AI Outcomes
mfonobong
2
500
Transcript
Jetpack Composeでグリッドに柔 軟にスペースを入れたい shibuya.apk #41 2023/04/21 (Fri.) Taichi Sato /
@syarihu Giftmall, Inc. Android Engineer
Taichi Sato (@syarihu) • Giftmall, Inc. (LUCHE GROUP) ◦ Android
App Engineer
shibuya.apkは3年ぶり9回目の発表みたいです
今回実現したい レイアウト
今回実現したい レイアウト • あるアイテムではスペー スなしで1行で表示したい
今回実現したい レイアウト • あるアイテムではスペー スなしで1行で表示したい • あるアイテムでは1行に3 列表示するグリッドを作 成し、そのグリッド内では スペースを入れたい
一見簡単そうに見えるが果たして…
LazyVerticalGridの標準機能で 頑張ってみる
スペースを入れない通常のレイアウトを作成する LazyVerticalGrid( modifier = Modifier.fillMaxSize(), columns = GridCells.Fixed(count = GRID_COLUMN_SIZE)
) {
スペースを入れない通常のレイアウトを作成する LazyVerticalGrid(...) { item(span = { GridItemSpan(GRID_COLUMN_SIZE) }) { Box(
modifier = Modifier .fillMaxWidth() .height(100.dp) .background(Color.DarkGray), contentAlignment = Alignment.Center ) { Text(text = "Header", color = Color.White) } }
スペースを入れない通常のレイアウトを作成する LazyVerticalGrid(...) { … items(items = items, span = {
GridItemSpan(1) }) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier.fillMaxWidth() .background(color = Color.LightGray) .padding(8.dp) ) { Image(painter = painterResource(R.drawable.ic_launcher_foreground), null) Text(text = it) } }
スペースを入れない通常のレイアウトを作成する LazyVerticalGrid(...) { … item(span = { GridItemSpan(GRID_COLUMN_SIZE) }) {
Box( modifier = Modifier .fillMaxWidth() .height(100.dp) .background(Color.DarkGray), contentAlignment = Alignment.Center ) { Text(text = "Footer", color = Color.White) } }
スペースを入れない 通常のレイアウト これをベースにして、グリッ ド間にスペースを入れられ ないか考えてみる
contentPaddingを使う
contentPaddingを使う LazyVerticalGrid( … contentPadding = PaddingValues(8.dp) ) {
実行結果
実行結果 LazyVerticalGridの上下左 右にpaddingが設定される
verticalArrangementや horizontalArrangementでspacedByを 使ってスペースを設定する
spacedByを使ってスペースを設定する LazyVerticalGrid( … verticalArrangement = Arrangement.spacedBy(8.dp), horizontalArrangement = Arrangement.spacedBy(8.dp) )
{
実行結果
実行結果 • 左端と右端のアイテムに スペースが入らない
実行結果 • 左端と右端のアイテムに スペースが入らない • すべてのアイテムにス ペースが適用されるた め、意図しないところにま でスペースが入ることが ある
アイテムごとにpaddingを設定する
アイテムごとにpaddingを設定する items( items = items, span = { GridItemSpan(1) },
) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() .background(color = Color.LightGray) .padding(8.dp) ) {
アイテムごとにpaddingを設定する items( items = items, span = { GridItemSpan(1) },
) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() .background(color = Color.LightGray) .padding(8.dp) ) {
アイテムごとにpaddingを設定する items( items = items, span = { GridItemSpan(1) },
) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() .padding(8.dp) // <- 背景色の前にpaddingを入れる .background(color = Color.LightGray) .padding(8.dp) ) {
アイテムごとにpaddingを設定する items( items = items, span = { GridItemSpan(1) },
) { Column( horizontalAlignment = Alignment.CenterHorizontally, modifier = Modifier .fillMaxWidth() .padding(8.dp) // <- 背景色の前にpaddingを入れる .background(color = Color.LightGray) .padding(8.dp) ) {
実行結果 • 1つのアイテムの上下左 右にpaddingが設定され ているため、アイテムとア イテムの間のpaddingが 大きくなる
アイテムによってpaddingを動的に変える
アイテムによって paddingを動的に変 える • 左端と右端だけ8dpにす る
アイテムによって paddingを動的に変 える • 左端と右端だけ8dpにす る • アイテム間は4dpにする
アイテムによってpaddingを動的に変える private const val GRID_COLUMN_SIZE = 3 val startIndexes =
(1..items.size) .map { GRID_COLUMN_SIZE * it - (GRID_COLUMN_SIZE - 1) } .toList() val endIndexes = (1..items.size) .map { GRID_COLUMN_SIZE * it } .toList()
アイテムによってpaddingを動的に変える itemsIndexed(...) { index, item -> Column( horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier .fillMaxWidth() .padding( top = if (index < GRID_COLUMN_SIZE) 8.dp else 4.dp, bottom = if (index > items.size - GRID_COLUMN_SIZE + 1) 8.dp else 4.dp, start = if (index == 0 || startIndexes.contains(index + 1)) 8.dp else 4.dp, end = if (endIndexes.contains(index + 1)) 8.dp else 4.dp, ) ...
アイテムによってpaddingを動的に変える itemsIndexed(...) { index, item -> Column( horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier .fillMaxWidth() .padding( top = if (index < GRID_COLUMN_SIZE) 8.dp else 4.dp, bottom = if (index > items.size - GRID_COLUMN_SIZE + 1) 8.dp else 4.dp, start = if (index == 0 || startIndexes.contains(index + 1)) 8.dp else 4.dp, end = if (endIndexes.contains(index + 1)) 8.dp else 4.dp, ) ...
アイテムによってpaddingを動的に変える itemsIndexed(...) { index, item -> Column( horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier .fillMaxWidth() .padding( top = if (index < GRID_COLUMN_SIZE) 8.dp else 4.dp, bottom = if (index > items.size - GRID_COLUMN_SIZE + 1) 8.dp else 4.dp, start = if (index == 0 || startIndexes.contains(index + 1)) 8.dp else 4.dp, end = if (endIndexes.contains(index + 1)) 8.dp else 4.dp, ) ...
アイテムによってpaddingを動的に変える itemsIndexed(...) { index, item -> Column( horizontalAlignment = Alignment.CenterHorizontally,
modifier = Modifier .fillMaxWidth() .padding( top = if (index < GRID_COLUMN_SIZE) 8.dp else 4.dp, bottom = if (index > items.size - GRID_COLUMN_SIZE + 1) 8.dp else 4.dp, start = if (index == 0 || startIndexes.contains(index + 1)) 8.dp else 4.dp, end = if (endIndexes.contains(index + 1)) 8.dp else 4.dp, ) ...
実行結果 • 一見うまくいったように見 えるが…
実行結果 • 一見うまくいったように見 えるが… • 画面幅を変えるとズレる
実行結果 • 一見うまくいったように見 えるが… • 画面幅を変えるとズレる • 列数を変えてもズレる
アイテムによってpaddingを動的に変える • verticalArrangementやhorizontalArrangementで spacedByを使ってスペースを設定した上で左端と右端に動 的にpaddingを設定した場合も、アイテムサイズのズレが発 生する
LazyVerticalGridの標準機能では 実現は難しい…🤔
ならば自分で作ろう💡
1行ごとにRowを生成し、 自前でグリッドを作ってみる
自前でグリッドを作ってみる • グリッド表示したいアイテムを1行ごとの塊に分割する • そのアイテムの塊を自分でRowを使ってグリッドを表現する
自前でグリッドを作ってみる • Rowを使えばRowにpaddingを設定し、自分で列と列の間 にSpacerを追加できる • これならアイテムがずれることがないため、期待通りのレイ アウトが実現できると考えた
自前でグリッドを作ってみる const val chunkSize = 3 val items = buildList
{ repeat(7) { add("item$it") } } val chunkedItems = items.chunked(chunkSize)
自前でグリッドを作ってみる const val chunkSize = 3 val items = buildList
{ repeat(7) { add("item$it") } } val chunkedItems = items.chunked(chunkSize)
自前でグリッドを作ってみる items( items = chunkedItems, span = { GridItemSpan(chunkSize) },
) { chunkedItems ->
自前でグリッドを作ってみる items(...) { chunkedItems -> Row( Modifier.fillMaxWidth() // Rowの左右にpaddingを入れる。 //
上下でpaddingが重ならないように上下ではtopのみにpaddingを入れる .padding(top = 8.dp, start = 8.dp, end = 8.dp), ) {
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { chunkedItems.forEachIndexed { index,
item -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) { itemContent(item) } if (index < chunkedItems.lastIndex || chunkedItems.size < chunkSize) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { chunkedItems.forEachIndexed { index,
item -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) { itemContent(item) } if (index < chunkedItems.lastIndex || chunkedItems.size < chunkSize) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { chunkedItems.forEachIndexed { index,
item -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) { itemContent(item) } if (index < chunkedItems.lastIndex || chunkedItems.size < chunkSize) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { chunkedItems.forEachIndexed { index,
item -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) { itemContent(item) } if (index < chunkedItems.lastIndex || chunkedItems.size < chunkSize) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { ... val spacerLastIndex
= chunkSize - chunkedItems.size - 1 repeat(chunkSize - chunkedItems.size) { index -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) if (index < spacerLastIndex) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { ... val spacerLastIndex
= chunkSize - chunkedItems.size - 1 repeat(chunkSize - chunkedItems.size) { index -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) if (index < spacerLastIndex) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる items(...) { chunkedItems -> Row(...) { ... val spacerLastIndex
= chunkSize - chunkedItems.size - 1 repeat(chunkSize - chunkedItems.size) { index -> Box(modifier = Modifier.fillMaxWidth().weight(1f)) if (index < spacerLastIndex) { Spacer(modifier = Modifier.size(rowSpace)) } }
自前でグリッドを作ってみる • アイテムをchunked(決まった数ごと)で取り出す
自前でグリッドを作ってみる • アイテムをchunked(決まった数ごと)で取り出す • chunkごとにRowを生成し、取り出したアイテムをそのRow 内でweight=1fで均等に表示する
自前でグリッドを作ってみる • アイテムをchunked(決まった数ごと)で取り出す • chunkごとにRowを生成し、取り出したアイテムをそのRow 内でweight=1fで均等に表示する • アイテムが列数より少なくなるケースでは足りないアイテム 分、weight=1fのSpacerで埋める
自前でグリッドを作ってみる • 自分でRowを使ってグリッドを表現しているため、これでグ リッド内のスペースを自分でコントロールできるようになった • あとはこれを汎用的に使えるように拡張関数にまとめる
自前でグリッドを作ってみる inline fun <T> LazyGridScope.chunkedItems( items: List<T>, chunkSize: Int, chunkedContentPadding:
PaddingValues = PaddingValues(0.dp), rowSpace: Dp = 0.dp, noinline key: ((item: List<T>) -> Any)? = null, noinline span: (LazyGridItemSpanScope.(item: List<T>) -> GridItemSpan)? = null, noinline contentType: (item: List<T>) -> Any? = { null }, crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit, )
自前でグリッドを作ってみる inline fun <T> LazyGridScope.chunkedItems( items: List<T>, chunkSize: Int, chunkedContentPadding:
PaddingValues = PaddingValues(0.dp), rowSpace: Dp = 0.dp, noinline key: ((item: List<T>) -> Any)? = null, noinline span: (LazyGridItemSpanScope.(item: List<T>) -> GridItemSpan)? = null, noinline contentType: (item: List<T>) -> Any? = { null }, crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit, )
自前でグリッドを作ってみる inline fun <T> LazyGridScope.chunkedItems( items: List<T>, chunkSize: Int, chunkedContentPadding:
PaddingValues = PaddingValues(0.dp), rowSpace: Dp = 0.dp, noinline key: ((item: List<T>) -> Any)? = null, noinline span: (LazyGridItemSpanScope.(item: List<T>) -> GridItemSpan)? = null, noinline contentType: (item: List<T>) -> Any? = { null }, crossinline itemContent: @Composable LazyGridItemScope.(item: T) -> Unit, )
自前でグリッドを作ってみる chunkedItems( items = items, chunkSize = GRID_COLUMN_SIZE, span =
{ GridItemSpan(GRID_COLUMN_SIZE) }, rowSpace = 8.dp, chunkedContentPadding = PaddingValues(top = 8.dp, start = 8.dp, end = 8.dp) ) { item -> // ここにアイテムのレイアウトを入れる } // 下部のスペース item(span = { GridItemSpan(GRID_COLUMN_SIZE) }) { Spacer(modifier = Modifier.size(8.dp)) }
自前でグリッドを作ってみる chunkedItems( items = items, chunkSize = GRID_COLUMN_SIZE, span =
{ GridItemSpan(GRID_COLUMN_SIZE) }, rowSpace = 8.dp, chunkedContentPadding = PaddingValues(top = 8.dp, start = 8.dp, end = 8.dp) ) { item -> // ここにアイテムのレイアウトを入れる } // 下部のスペース item(span = { GridItemSpan(GRID_COLUMN_SIZE) }) { Spacer(modifier = Modifier.size(8.dp)) }
自前でグリッドを作ってみる chunkedItems( items = items, chunkSize = GRID_COLUMN_SIZE, span =
{ GridItemSpan(GRID_COLUMN_SIZE) }, rowSpace = 8.dp, chunkedContentPadding = PaddingValues(top = 8.dp, start = 8.dp, end = 8.dp) ) { item -> // ここにアイテムのレイアウトを入れる } // 下部のスペース item(span = { GridItemSpan(GRID_COLUMN_SIZE) }) { Spacer(modifier = Modifier.size(8.dp)) }
実行結果 3列の場合
実行結果 4列の場合
無事に期待通りのレイアウトを実現できた 🎉
おわりに • 今回実装したコードは以下のgistに公開しています ◦ https://sh.syarihu.net/gist-chunked-items • itemsIndexedのようにindexを取得したいケースのサンプル コードも記載しているので、参考までにどうぞ
Jetpack Composeでグリッドに柔 軟にスペースを入れたい shibuya.apk #41 2023/04/21 (Fri.) Taichi Sato /
@syarihu Giftmall, Inc. Android Engineer