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
380
Composeでカスタムレイアウトを組むときの気持ち
YUMEMI.grow Mobile #4の発表資料
mikan
June 21, 2023
Tweet
Share
More Decks by mikan
See All by mikan
APIとはなにか
mikanichinose
0
120
イベントをどう管理するか
mikanichinose
2
140
ライブラリでしかお目にかかれない珍しい実装
mikanichinose
2
380
Strong Skipping Mode によってrecompositionはどう変わったのか
mikanichinose
0
200
Modeling UiEvent
mikanichinose
0
41
UIの構成要素に関する考察
mikanichinose
0
39
再考: 監視可能オブジェクト
mikanichinose
0
55
マルチモジュール懐疑派だったかつての自分に送る マルチモジュールの効能
mikanichinose
0
200
書評: 単体テストの考え方/使い方
mikanichinose
0
230
Other Decks in Technology
See All in Technology
20241220_S3 tablesの使い方を検証してみた
handy
4
710
GitHub Copilot のテクニック集/GitHub Copilot Techniques
rayuron
39
17k
成果を出しながら成長する、アウトプット駆動のキャッチアップ術 / Output-driven catch-up techniques to grow while producing results
aiandrox
0
400
クレカ・銀行連携機能における “状態”との向き合い方 / SmartBank Engineer LT Event
smartbank
2
110
プロダクト開発を加速させるためのQA文化の築き方 / How to build QA culture to accelerate product development
mii3king
1
290
AWS re:Invent 2024 recap
hkoketsu
0
180
Fanstaの1年を大解剖! 一人SREはどこまでできるのか!?
syossan27
2
270
[Oracle TechNight#85] Oracle Autonomous Databaseを使ったAI活用入門
oracle4engineer
PRO
1
150
OCI技術資料 : ファイル・ストレージ 概要
ocise
3
11k
レンジャーシステムズ | 会社紹介(採用ピッチ)
rssytems
0
300
組み込みアプリパフォーマンス格闘記 検索画面編
wataruhigasi
1
170
効率的な技術組織が作れる!書籍『チームトポロジー』要点まとめ
iwamot
2
130
Featured
See All Featured
Rails Girls Zürich Keynote
gr2m
94
13k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.3k
Dealing with People You Can't Stand - Big Design 2015
cassininazir
365
25k
Site-Speed That Sticks
csswizardry
2
190
Code Reviewing Like a Champion
maltzj
521
39k
Building a Modern Day E-commerce SEO Strategy
aleyda
38
7k
10 Git Anti Patterns You Should be Aware of
lemiorhan
PRO
656
59k
Scaling GitHub
holman
459
140k
Done Done
chrislema
182
16k
Testing 201, or: Great Expectations
jmmastey
41
7.1k
Adopting Sorbet at Scale
ufuk
73
9.1k
How STYLIGHT went responsive
nonsquared
96
5.2k
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