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
Composeでカスタムレイアウトを組むときの気持ち
Search
mikan
June 21, 2023
Technology
0
450
Composeでカスタムレイアウトを組むときの気持ち
YUMEMI.grow Mobile #4の発表資料
mikan
June 21, 2023
Tweet
Share
More Decks by mikan
See All by mikan
Navigation3でViewModelにデータを渡す方法
mikanichinose
0
240
「脳に収まるコードの書き方」を読んで学んだこと
mikanichinose
1
110
RepositoryのSSoT化
mikanichinose
0
44
Kotlin Multiplatform 始めました
mikanichinose
1
130
Web APIをなぜつくるのか
mikanichinose
0
2.3k
イベントをどう管理するか
mikanichinose
3
340
ライブラリでしかお目にかかれない珍しい実装
mikanichinose
2
440
Strong Skipping Mode によってrecompositionはどう変わったのか
mikanichinose
0
310
Modeling UiEvent
mikanichinose
0
81
Other Decks in Technology
See All in Technology
DBのスキルで生き残る技術 - AI時代におけるテーブル設計の勘所
soudai
PRO
54
21k
Reach American Airlines®️ Instantly: 19 Calling Methods for Fast Support in the USA
flyamerican
1
180
freeeのアクセシビリティの現在地 / freee's Current Position on Accessibility
ymrl
2
240
Delta airlines Customer®️ USA Contact Numbers: Complete 2025 Support Guide
deltahelp
0
930
Rethinking Incident Response: Context-Aware AI in Practice
rrreeeyyy
1
130
ABEMAの本番環境負荷試験への挑戦
mk2taiga
4
310
関数型プログラミングで 「脳がバグる」を乗り越える
manabeai
2
210
Contributing to Rails? Start with the Gems You Already Use
yahonda
2
110
Operating Operator
shhnjk
1
620
Enhancing SaaS Product Reliability and Release Velocity through Optimized Testing Approach
ropqa
1
240
改めてAWS WAFを振り返る~業務で使うためのポイント~
masakiokuda
2
300
「クラウドコスト絶対削減」を支える技術—FinOpsを超えた徹底的なクラウドコスト削減の実践論
delta_tech
4
180
Featured
See All Featured
Writing Fast Ruby
sferik
628
62k
Distributed Sagas: A Protocol for Coordinating Microservices
caitiem20
331
22k
Facilitating Awesome Meetings
lara
54
6.4k
Git: the NoSQL Database
bkeepers
PRO
430
65k
Practical Orchestrator
shlominoach
189
11k
KATA
mclloyd
30
14k
GraphQLの誤解/rethinking-graphql
sonatard
71
11k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
281
13k
Documentation Writing (for coders)
carmenintech
72
4.9k
How GitHub (no longer) Works
holman
314
140k
Six Lessons from altMBA
skipperchong
28
3.9k
YesSQL, Process and Tooling at Scale
rocio
173
14k
Transcript
Compose でカスタムレイアウト を組むときの気持ち YUMEMI.grow Mobile #4 一瀬喜弘(@mikanIchinose)
自己紹介 object Mikan { val name = " 一瀬喜弘" val
company = "karabiner.tech" val hoby = listOf( " 漫画", " アニメ", " ゲーム", " 折り紙", "OSS 開発・コントリビュート", ) }
今日発表すること 🙏 Google I/O, WWDC に関連しない 📐 Layout API ❌
measurable, placeable, constraints ⭕ UI 要素のポジションを決める計算をするときの思考 ↓ ソースコード
題材: TopAppBar
要件 タイトルを中央寄せ タイトルが長すぎて領域に収まらなければ3 点リーダーで省略 左にボタンが1 つ、右にボタンが複数ある非対称なレイアウト
長いタイトルの中央寄せの挙動が Flutter と異なる Flutter: AppBar package:flutter/material.dart Compose: CenterAlignedTopAppBar androidx.compose.material3:material3
長いタイトルの中央寄せの挙動が Flutter と異なる Flutter: AppBar package:flutter/material.dart Compose: CenterAlignedTopAppBar androidx.compose.material3:material3
タイトルのレイアウトを細かく 制御する必要がある
タイトル ( 中央にある UI 要素 ) が満すべき要件 タイトルが短いときは中央寄せ タイトルが長くなって隣接するアイコンに接し始めたら場所に応じて右寄せまたは左寄せ タイトルがさらに長くなってもう片方のアイコンにも接し始めたら省略開始
ひとまず中央寄せなレイアウト を組んでみよう
インターフェース CenterAlignedTopAppBar を参考にします @Composable fun MyCenterAlignedTopAppBar( title: @Composable () ->
Unit, modifier: Modifier = Modifier, navigationIcon: @Composable () -> Unit = {}, actions: @Composable () -> Unit = {} ): Unit
Layout Layout( modifier = modifier, content = { Box( modifier
= Modifier.layoutId("navigationIcon"), ) { navigationIcon() } Box(modifier = Modifier.layoutId("title")) { title() } Box( modifier = Modifier.layoutId("actions"), ) { actions() } }, ) { measurables, constraints -> // ...
つづき ) { measurables, constraints -> val navigationIconPlaceable = measurables.first
{ it.layoutId == "navigationIcon" } .measure(constraints.copy(minWidth = 0)) val actionsPlaceable = measurables.first { it.layoutId == "actions" } .measure(constraints.copy(minWidth = 0)) // タイトルの横幅がアイコンを侵略しないようにする val maxTitleWidth = (constraints.maxWidth - navigationIconPlaceable.width - actionsPlaceable.width) .coerceAtLeast(0) // 0 未満にならないようにガードする val titlePlaceable = measurables.first { it.layoutId == "title" } .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) // ...
つづき val maxTitleWidth = (constraints.maxWidth - navigationIconPlaceable.width - actionsPlaceable.width) .coerceAtLeast(0)
// 0 未満にならないようにガードする ) { measurables, constraints -> val navigationIconPlaceable = measurables.first { it.layoutId == "navigationIcon" } .measure(constraints.copy(minWidth = 0)) val actionsPlaceable = measurables.first { it.layoutId == "actions" } .measure(constraints.copy(minWidth = 0)) // タイトルの横幅がアイコンを侵略しないようにする val titlePlaceable = measurables.first { it.layoutId == "title" } .measure(constraints.copy(minWidth = 0, maxWidth = maxTitleWidth)) // ...
つづき // CenterAlignedTopAppBar はいい感じに高さを計算していたが、面倒なので一旦固定 val height = 64.dp.roundToPx() layout(constraints.maxWidth, height)
{ // 左寄せ navigationIconPlaceable.placeRelative( x = 0, y = (height - navigationIconPlaceable.height) / 2 ) // 中央寄せ titlePlaceable.placeRelative( x = (constraints.maxWidth - titlePlaceable.width) / 2, y = (height - titlePlaceable.height) / 2 ) // 右寄せ actionsPlaceable.placeRelative( x = (constraints.maxWidth - actionsPlaceable.width), y = (height - actionsPlaceable.height) / 2 ) } )
placeable によるレイアウト : ナビゲーションアイコン y x (0, 0) (?, ?)
navigationIconPlaceable.placeRelative( x = 0, // 左詰め y = (height - navigationIconPlaceable.height) / 2 // 中央 )
placeable によるレイアウト : ナビゲーションアイコン y x (0, 0) (0, ?)
navigationIconPlaceable.placeRelative( x = 0, // 左詰め y = (height - navigationIconPlaceable.height) / 2 // 中央 )
placeable によるレイアウト : ナビゲーションアイコン navigationIconPlaceable.placeRelative( x = 0, // 左詰め
y = (height - navigationIconPlaceable.height) / 2 // 中央 )
placeable によるレイアウト : ナビゲーションアイコン navigationIconPlaceable.height height navigationIconPlaceable.placeRelative( x = 0,
// 左詰め y = (height - navigationIconPlaceable.height) / 2 // 中央 )
placeable によるレイアウト : ナビゲーションアイコン navigationIconPlaceable.height height navigationIconPlaceable.placeRelative( x = 0,
// 左詰め y = (height - navigationIconPlaceable.height) / 2 // 中央 )
placeable によるレイアウト : ナビゲーションアイコン (0, 0) (0, (height - navigationIconPlaceable.height)
/ 2) navigationIconPlaceable.placeRelative( x = 0, // 左詰め y = (height - navigationIconPlaceable.height) / 2 // 中央 )
placeable によるレイアウト : タイトル x = (constraints.maxWidth - titlePlaceable.width) /
2 y = (height - titlePlaceable.height) / 2 titlePlaceable.placeRelative( x = (constraints.maxWidth - titlePlaceable.width) / 2, y = (height - titlePlaceable.height) / 2 )
placeable によるレイアウト : アクション x = (constraints.maxWidth - actionsPlaceable.width) y
= (height - actionsPlaceable.height) / 2 actionsPlaceable.placeRelative( x = (constraints.maxWidth - actionsPlaceable.width), y = (height - actionsPlaceable.height) / 2 )
中くらいの長さにおける右寄せ、左寄せを考慮する // .. val titleX = if ((constraints.maxWidth / 2)
< ((titlePlaceable.width / 2) + navigationIconPlaceable.width)) { // 左寄せ navigationIconPlaceable.width } else if ((constraints.maxWidth / 2) < ((titlePlaceable.width / 2) + actionsPlaceable.width)) { // 右寄せ constraints.maxWidth - actionsPlaceable.width - titlePlaceable.width } else { // 中央寄せ (constraints.maxWidth - titlePlaceable.width) / 2 } // .. layout(constraints.maxWidth, height) { // .. titlePlaceable.placeRelative( x = titleX, y = (height - titlePlaceable.height) / 2 ) // ... }
左寄せが必要なシチュエーション if ((constraints.maxWidth / 2) < ((titlePlaceable.width / 2) +
navigationIconPlaceable.width)) { val titleX = navigationIconPlaceable.width // ...
左寄せが必要なシチュエーション navigationIconPlaceable.width val titleX = if ((constraints.maxWidth / 2) <
((titlePlaceable.width / 2) + navigationIconPlaceable.width)) { // ...
右寄せが必要なシチュエーション } else if ((constraints.maxWidth / 2) < ((titlePlaceable.width /
2) + actionsPlaceable.width)) { val titleX = // ... constraints.maxWidth - actionsPlaceable.width - titlePlaceable.width // ...
右寄せが必要なシチュエーション constraints.maxWidth - actionsPlaceable.width - titlePlaceable.width val titleX = //
... } else if ((constraints.maxWidth / 2) < ((titlePlaceable.width / 2) + actionsPlaceable.width)) { // ...
完成!! Full source https://gist.github.com/mikanIchinose/551303ea02bd457dfbbde92896384d65
まとめ カスタムレイアウトを組むときは要素の始点をどこにどうやって持っていくかを座標系を意識して考える 実際はもっと時間かかってますし、試行錯誤しまくってますw