Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
Modifier.Nodeに移行してパフォーマンスを比べてみた
Search
usuiat
December 13, 2023
Programming
560
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
Modifier.Nodeに移行してパフォーマンスを比べてみた
YUMEMI.grow Mobile #9
https://yumemi.connpass.com/event/302462/
usuiat
December 13, 2023
More Decks by usuiat
See All by usuiat
Google I/O 2025 報告LT会 Small language models with Google AI Edge
usuiat
0
580
発信から広がった地方エンジニアのキャリア
usuiat
1
78
タッチイベントの仕組みを理解してジェスチャーを使いこなそう
usuiat
1
1.8k
Google I/O 2024 報告LT会(Androidのオンデバイス生成AI)
usuiat
0
1k
reifiedって何ですか?
usuiat
2
1.4k
Other Decks in Programming
See All in Programming
なぜCTOを降りてFDEを選んだのか?〜なぜプロダクト企業がFDEで顧客の現場に踏み込むのか〜
gonta
1
120
パズルゲームの作り方 / how to make puzzle games
kaityo256
PRO
2
260
iOS 27でニュースアプリはどう変わる!? 〜日経電子版の新機能対応と、開発事例から〜
lynnswap
7
13k
世界の中心で、AI(App Intents)をさけぶ ー App Intents中心設計の実践ガイド
touyou
0
750
難しいけど、読めた。- OSSの入口に立った話。
sts11142
0
130
20260914 AIエージェント時代のPlatform Engineering LLM基盤とプロダクトの責務境界線
kanfab1
7
2.3k
Go × SIMDで高速化するベクトル検索 ~ルーフラインモデルでSIMDが効く境界を探れ! ~
po3rin
1
5.9k
Workers Cache を知る
syumai
0
320
市販E-Readerを乗っ取れ 〜Embedded Swiftで電子ペーパーガジェットを制御する〜
trickart
0
230
wkhtmltopdfの次どうするか問題2026
willnet
2
1.8k
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
520
スマートフォンでモールス信号を送受信する 〜スマートフォンのLEDとカメラで作る光通信の設計と実装〜
atsuki_seo
0
220
Featured
See All Featured
Prompt Engineering for Job Search
mfonobong
0
470
Building Experiences: Design Systems, User Experience, and Full Site Editing
marktimemedia
1
620
HDC tutorial
michielstock
2
940
What's in a price? How to price your products and services
michaelherold
247
13k
Build your cross-platform service in a week with App Engine
jlugia
234
19k
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.5k
How to Talk to Developers About Accessibility
jct
2
560
Scaling GitHub
holman
464
140k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
320
Ethics towards AI in product and experience design
skipperchong
2
410
Navigating the moral maze — ethical principles for Al-driven product design
skipperchong
2
590
RailsConf & Balkan Ruby 2019: The Past, Present, and Future of Rails at GitHub
eileencodes
141
35k
Transcript
Modifier.Nodeに移行して パフォーマンスを比べてみた 2023.12.13 YUMEMI.grow Mobile #9 usuiat
usuiat 自己紹介 Atsushi Usui / うっすぃ~ / usuiat Androidエンジニア 静岡県在住
X/GitHub : usuiat Blog : https://engawapg.net/
usuiat 本日の発表内容 ZoomableというJetpack Composeのライブラリを Modifier.Nodeに移行したので 移行方法の概要と 移行前後のパフォーマンスの比較結果を 共有します Modifier.Node移行の具体例はまだまだ情報が少ないので、これから移行する人の参考になれば幸いです
usuiat Zoomable Jetpack Composeライブラリ 最短1行で画像などをズーム可能にする ピンチジェスチャー、ダブルタップ、タップ&ドラッグ対応 HorizontalPagerと組み合わせ可 https://github.com/usuiat/Zoomable
usuiat Modifier.Node Modifier: Composeの見た目や振る舞いを変更するためのインターフェース 状態を持つModifier: これまではModifier.composedで実装されてきた 現在はパフォーマンスに優れるModifier.Nodeへの移行が進みつつある (参考) Modifier.Nodeを使いましょう Compose
Modifiers deep dive 実践 脱Modifier.composed / Let's Modifier.Node Create custom modifiers
usuiat ZoomableのModifier.Node移行 ZoomableもModifier.composedを使っていたので、 Modifier.Nodeに移行しました。 本日は実装の概要を紹介します。 ソースコード詳しく見たい方はGitHubのPRをご覧ください。 https://github.com/usuiat/Zoomable/pull/131/files
usuiat fun Modifier.zoomable(): Modifier = composed { Modifier .onSizeChanged {
size -> zoomState.setLayoutSize(size.toSize()) } .nestedScroll(connection, dispatcher) .pointerInput(zoomState) { detectTransformGestures( onGesture = { centroid, pan, zoom -> scope.launch { zoomState.applyGesture( pan = pan, zoom = zoom, position = centroid, ) } }, ) } .graphicsLayer { scaleX = zoomState.scale scaleY = zoomState.scale translationX = zoomState.offsetX translationY = zoomState.offsetY } } Modifier.zoomable (composed版)の構造 1) pointerInput()でジェスチャーを検出し、ZoomStateを変更 2) graphicsLayer()でZoomStateの状態をレイアウトに反映 4) nestedScroll()で上位のPagerなどにスクロールイベントを伝達 3) onSizeChanged()でコンポーネントのサイズを取得 (ドラッグ可能な範囲の算出に使う) Modifier.composed()を使用
usuiat ZoomableNodeの実装方針 1) pointerInput()でジェスチャーを検出し、ZoomStateを変更 2) graphicsLayer()でZoomStateの状態をレイアウトに反映 4) nestedScroll()で上位のPagerなどにスクロールイベントを伝達 3) onSizeChanged()でコンポーネントのサイズを取得
(ドラッグ可能な範囲の算出に使う) PointerInputModifierNodeを実装 LayoutModifierNodeを実装 nestedScrollModifierNodeに委任 private class ZoomableNode(): PointerInputModifierNode, LayoutModifierNode, DelegatingNode() { ... }
usuiat private class ZoomableNode(): PointerInputModifierNode, DelegatingNode() { override fun onPointerEvent(
pointerEvent: PointerEvent, pass: PointerEventPass, bounds: IntSize ) { pointerInputNode.onPointerEvent(pointerEvent, pass, bounds) } override fun onCancelPointerInput() { pointerInputNode.onCancelPointerInput() } val pointerInputNode = delegate(SuspendingPointerInputModifierNode { detectTransformGestures( onGesture = { centroid, pan, zoom -> coroutineScope.launch { zoomState.applyGesture(pan, zoom, centroid) } }, ) }) } 1) ジェスチャーを検出し、ZoomStateを変更 PointerInputModifierNodeのonPointerEvent()と onCancelPointerInput()をoverride pointerInputNodeの処理を呼び出す SuspendingPointerInputModifierNodeにはPointerInputScopeの 処理を渡せるので、Modifier.pointerInputに書いていた処理を ほぼそのまま書ける。 pointerInputNodeはSuspendingPointerInputModifierNode 実際の処理はSuspendingPointerInputModifierNodeに 委任する PointerInputModifierNodeを実装
usuiat private class ZoomableNode(): LayoutModifierNode, DelegatingNode() { var measuredSize =
Size.Zero override fun MeasureScope.measure( measurable: Measurable, constraints: Constraints ): MeasureResult { val placeable = measurable.measure(constraints) measuredSize = IntSize(placeable.measuredWidth, placeable.measuredHeight).toSize() zoomState.setLayoutSize(measuredSize) return layout(placeable.width, placeable.height) { placeable.placeWithLayer(x = 0, y = 0) { scaleX = zoomState.scale scaleY = zoomState.scale translationX = zoomState.offsetX translationY = zoomState.offsetY } } } } 2) ZoomStateの状態をレイアウトに反映 3) コンポーネントのサイズを取得 MeasureScope.measure()をoverride placeableからコンポーネントのサイズを取得できる placeWithLayer()に、Modifier.graphicsLayer()の処理を そのまま書ける LayoutModifierNodeを実装
usuiat private class ZoomableNode(): DelegatingNode() { val connection = object
: NestedScrollConnection{} val dispatcher = NestedScrollDispatcher() init { delegate(nestedScrollModifierNode(connection, dispatcher)) } } 4) スクロールイベントを伝達 initブロックでnestedScrollModifierNodeを作成し、 delegateで委任する DelegatingNodeを継承
usuiat このほか、ModifierNodeElementの実装などが必要ですが、 今日は省略します。 ソースコード詳しく見たい方はGitHubのPRをご覧ください。 https://github.com/usuiat/Zoomable/pull/131/files
usuiat パフォーマンスは良くなったのか???
usuiat 見た目には何も変わらなかった もともとパフォーマンス悪かったわけではないので、それはそう🙄
usuiat 見た目で分からないなら計測しよう! Android Studio Flamingo以降ではProfilerでComposable関数をトレースできる😎
usuiat 再コンポーズの処理時間を計測 @Composable fun Sample() { var offset by remember
{ mutableStateOf(0.dp) } Image( painter = painter, contentDescription = null, contentScale = ContentScale.Fit, modifier = Modifier .fillMaxSize() .offset(y = offset) .zoomable( zoomState = rememberZoomState(), onTap = { offset += 10.dp } ), ) } Modifier.Nodeでは、再コンポーズ時のパフォーマンスが改善しているはず! onTapでoffsetを変更することによって再コンポーズを発生させて、 Image()の実行時間を測定する。
usuiat トレース結果はこんな感じ 指が触れ た 指が離れ た ダブルタップかどうかの 判定のための待ち時間 再コンポー ズ
このあたりにImage()がある
usuiat Modifier.composed版 10回平均 8.2ms 最大 20.78ms 最小 4.16ms materializeModifierの処理時間が Image()全体の半分程度を占めて
いる。 materializeがいかに大変かとい う話は、この動画で説明されて います。 https://www.youtube.com/watch? v=BjGX2RftXsU Image()の処理時間
usuiat Modifier.Node版 10回平均 3.72ms 最大 5.24ms 最小 1.98ms Image()の処理時間 materialize処理が
消えている
usuiat 速くなった! Modifier.composed Modifier.Node 10回平均 8.2ms 3.72ms 最大 20.78ms 5.24ms
最小 4.16ms 1.98ms 平均処理時間が約半分になった
usuiat ベータ版提供中 Zoomable v1.6.0-beta2提供中です。 不具合等あればご連絡お願いします https://github.com/usuiat/Zoomable/releases/tag/v1.6.0-beta2