Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
[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
[DroidKaigi 2026] Making UI specifications visible: Android UI development in the AI agent era supported by Compose Screenshot Testing and galleries
syarihu
0
670
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.3k
[shibuya.apk #46] Composableの枠を超えてアニメーションする / Animation beyond Composable
syarihu
0
150
[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
Other Decks in Technology
See All in Technology
「ピッケル本」日本語版は4.0(第6版)が出版されるべき / pickaxe4-nagoyark05
kakutani
2
270
Claude in Chrome 入門 / Introduction to Claude in Chrome
cielo1985
0
880
人間はどの意思決定を手放せるのか
kawasima
15
7.9k
サーバーレスをどこまで使う? WebRTC対戦ゲームで選んだVPSとの共存設計
kaidouji85
0
300
2026-09-08 そのJavaモダナイゼーション、AIに丸投げで大丈夫?IBM Bobで変わる品質と効率
yutanonaka
1
120
え、こんなに早く改修できるの?──新人エンジニアとスクラムマスターの2人が語る、AI×アジャイル開発の現場
ysasago
2
640
AgentCore Runtime上にAgentic Coding基盤を構築・展開する際の設計ポイントと限界点 / Design considerations and limitations when building an agentic coding platform on AgentCore Runtime
har1101
5
630
Cloudflare Workers 向けアプリを C# で構築する ~WASM Native AOT への道~
nenonaninu
1
930
aws-iot-platform-architecture-use-cases.pdf
ma2shita
0
480
Azure Serverless 2026:Production-ready な AI エージェント基盤 / Azure Serverless 2026: Production-Ready AI Agent Platform
miyake
2
490
ADKで始める業務改善 - AIエージェント開発時の考えと設計
harappa80
2
250
アプリログインとWeb認証基盤をつなぐ ASWebAuthenticationSession 作法
shimastripe
1
370
Featured
See All Featured
How to train your dragon (web standard)
notwaldorf
97
6.8k
How to Talk to Developers About Accessibility
jct
2
550
The Impact of AI in SEO - AI Overviews June 2024 Edition
aleyda
6
1.2k
For a Future-Friendly Web
brad_frost
183
10k
SEO in 2025: How to Prepare for the Future of Search
ipullrank
3
3.8k
AI in Enterprises - Java and Open Source to the Rescue
ivargrimstad
0
1.5k
svc-hook: hooking system calls on ARM64 by binary rewriting
retrage
2
580
Amusing Abliteration
ianozsvald
1
310
Automating Front-end Workflow
addyosmani
1369
210k
What Being in a Rock Band Can Teach Us About Real World SEO
427marketing
0
1.1k
A Soul's Torment
seathinner
8
3.6k
Self-Hosted WebAssembly Runtime for Runtime-Neutral Checkpoint/Restore in Edge–Cloud Continuum
chikuwait
0
820
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