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
Sponsored
·
SiteGround - Reliable hosting with speed, security, and support you can count on.
→
mikan
June 21, 2023
Technology
0
480
Composeでカスタムレイアウトを組むときの気持ち
YUMEMI.grow Mobile #4の発表資料
mikan
June 21, 2023
Tweet
Share
More Decks by mikan
See All by mikan
Navigation3でViewModelにデータを渡す方法
mikanichinose
0
630
「脳に収まるコードの書き方」を読んで学んだこと
mikanichinose
1
180
RepositoryのSSoT化
mikanichinose
0
83
Kotlin Multiplatform 始めました
mikanichinose
1
140
Web APIをなぜつくるのか
mikanichinose
0
3.5k
イベントをどう管理するか
mikanichinose
3
390
ライブラリでしかお目にかかれない珍しい実装
mikanichinose
2
480
Strong Skipping Mode によってrecompositionはどう変わったのか
mikanichinose
0
360
Modeling UiEvent
mikanichinose
0
120
Other Decks in Technology
See All in Technology
Claude Code for NOT Programming
kawaguti
PRO
1
290
Claude Code で画面の仕様書を作ろう
zozotech
PRO
0
270
SchooでVue.js/Nuxtを技術選定している理由
yamanoku
3
7k
意外と知ってそうでしらない、Reserved Instances の世界
mappie_kochi
0
110
生成AIを活用した音声文字起こしシステムの2つの構築パターンについて
miu_crescent
PRO
3
260
React 19時代のコンポーネント設計ベストプラクティス
uhyo
12
4k
ECSネイティブのBlue/Green デプロイを攻略しよう ~CodeDeployとの違いから、デプロイフロー実装まで~
ideaws
2
270
Open Table Formatにおけるストレージ抽象化の比較
lycorptech_jp
PRO
0
150
LLMOpsのこれまでとこれからを学ぶ
nsakki55
2
580
OWASP Top 10:2025 リリースと 少しの日本語化にまつわる裏話
okdt
PRO
3
1k
GitHub Copilot CLI 現状確認会議(2026年2月のすがた)
torumakabe
1
210
ブロックテーマ、WordPress でウェブサイトをつくるということ / 2026.02.07 Gifu WordPress Meetup
torounit
0
220
Featured
See All Featured
For a Future-Friendly Web
brad_frost
182
10k
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
260
Docker and Python
trallard
47
3.7k
Deep Space Network (abreviated)
tonyrice
0
72
Music & Morning Musume
bryan
47
7.1k
No one is an island. Learnings from fostering a developers community.
thoeni
21
3.6k
Statistics for Hackers
jakevdp
799
230k
The Pragmatic Product Professional
lauravandoore
37
7.2k
Bioeconomy Workshop: Dr. Julius Ecuru, Opportunities for a Bioeconomy in West Africa
akademiya2063
PRO
1
62
Optimizing for Happiness
mojombo
379
71k
Leveraging LLMs for student feedback in introductory data science courses - posit::conf(2025)
minecr
0
170
Visualizing Your Data: Incorporating Mongo into Loggly Infrastructure
mongodb
49
9.9k
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