フォーカス管理の概要のビデオ
次のビデオでは、Vega Fire TVデバイスでのフォーカス管理について紹介しています。Vegaでの基本的なフォーカス処理
フォーカスを示す方法
フォーカスインジケーターは、画面上のどの要素に現在キーボードフォーカスがあるかを示す重要な視覚的表示要素です。これらのインジケーターは通常、背景色や不透明度の調整などのスタイル変更によって実装されますが、アクセシビリティ上の理由から物理的特性の変更を含めることが重要です。 境界線の追加、要素のサイズ変更、UIコンポーネントの追加などの物理的な変更を加えることにより、さまざまな条件下でフォーカス状態の表示が維持されるようになります。物理的な変化は、単なる視覚的な変化(色や不透明度)とは異なり、ハイコントラストモードなどのアクセシビリティ設定を有効にした状態でも検出できます。フォーカス状態での表示がすぐに使用できるコンポーネント
Vegaでは、React Nativeと同様に、ほとんどのコンポーネントについて、プラットフォームレベルでのフォーカス表示が提供されていません。フォーカスすると外観が変わるのは、との2つのコンポーネントだけです。不透明度が0.2に変更されますが、これはactiveOpacityプロパティを使用して調整できます。
Vegaでのフォーカスへの対応
onFocusプロパティとonBlurプロパティを使用すると、フォーカスの状態に基づいてコンポーネントの外観を動的に変更できます。これらのプロパティは、それぞれコンポーネントがフォーカスを取得したとき、または失ったときにトリガーされるコールバック関数を受け入れます。
コンポーネントにフォーカスが移動したときに青い境界線を追加する例を以下に示します。
Vegaにおけるコンポーネントのフォーカスの可否
TVアプリで対話型コンポーネントを使用する場合、さまざまなコンポーネントでのフォーカスの動作を理解することが不可欠です。一部のコンポーネントはデフォルトでフォーカスされますが、その他のコンポーネントはfocusableプロパティを使用してフォーカスの可否を指定します。FlatListやTVFocusGuideViewなどのコンテナを使用する場合、これらのコンテナとその子コンポーネントのフォーカス可否設定に応じて、フォーカスの動作が変わります。
Component Focusabilityデモ
Component Focusabilityデモでは、デフォルトのフォーカス処理を確認できるほか、フォーカス可能なプロパティとpointerEventsがさまざまな一般的なUIコンポーネントに与える影響を確認できます。
Container Focusabilityデモ
Container Focusabilityデモでは、複数のフォーカス可能なプロパティ構成に基づいて、コンテナとその子コンポーネント間で発生する可能性のあるフォーカスの相互作用を確認できます。デモ画面のキャプチャ
以下の画像は、Vega Container Focusabilityデモの画面キャプチャです。- コンポーネント/コンテナがフォーカスを受け取ると、オレンジ色の境界線でレンダリングされます。
Vegaでのフォーカスナビゲーションの動作
このセクションでは、Vegaのフォーカスナビゲーションの動作について、例を挙げながらさまざまなシナリオと予想される動作を紹介します。Vegaでは、Fire OS上のReact Nativeと一貫したエクスペリエンスを提供することを目指しています。デカルトフォーカス管理
デカルトフォーカス管理(または暗黙的フォーカス管理)とは、D-Padキーを押した方向に「最も近い」アイテムにフォーカスを移動する戦略です。シミュレーターでは、矢印キーとEnterキーを使用します。デフォルトの動作をオーバーライドする個別のロジックがアプリに実装されていない限り、Vegaはデカルトフォーカス管理を適用します(動作をオーバーライドするさまざまな方法については、Vegaでのフォーカス動作のカスタマイズに関するセクションを参照してください)。 水平軸と垂直軸に沿って均一な形状のアイテムが配置されているレイアウトでは、フォーカスナビゲーションを直感的に行えます。- 下ボタンを押すと、すぐ下のアイテムにフォーカスが移動します。
- 右ボタンを押すと、隣接するアイテムにフォーカスが水平に移動します。
形状の異なる複数のアイテムが配置されているレイアウトでは、ユーザーがコントロールのD-Padを使用して移動方向を選択すると、その方向とほぼ同じ方向にあるすべての要素がフォーカスの移動先候補になります。その中で重み付け距離が最短の候補(入力方向に沿った距離ほど重み付けが小さくなります)が、次のフォーカス候補と見なされます。
コンポーネントが重なっている場合、フォーカス候補は、ビュー階層内のZオーダーとは無関係に選択されます。たとえば、一部が重なって隠れているコンポーネントがあるとします。上ボタンを押したときに、そのコンポーネントまでの重み付け距離がほかよりも短いと計算されると、離れた位置にある隠れていないコンポーネントではなく、隠れているコンポーネントにフォーカスが設定されます。以下のセクションでは、さまざまなレイアウトでどのようにコンポーネントにフォーカスが移動するか、事例別に詳しく説明します。
さまざまなレイアウトでのデカルトフォーカスナビゲーションアルゴリズムの事例
より複雑なレイアウト、特に形状が不均一なアイテムを扱う場合、フォーカスナビゲーションはコンポーネントのサイズと空間的関係に依存するようになります。Vegaは一貫性を保つためにFire OSと同様のフォーカス検索アルゴリズムを実装していますが、シナリオによっては、アイテムのサイズと場所によってナビゲーション動作が異なる場合があります。 以下に、垂直な経路上に配置されたアイテムにフォーカスが移動しない事例をいくつか紹介します。事例1:下ボタンを押してもフォーカスが真下のアイテムに移動しないケース
このセットアップは4つのアイテムで構成されており、[上アイテム] にフォーカスがあってユーザーが下ボタンを押すユースケースをシミュレートしています。 このセットアップは4つのアイテムで構成されており、[上アイテム] にフォーカスがあってユーザーが下ボタンを押すユースケースをシミュレートしています。
左側のデモの動作:
- [上アイテム] から下ボタンを押した場合:
- フォーカスが [Small Item] に移動します。
- これは、[Small Item] の下端と [Bottom Item] の上端が水平方向で重なり合っていないためです。
- 同様のコンポーネント配置で、代わりに [Bottom Item] にフォーカスが移動します。
- このように動作が異なるのは、[Small Item] の下端が [Bottom Item] の上端と水平方向で重なり合っているためです。
上側のデモの動作:
- [上アイテム] から下ボタンを押した場合:
- フォーカスが [Small Item] に移動します。
- これは、[Small Item] の左端が [Top Item] の右端と垂直方向で重なり合っているためです。
- 同様のコンポーネント配置の場合:代わりに [Bottom Item] にフォーカスが移動します。
- これは、[Small Item] の左端が [Top Item] の右端と垂直方向で重なり合っていないためです。
- コンポーネント間のエッジの重なりも考慮されます。
- 次のフォーカス可能な要素までの距離も考慮されます。
- コンポーネントの配置はナビゲーションパスに大きく影響します。
FocusManager.setNextFocus()メソッド。nextFocusUp/nextFocusDown/nextFocusLeft/nextFocusRightプロパティ- プログラムによって特定の要素にフォーカスを割り当てることができる
FocusManager.focus() - コンテナ内に移動するときにフォーカスを制御する<TVFocusGuideView>の
destinationプロパティ
事例2:縦に並んだ異なる幅のアイテム間を移動するときに、全幅の要素にフォーカスが移動しないケース
以下のコンポーネントレイアウトでは、アイテム1にフォーカスがあるときにユーザーが下ボタンを押すと、コンポーネントのレイアウトに応じて、フォーカスが [アイテム1] の下の [全幅アイテム] または [全幅アイテム] に移ります。
シナリオ1:幅を狭くすると全幅アイテムへのフォーカスはスキップされない
幅を1208から1070に縮小し、高さはそのままにした場合、[アイテム1] から下ボタンを押すと、フォーカスは [全幅アイテム] に移動します。
幅を1208から1270に更新すると、アイテム1から下に移動したときに全幅アイテム2がフォーカスを受け取ります。
シナリオ2:高さを増やすと全幅アイテムへのフォーカスはスキップされない
[全幅アイテム] の高さを104から105に拡大し、幅はそのままにした場合、[アイテム1] から下ボタンを押すと、フォーカスは [全幅アイテム] に移動します。
高さを104から105に更新すると、アイテム1から下に移動したときに全幅アイテム2がフォーカスを受け取ります。
デフォルトの動作をオーバーライドする実装
デフォルトのフォーカス動作は、すべてのアプリのニーズに合わない場合があります。アプリでは、FocusManagerターボモジュールのsetNextFocus()を使用して、コンポーネント間のカスタムフォーカスパスを宣言的に定義できます。
- 下ボタンを押すと、強制的に [アイテム1] から [全幅アイテム] にフォーカスが移動します。
- [アイテム3] から [全幅アイテム] への上方向のナビゲーション
- [全幅アイテム] から [アイテム3] への下方向のナビゲーション
Vegaにおけるフォーカス動作
異なる行のアイテム間のフォーカスの処理
ScrollViewやFlatListなどのリストでは、基本的なフォーカス管理機能がサポートされています。インデックスベースのナビゲーションが使用され、フォーカス可能な次のアイテムが画面の外またはScrollViewの境界外にある場合にのみリストがスクロールされます。最後の要素または最初の要素からリストの方向にスクロールすると、フォーカスが次のリストに移動します。たとえば、リストの右端にある最後の要素にフォーカスがある場合、右ボタンを押すとフォーカスが下のリストのタイルに移動します。つまり、フォーカスが要素1.3にある場合、D-padの右ボタンを押すとフォーカスは要素2.3に移動します。
上記の例のデフォルトのフォーカス動作は、ユーザーエクスペリエンスとして望ましくない場合があります。一部のアプリでは、フォーカスを行の末尾で「トラップ」させ、上の例では、フォーカスが要素1.3にある場合、D-Padの右ボタンを押してもフォーカスが移動しないようにする必要があります。これに対し、FlashListとFlatListの両方に推奨される実装があります。
フォーカスの復元
プラットフォームの制限 Vegaプラットフォームにはフォーカス復元機能が組み込まれていません。フォーカスの復元は、特定のReact Navigationスタックナビゲーターから行います。 スタックナビゲーターの動作- @amazon-devices/react-navigation__stack
- ❌ フォーカスの自動復元なし
- @amazon-devices/react-navigation__native-stack
- ❌ フォーカスの自動復元なし
フォーカスを復元するための実装オプション
React Navigationで構築されたマルチスクリーンアプリケーションの場合:- 各画面について、最近フォーカスされた要素への参照を保存します。
useFocusEffect()を使用して、ユーザーがある画面に戻ったことを検出します。useFocusEffect()内で、保存されている参照を使用してFocusManager.setFocus()を呼び出し、正しい要素にフォーカスを復元します。
初期フォーカスの構成
初期フォーカスを管理するには、主に次の2つの方法があります。hasTVPreferredFocusプロパティは、コンポーネントの初期マウント中のフォーカスを設定します。このプロパティは初期のレンダリング段階でのみ機能します。useEffectやuseFocusEffect(React Navigationに含まれる)をFocusManager.setFocus()と組み合わせると、特にアプリがReact Navigationで構築された複数ページのアプリ内でフォーカス状態をアクティブに復元する必要がある場合に、より動的なアプローチが可能になります。このアプローチにより、初期マウント段階以降もフォーカスの動作をより柔軟に細かく制御できます。
フォーカス動作のカスタマイズ
Vegaは、次のフォーカス管理のパラダイムを提供しています。- フォーカスプロパティとメソッド:アプリ内の特定のコンポーネントでデフォルトのフォーカス動作をオーバーライドする必要がある場合、Vegaでは、フォーカスに関連するさまざまなプロパティと
TurboModuleメソッドがサポートされています。これらは、デフォルトの動作が適さない特殊なケースを定義するために使用できます。 - 専用のフォーカスコンポーネント:場合によっては、デフォルトのデカルト動作とは体系的に異なる動作が必要になることがあります(たとえば、よく使用されるものにTVFocusGuideViewがあります)。このセクションには、Vegaでサポートされているカスタムコンポーネント(
TVFocusGuideView)の一覧が含まれています。
フォーカスプロパティとメソッド
フォーカスプロパティは、focusableがtrueのコンポーネントに設定できるプロパティで、それらのコンポーネントからのフォーカスの移動方法を変更するために使用します。以下は、各方向のフォーカスプロパティです。nextFocusUpnextFocusDownnextFocusLeftnextFocusRight
nextFocusUp、nextFocusDown、nextFocusLeft、nextFocusRightの各プロパティを設定します。値を同じコンポーネントのノードハンドルに設定すると、指定した方向のボタンの押下動作が実質的にブロックされます。
- 値をundefinedに設定すると、指定した方向のボタンを押したときのデフォルトのデカルト動作が再び有効になります。
- 命令型APIを使用する場合は、
FocusManager``TurboModuleに、フォーカスプロパティと同じ動作をする関数が用意されています。フォーカスがコンポーネントまたはその子から離れないようにするsetFocusRootAPIもあります。詳細については、Focus Manager (Vega)(英語のみ)を参照してください。
FocusManagerターボモジュール
上記のプロパティに加えて、FocusManagerターボモジュールはフォーカス動作のエッジケースを定義するための命令型APIを提供します。サポートされているメソッドのリストについては、フォーカスマネージャー(Vega)を参照してください。
setNextFocus()—clearNextFocus()—setFocusRoot()focus()—blur()getFocused()
専用のフォーカスコンポーネント
TVFocusGuideView
TVFocusGuideViewは、React NativeのTVFocusGuide APIのVega実装を提供します。詳細については、TVFocusGuideViewを参照してください。このコンポーネントはreact-native-tvosから移植されています。
同期フォーカスイベントのディスパッチ
Vegaには、UIスレッドとJS間でフォーカスイベントを同期的にディスパッチできる新機能が導入されています。UIスレッドは、前のフォーカスイベントまたはフォーカス解除イベントの実行が終了するまで、新しいフォーカス変更を処理せずに待機します。UIスレッドがブロックされている間に受信したほかのフォーカスイベントやフォーカス解除イベントは、キューに入れられます。 同期フォーカスは、キーをすばやく連打したときに発生するフォーカスの問題を解決することを目的としています。- アプリがフォーカスを受け取ったときにアイテムのzIndexを更新すると、zIndexの変更により、アイテムの削除と再挿入が行われます。その結果、アイテムのフォーカスが解除され、フォーカスが失われます。別の状況では、アイテムが<TVFocusGuideView>の内部に配置されている場合、フォーカスが失われる代わりに、
TVFocusGuideによって最初の子にフォーカスが復元されます。
アプリで同期フォーカスを有効にする必要がある場合
次のような場合は、同期フォーカスを使用することを検討してください。- フォーカスが<TVFocusGuideVIew/>内の最初のアイテムにリセットされる問題が発生している。
- 固有のUI要件により、アイテムへのフォーカスの設定または解除に伴うすべての問題を、フォーカスが別のアイテムに移動する前に厳密に適用する必要がある。
- 使用はできるだけ最小限に抑えます。
- 不要な再レンダリングを避けるために、アプリを最適化し、アプリのパフォーマンスに関するベストプラクティスに従う必要があります。
onFocus()とonBlur()はできるだけ軽量に保ち、UIスレッドが必要以上に長くブロックされないようにします。
onFocus()イベントやonBlur()イベントが戻るまで、UIスレッドがブロックされてアイドル状態になることを意味します。その結果、キーをすばやく連打した場合のパフォーマンスとレンダリングにさまざまな影響が生じます。次のセクションでは、いくつかの既知の影響と解決策について詳しく説明します。
既知の影響と解決策
キーをすばやく連打すると長いアニメーションがスキップされる
アイテムへのフォーカスの設定時または解除時にアプリでアニメーションをスケジュールしている場合、アニメーションの再生時間が各キー押下の間隔よりも長いと、アニメーションがスキップされることがあります。アプリでこの動作が発生する場合は、応答性を高めるためにアニメーションの再生時間を短くしてください。不要な再レンダリングによりアプリで遅延が発生する
イベントを同期的にディスパッチするときは、アプリを最適化して不要な再レンダリングを避ける必要があります。最適化の詳細については、アプリのパフォーマンスに関するベストプラクティスを参照してください。 さらに、UIスレッドが必要以上に長くブロックされるのを防ぐために、アプリでは、onFocus()またはonBlur()コールバックに長時間実行されるロジックを含めないようにする必要があります。
フォーカスイベントまたはフォーカス解除イベントのディスパッチ時に進行中のアニメーションでフレーム落ちが発生する
UIスレッドはJavaScriptのonFocus()またはonBlur()コールバックが戻るまでブロックされます。アプリでアニメーションが繰り返し再生されている場合や、イベントのディスパッチと同時に実行されるアニメーションがある場合は、フレーム落ちが発生する可能性があります。
同期フォーカスイベントを有効にする方法
Vega向けReact Nativeには、enableSynchronousFocusEventsVegaプロパティが実装されています。アプリでこのプロパティを使用することで、コンポーネントごとにフォーカスイベントのディスパッチを同期するか非同期にするかを選択できます。
プロパティの使用方法:
-
enableSynchronousFocusEventsVegaをtrueに設定した場合、アプリのonFocus()とonBlur()の実装内では、フォーカスをプログラムによって変更するメソッドを呼び出さないようにする必要があります。このようなメソッドには以下が含まれます。requestTVFocus()FocusManager.Focus()と.Blur()ref.focus()とref.blur()
-
初回のレンダリング後に
enableSynchronousFocusEventsVegaの値を変更しないでください。 -
enableSynchronousFocusEventsVegaの値が異なるコンポーネントを共通の親コンテナに混在させることは推奨されません。 たとえば、コンポーネントのenableSynchronousFocusEventsVegaの使用方法が混在する次のようなパターンは避けてください。
react-native、react-native-tvos、react-native-kepler
Vegaプラットフォームは、JavaScriptパッケージであるreact-native-keplerを使用してReact Nativeをサポートしています。これは、react-native-macosなど、ツリー外のほかのプラットフォームと同様のアプローチです。 一般的には、react-native名前空間からAPIとコンポーネントをインポートすることが推奨されます。これにより、アプリ開発者はReact Nativeがサポートするすべてのプラットフォーム間での移植が可能になります。React Nativeのドキュメントで詳しく説明されているように、react-nativeでは、Metroの設定を使用して同じ名前空間を共有することができます。 プラットフォーム固有の機能を扱う場合、このアプローチはより複雑になります。たとえば、FocusManagerのようなプラットフォーム固有のAPIは、react-native-kepler名前空間でしか使用できず、react-nativeからインポートするとエラーが発生します。
Vegaプラットフォームは、マルチモーダルプラットフォームとTVプラットフォームの両方をサポートしているため、さらに複雑さが増します。React Nativeでは、分岐型のアプローチを採用してreact-native-tvosを作成し、TVプラットフォームのサポートやTVFocusGuideViewなどの機能を追加しています。これらはreact-native-tvos名前空間から利用できます。
対照的に、VegaではTV固有のコンポーネント(TVFocusGuideViewなど)をreact-native-kepler名前空間に移植しました。そのため、この単一の名前空間で、マルチモーダルプラットフォームとTVプラットフォームの両方をサポートできます。開発者は@amazon-devices/react-native-kepler名前空間からTV固有のコンポーネントやAPIをインポートできます。





