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
370
Composeでカスタムレイアウトを組むときの気持ち
YUMEMI.grow Mobile #4の発表資料
mikan
June 21, 2023
Tweet
Share
More Decks by mikan
See All by mikan
ライブラリでしかお目にかかれない珍しい実装
mikanichinose
2
350
Strong Skipping Mode によってrecompositionはどう変わったのか
mikanichinose
0
170
Modeling UiEvent
mikanichinose
0
36
UIの構成要素に関する考察
mikanichinose
0
38
再考: 監視可能オブジェクト
mikanichinose
0
53
マルチモジュール懐疑派だったかつての自分に送る マルチモジュールの効能
mikanichinose
0
190
書評: 単体テストの考え方/使い方
mikanichinose
0
220
ComposeでリストUIをDraggableにする方法
mikanichinose
0
1.2k
Composeのライフサイクル対応を支援するLifecycleEventEffectの紹介
mikanichinose
1
640
Other Decks in Technology
See All in Technology
[CV勉強会@関東 ECCV2024 読み会] オンラインマッピング x トラッキング MapTracker: Tracking with Strided Memory Fusion for Consistent Vector HD Mapping (Chen+, ECCV24)
abemii
0
220
IBC 2024 動画技術関連レポート / IBC 2024 Report
cyberagentdevelopers
PRO
0
110
【若手エンジニア応援LT会】ソフトウェアを学んできた私がインフラエンジニアを目指した理由
kazushi_ohata
0
150
SREが投資するAIOps ~ペアーズにおけるLLM for Developerへの取り組み~
takumiogawa
1
180
Engineer Career Talk
lycorp_recruit_jp
0
160
エンジニア人生の拡張性を高める 「探索型キャリア設計」の提案
tenshoku_draft
1
120
【Startup CTO of the Year 2024 / Audience Award】アセンド取締役CTO 丹羽健
niwatakeru
0
990
EventHub Startup CTO of the year 2024 ピッチ資料
eventhub
0
110
Terraform Stacks入門 #HashiTalks
msato
0
350
隣接領域をBeyondするFinatextのエンジニア組織設計 / beyond-engineering-areas
stajima
1
270
ノーコードデータ分析ツールで体験する時系列データ分析超入門
negi111111
0
410
OCI 運用監視サービス 概要
oracle4engineer
PRO
0
4.8k
Featured
See All Featured
Building Better People: How to give real-time feedback that sticks.
wjessup
364
19k
Fantastic passwords and where to find them - at NoRuKo
philnash
50
2.9k
How GitHub (no longer) Works
holman
310
140k
Helping Users Find Their Own Way: Creating Modern Search Experiences
danielanewman
29
2.3k
Rails Girls Zürich Keynote
gr2m
94
13k
Fireside Chat
paigeccino
34
3k
The MySQL Ecosystem @ GitHub 2015
samlambert
250
12k
Producing Creativity
orderedlist
PRO
341
39k
Responsive Adventures: Dirty Tricks From The Dark Corners of Front-End
smashingmag
250
21k
Building a Scalable Design System with Sketch
lauravandoore
459
33k
Cheating the UX When There Is Nothing More to Optimize - PixelPioneers
stephaniewalter
280
13k
Designing Experiences People Love
moore
138
23k
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