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
ユーザーも開発者も悩ませない TV アプリ開発 ~Compose の内部実装から学ぶフォーカス制御~
Search
Daichi Takeya
September 11, 2025
Programming
480
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ユーザーも開発者も悩ませない TV アプリ開発 ~Compose の内部実装から学ぶフォーカス制御~
Daichi Takeya
September 11, 2025
More Decks by Daichi Takeya
See All by Daichi Takeya
Worry free TV App development for users and developers - a focus on Compose internal implementation
taked137
0
34
縦横無尽に駆け回るフォーカスをユーザーフレンドリーに制御する feat. Compose for TV
taked137
0
100
Other Decks in Programming
See All in Programming
エージェンティックRAGにAWSで入門しよう!
har1101
8
1.3k
Agentic UI
manfredsteyer
PRO
0
120
A2UI という光を覗いてみる
satohjohn
1
120
[2026年度第1回ORセミナー] 計画最適化ベンチャーと競技プログラミング人材
terryu16
0
250
Oxcを導入して開発体験が向上した話
yug1224
4
300
PHPで使える日時の表現と、その知り方 #frontend_phpcon_do
o0h
PRO
0
220
AIエージェントの隔離技術の徹底比較
kawayu
0
470
技術記事、AIに書かせるか、自分で書くか? 〜それでも私が自分の手で書く理由〜 / #QiitaConference
jnchito
2
1.3k
The NotImplementedError Problem in Ruby
koic
1
660
LLM本来の能力を解き放つサンドボックス技術とAI民主化への適用
yukukotani
3
3.4k
例外の正しい扱い方 そのエラー try-catchして大丈夫?
jinwatanabe
0
170
TSKaigi Night Talks 2026_TypeScriptでサプライチェーンの整合性を型に閉じ込める
geekplus_tech
0
330
Featured
See All Featured
Optimizing for Happiness
mojombo
378
71k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
360
The Myth of the Modular Monolith - Day 2 Keynote - Rails World 2024
eileencodes
28
3.5k
実際に使うSQLの書き方 徹底解説 / pgcon21j-tutorial
soudai
PRO
201
75k
The Straight Up "How To Draw Better" Workshop
denniskardys
239
140k
Bash Introduction
62gerente
615
210k
What's in a price? How to price your products and services
michaelherold
247
13k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
720
Exploring anti-patterns in Rails
aemeredith
3
400
HU Berlin: Industrial-Strength Natural Language Processing with spaCy and Prodigy
inesmontani
PRO
0
410
Applied NLP in the Age of Generative AI
inesmontani
PRO
4
2.3k
Between Models and Reality
mayunak
4
330
Transcript
ユーザーも開発者も悩ませない TV アプリ開発 ~ Compose の内部実装から学ぶフォーカス制御 ~ DroidKaigi 2025 -
2025/09/11 Daichi Takeya
自己紹介 • Daichi Takeya (X:@taked_oO, GitHub:@taked137) ◦ 2023/04 ~ 株式会社サイバーエージェント
◦ 2023/05 ~ 株式会社AbemaTV - Native Mobileチーム ◦ 2024/05 ~ 株式会社AbemaTV - Native AndroidTVチーム
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
• リモコンを利用した操作 TV アプリの操作 "TV Navigation Controller" by Android Developers,
licensed under CC BY 2.5. ・フォーカスされている Itemを目立たせる ・フォーカスに伴って 自動でスクロール する Mobile アプリほど直感的な操作が行えない フォーカスの操作性 がユーザー体験に大きく関係する
• “フォーカスの操作性向上 ” を実現する Android View ベースのライブラリ Leanback ライブラリ 命令的にUI描画を行うので意図せぬ状態の更新漏れが生じやすい
2025年にもなって Adapter を触りたくない 😤😤😤 RecyclerView Leanback とは言え、、、
• Compose for TV は二つのライブラリが存在 ◦ tv-material (stableになった🎉) ◦ tv-foundation
(stable な “compose-foundation” にマイグレーション🚛) Leanback から Compose for TV への移行 Leanback Compose 何がフォーカスされているか分からないし 縦スクロールもガタガタしてるよ 自分でなんとかする必要があります \ナンテコッタイ / 今日 完全に理解しましょう Compose for TV の時代が来た!?
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
TVアプリ向けの style 適用 • まずはフォーカス中のアイテムを目立たせる androidx.tv.material3 の Composable を使うと簡潔に設定可能 e.g.)
Surface, Card, etc… フォーカス中のアイテムが見つけやすくなった 🎉 Modifier.onFocusChanged で頑張ってもいいけど ... 見やすくなったところで本題に入っていきます
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
Compose におけるデフォルトのフォーカス移動 • 入力したキー方向で一番近い View がフォーカスされる 実際のフォーカス移動 キー方向に対応するフォーカス先 シンプルな画面だと デフォルトの挙動で十分
💯 みんな幸せ
デフォルトのフォーカス移動に関するクイズ 各アイテムはフォーカス可能であるとし、フォーカス中は赤い枠線が表示される
デフォルトのフォーカス移動に関するクイズ Q. オレンジ のアイテム上で “→” を押下すると、次は何がフォーカスされる?
デフォルトのフォーカス移動に関するクイズ Q. オレンジ のアイテム上で “→” を押下すると、次は何がフォーカスされる? ワタシだ
デフォルトのフォーカス制御ロジックの深掘り フォーカス中の View よりも “右側” にあり “左側” にはみ出ていないことが 選出条件 ❌
フォーカス中の View と “Y座標” が重なっていると 優先的に選出 ⭕ ⭕ 他の View は “左右” というよりは “上下” に位置していると感じられる👏 • なぜ “→” 押下で、一番右上のアイテム (青色) にフォーカスが当たるのか
デフォルトのフォーカス制御ロジックの深掘り フォーカス中の View よりも “右側” にあり “左側” にはみ出ていないことが 選出条件 ❌
フォーカス中の View と “Y座標” が重なっていると 優先的に選出 ⭕ ⭕ 他の View は “左右” というよりは “上下” に位置していると感じられる👏 • なぜ “→” 押下で、一番右上のアイテム (青色) にフォーカスが当たるのか この他にも多くの精巧なロジックによって フォーカス制御が行われている
デフォルトのフォーカス制御の限界 • 純粋に View の位置関係だけに基づいたフォーカス移動 ◦ Column や Row といった
Component の単位を考慮していない 先頭のアイテムが 最初にフォーカスされない Master/Detail Flow な画面 別タブに 戻ってしまう クイズで利用した画面 “→” 押下時の フォーカス対象を変えたい
フォーカス制御のアプローチ 🤖 自動制御 ・数行追加するだけで完結する ・Compose 側がロジックを隠蔽してくれる ・多くのケースでは自動制御で事足りる ・対応可能な問題には限界がある ・Compose のバージョンによってはバグが存在
🎯 手動制御 ・(おそらく) 全てのケースに対応できる ・適切に管理できないとすぐ予期せぬ挙動が生じる ・可読性が著しく低下する 適切なアプローチを採用して ユーザ と 開発者 の両方に優しいアプリを目指そう
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
Modifier.focusGroup() --- フォーカス制御を支えている基盤 • 子コンポーネントを「フォーカス探索のまとまり」としてグルーピング ◦ フォーカス移動時にグループ内の要素を優先的に探索してくれたりする default focusGroup 赤い領域
のボタン群をグルーピング “←” を押下時のフォーカス先 (グループ内の要素が優先的に選出 ) 一見地味だがフォーカス制御には欠かせない
(余談) デフォルトで focusGroup である Composable • LazyColumn, LazyRow などはデフォルトで focusGroup
もし focusGroup ではなかったら... LazyRow のフォーカス移動 描画されていない子要素 もフォーカス対象として考慮 → 番組表がスクロールできる🙌 描画中の要素 の位置関係に基づいてフォーカス対象を選出 → 番組表がスクロールできない😢
直感的なUXを実現する “フォーカス位置の保存/復元” Compose ではフォーカス位置の復元ロジックを別途実装する必要あり Viewの位置関係だけに基づいたフォーカス移動 😚 左右にある前後のタブへ自然に移動できる → 直感的 にフォーカスが移動する
😒 何の脈略もないタブへ急に移動する → 期待と異なる 挙動 フォーカス位置を復元
Modifier.focusRestorer --- フォーカス位置の保存/復元 • focusGroup の子要素に対してフォーカス位置の保存/復元を行う ・Modifier に追加するだけ ・compose-ui 1.8.0
にて遂に stable に LazyColumn は既に focusGroup なので focusRestorer だけを指定 Column は “focusRestorer → focusGroup” の順に適用
Modifier.focusRestorer を適用した後の挙動 左右の Column それぞれに focusRestorer を設定 フォーカス位置が 復元される👍 リストが更新されても
以前保存した状態が 使い回される😑 保存したフォーカス情報を 破棄する仕組みが必要
• focusRestorer の状態を初期化したい ◦ UI の差分更新だけでは初期化されない compose.runtime.key --- コンポジションの再構築を誘発 左側の別のタブが選択されたら
コンポジションツリーごと再生成 リストの更新に伴って focusRestorer の状態も 初期化される👍 リストの更新直後は 表示位置に基づいて フォーカス対象が選出される😑 更新直後は先頭のアイテムが フォーカスされるようにしたい
focusRestorer における fallback 対象の指定 • 初回などフォーカス先の復元に失敗した際のフォーカス対象 リストの最初にフォーカスできる要素を フォールバック対象に指定 フォーカス対象を 識別するためのマーカー
🎉 見失わないフォーカスに • フォーカス位置の復元 • リスト更新直後は 先頭のアイテムにフォーカス
自動制御まとめ 子要素を「フォーカス探索のまとまり」にグルーピング ・グループ内で優先的にフォーカス対象を選出 ・グループ単位の制御 を実現 (フォーカス対象の復元など ) ・Lazy Layout は
デフォルトで focusGroup Modifier.focusGroup Modifier.focusRestorer 同じグループ内でフォーカス対象を復元 ・Modifier.focusGroup とセットで扱う ・UIが変わったらフォーカス情報をリセット (keyを利用) ・FocusRequester を使用して fallback 先を指定
自動制御まとめ 子要素を「フォーカス探索のまとまり」にグルーピング ・グループ内で優先的にフォーカス対象を選出 ・グループ単位の制御 を実現 (フォーカス対象の復元など ) ・Lazy Layout は
デフォルトで focusGroup Modifier.focusGroup Modifier.focusRestorer 同じグループ内でフォーカス対象を復元 ・Modifier.focusGroup とセットで扱う ・UIが変わったらフォーカス情報をリセット (keyを利用) ・FocusRequester を使用して fallback 先を指定 この他にも もっと柔軟なフォーカス制御を行いたい!
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
手動制御が必要なケース 意図しない状態破棄の回避 Composable をフォーカス不可能に変更 AnimatedVisibility で非表示になると focusRestorer の状態も破棄 Button で
`enabled = false` にしても フォーカスできてしまう フォーカスの移動先を変更 ❌ “→” キー押下時の移動先は Compose が決定 とある Modifier で全て解決
Modifier.focusProperties --- フォーカス不可能に設定 • 内部で focusable に設定される Composable もフォーカス不可能に上書きする disabled
な Button も フォーカス可能 😢 focusProperties の canFocus により フォーカス先の選出候補から外れる 🎉
Modifier.focusProperties --- D-padによるフォーカス移動先の制御 focusProperties の設定例 キー操作の移動先を コンポーネントごとに細かく設定できる 実際にフォーカス移動先を変更する 右キー押下で fr
(紫) にフォーカス
Modifier.focusProperties --- focusGroup に対するフォーカス挙動の制御 • focusGroup に対して enter/exit 時のフォーカス先を設定 (頑張れば)
focusRestorer を代替可能 • “フォーカス状態” を任意のグループで管理できる ◦ key で CompositionTree 全体を破棄しなくて良い ◦ AnimatedVisibility の外で保存することも可能 enter/onEnter exit/onExit
(余談) FocusRequester によるフォーカス位置の復元 focusGroup のフォーカス位置を 任意のタイミングで 保存/復元 できる (focusRestorer だと
enter/exit で自動的に実行)
手動制御 (特に focusProperties) の注意点 focusProperties で 実現してみる コード量が約 2倍に 😫
安易に導入せず プロダクト要件として重要度を 考慮した上で利用する!
手動制御まとめ • Modifier.focusProperties を最終手段として使用 👍 細かい挙動まで制御可能 • Composable をフォーカス不可能に設定 •
フォーカスの移動先をカスタマイズ • focusRestorer の代替 ◦ “UI” と “状態” を独立して管理 🥶 実装の複雑化 • 実装者ですらロジックの解読が困難 • バグの混入リスク フォーカスの移動先をカスタマイズ Composable のフォーカス可否を上書き可能
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
TV アプリにおけるスクロール フォーカス移動に伴って 自動で必要な量だけ スクロール (タッチ操作が可能な端末を使えば ) スワイプ操作でのスクロール も可能 “フォーカス移動
” と “スワイプ操作 ” で 異なるスクロールロジックが実行される スクロールを無効化したつもりが “スワイプ操作” のスクロールだけ無効化されていた 😔こんなこともしばしば、、、
スクロール可能なコンテナ化 • Modifier.verticalScroll / Modifier.horizontalScroll でスクロール可能になる Mobile & TV 向けのロジックを両方含む
• 📱スワイプ操作によるスクロールの有効化 • 📺フォーカス移動に伴うスクロールの有効化 focusGroup も自動で適用される • ScrollableNode は FocusGroupNode と同様に FocusTargetModifierNode に委譲
• 親の30%地点にフォーカス中のアイテムの上端 (左端) を揃える フォーカス移動に伴うスクロールの移動量 30% 70%
デフォルトのスクロールにおける困りごと ボタンがフォーカスされたら タイトルまで見せたい 30%地点までスクロールされて タイトルが見切れる フォーカス中の Composable にのみ基づいたスクロールでは不十分 30%
BringIntoViewSpec --- フォーカスに伴うスクロール量の制御 • スクロールアニメーションとスクロール量を制御 calculateScrollDistance で 0 を返却するとスクロールが止まる (LazyColumn
の userScrollEnabled では止まらない)
BringIntoViewSpec --- フォーカスに伴うスクロール量の制御 • スクロールアニメーションとスクロール量を制御 マイグレーションガイド で 紹介されている制御ロジック 親と子の位置関係からスクロール量を算出
スクロールの基準位置変更 65% parentFraction, childFraction で基準位置を指定して CompositionLocal で提供 見切れなくなったが スクロールの挙動が全体的に変わった
BringIntoViewModifierNode --- 子要素のスクロールロジックを変更 スクロールの基準位置を “子ノード” → “親ノード” に変更 親ノード 全体が見えるようにスクロール
子ノード の座標情報は全て無視 全体のスクロールの挙動は変えずに 見切れ問題だけ解決 🎉
その他のスクロールアニメーション • モバイルアプリ開発でよく見る API も利用可能 NestedScroll も適用可能 (スクロールを完全に停止させてます ) LazyListState.animateScrollToItem
によるスクロール (※フォーカス位置は移動しない )
BringIntoViewSpec --- スクロールアニメーションの変更 • BringIntoViewSpec の animationSpec をオーバーライド ゆったり ⚠
compose 1.8.x では無視される テキパキ
スクロールの制御まとめ スクロール量とアニメーションを変更 ・CompositionLocal で配布 ・公式で紹介されている位置ベースのロジックが便利 ・アニメーションは compose 1.8.x では無視される ・リスト全体のスクロールに影響あり
BringIntoViewSpec 子のフォーカスに伴うスクロールロジックを上書き ・子からのスクロール要求を受け取って挙動を変更可能 ・親全体が見えるように変更すると見切れない ・特定の item にだけ適用可能 BringIntoViewModifierNode 65% 親全体を 見せる 子の座標は 無視
目次 1. Compose for TV とは? 2. TV アプリ向けの style
設定 3. フォーカス移動の制御 a. 自動制御 b. 手動制御 4. スクロールの制御 5. 実際に導入してみた感想
導入して得られた開発効率の向上 RowsSupportFragment DetailsSupportFragment PresenterSelector ListRow VerticalGridView WindowAlignment SingleRow etc… Leanback
の学習コストが不要に 使い方が分からず Fragment in Fragment で無理やり 表示させた過去も...😢 命令的な状態更新に起因するバグ根絶 ViewHolder が再利用された際に 別の item 向けに設定していた状態が 意図せず使いまわされる
導入して感じた注意点 • UI とフォーカス制御の分離が難しい • FocusRequester ベースの処理が読みづらい ◦ FocusRequester の
attach 先を発見する労力が必要 • attach する前に利用するとすぐクラッシュする (していた) ◦ Lazy Layout で陥りやすい 🕹 FocusRequester の取り扱いの難しさ • Experimental な機能が多く、挙動がすぐ変わる ◦ スクロールアニメーションが突如無視される ◦ Exception を throw していた処理が println になる • stable な機能もバグが放置されていたりする ◦ e.g.) LazyVerticalGrid でフォーカスが飛ぶ (link) 🐝 技術的な不安定性 扱いづらいところはあるものの 開発スピードの向上, バグ混入リスクの低減, 容易な機能拡張 など Compose for TV の導入で開発効率は向上した と感じてます
まとめ • TV アプリはフォーカス操作の快適さがユーザー体験に大きく影響 ◦ Leanback ライブラリはユーザー体験が向上するためのロジックが自動で適用される ◦ Compose for
TV では自前で実装していく必要がある • フォーカス制御を行う API はいくつかあるのでトレードオフを考慮して選択 ◦ 自動制御:focusGroup, focusRestorer ◦ 手動制御:focusProperties, focusRequester • 「フォーカス移動」に伴う自動スクロールの挙動は変更可能 ◦ CompositionLocal による BringIntoViewSpec の提供で基準位置を変更 ◦ BringIntoViewModifierNode を使ってロジックを上書き Compose for TV はいいぞ!