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
650
[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
Digitization部 紹介資料
sansan33
PRO
2
7.7k
え?フロントエンドエンジニアの ワイがインフラも!?
puku0x
1
730
少人数チームで実現する、大規模AWSサーバーレス基盤における"揺らがない"信頼性運用
maimyyym
1
110
SO-101×VLAによる3色キューブのピック&プレース
abeja
0
220
案件に一番詳しいAIを Amazon Bedrock AgentCore で作る ― 知見が知見を生むチームへ / Compounding Knowledge with AgentCore
yusukeshimizu
1
140
35分でわかるEffective Platform Engineering
nwiizo
3
290
システム思考で問題に対処する
yussak
0
330
LLM・AIエージェントシステムベストプラクティス
shibuiwilliam
6
1.4k
20260804_Q4AzureUpdateBite_FabricDataAgentの精度を高める設計.pdf
matayuuu
1
140
Sansan Engineering Unit 紹介資料
sansan33
PRO
1
4.9k
メルカリのグローバルアプリで挑んだ AlloyDB 運用と課題解決の実践記
hatappi
0
250
AI-DLC実践録_フルサイクル開発への挑戦
miyuc
0
240
Featured
See All Featured
How Software Deployment tools have changed in the past 20 years
geshan
1
34k
Thoughts on Productivity
jonyablonski
76
5.3k
Art, The Web, and Tiny UX
lynnandtonic
304
22k
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
6k
Google's AI Overviews - The New Search
badams
0
1.1k
Visual Storytelling: How to be a Superhuman Communicator
reverentgeek
2
620
Tips & Tricks on How to Get Your First Job In Tech
honzajavorek
1
700
The B2B funnel & how to create a winning content strategy
katarinadahlin
PRO
1
470
YesSQL, Process and Tooling at Scale
rocio
174
15k
Making the Leap to Tech Lead
cromwellryan
135
10k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.6k
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