はじめに

本記事はAndroidデバイス開発経験があり、AndroidTVデバイス開発を視野に入れている読者をメインターゲットとしています。そのため多くはAndroidデバイス開発との相違点に注目し、1つの解決策を展開します。

端末について

事前にどの端末をサポートするか絞ったほうが良いです。また、端末によって動き方が変わるため、可能であれば、サポートする端末の実機を入手することをお勧めします。筆者が経験した中だと下記のような特徴があります。

端末特徴
popin aladdinの初代機・MotionLayoutが動かない
・単位系dpの変換ロジックが他端末と異なる
Chromecast with GoogleTVandroidx.media3.ui.PlayerViewで同時に2個再生可能
Regza, BraviaなどChromecast with GoogleTVとは異なり、同時に2個以上の再生は不可かつ他FragmentでExoPlayerをインスタンスを持ち、それがmediaItemを持つと他Fragmentなどでは再生不可

他にも端末依存による不具合はちらほらあったりします。これらの厄介な点はクラッシュという形で表に出ずに、シナリオテストをする際に違和感に気づくことが多いため、なかなか油断できない落とし穴だったりします。このような不具合を考慮しつつ実装を進めるにはウォータフォール型のように事を進めるのではなくアジャイル型のようにある程度のサイクルで進めつつ、いきなり全てを作り込まずに一度ラフを作り、端末依存がないか確認しながら実装を進めることをお勧めします。

フォーカス設定

AndroidTVデバイスのUI体験はAndroidデバイスのように指でスライドするなどのUI体験を得ることが出来ず、専用のリモコンデバイスを通じてUI体験をすることになります。そのため、現在フォーカスを得ているViewからの上下左右へのフォーカス移動先の定義が必要不可欠になります。

フォーカスの移動先定義は下記のようにルールを決めてあげると上手くいくケースが多いです。

  • レンダリング画面が静的であるならレイアウトのnextFocus系で調整する
  • レンダリング画面が動的であるならプログラム処理で調整する

静的な画面では次のような実装が上手くいきます。

<View
    android:nextFocusDown="@id/nextFocusDownView" ... />

このようにレイアウトでフォーカスを定義します。一方で動的な画面では上記の実装では上手くいかないケースが多いです。動的な画面では下記の3つの方法を組み合わせて調整するのが楽なうえで上手くいくことが多いです。

  • androidx.leanback.widget.BrowseFrameLayout#OnFocusSearchListener
  • View#setOnKeyListener
  • レイアウトのnextFocus系

この3つの手段の優先度はプロジェクトで採用するアーキテクトに適したものを選ぶ事を推奨しますが、筆者の経験から基本的にはOnFocusSearchListenerを使用し、部分的に静的なパーツはnextFocus系で実装の手間を省略し、OnFocusSearchListenerで上手くいかないものはsetOnKeyListenerで調整してやる方針をお勧めします。

WebView

AndroidTVデバイスはその特徴からAndroidデバイスのように指でスライドしてWebViewのUI体験を得ることが出来ません。従って、フォーカスの観点から事を考えないといけません。その時に障害となるのがWebView内のLinkなどのフォーカスを取るものです。実装時にスクロールアクションがLink系にフォーカスが吸われ、大きくスクロールした時に解決するために下記コードの実装が必要になります。

WebSettings#setNeedInitialFocus(false)

また、レンダーされる側が1920×1080の解像度で作成されることが多いですが、AndroidTVデバイス用に960×540の解像度で作成することをお勧めします。FHDで作ると出力先で文字サイズが2倍になるなどの現象が起きます。一度実機で確認し、CSSなどを調整する事を推奨します。

RecyclerView

RecyclerViewはAndroidTV開発になるとかなり難易度の高いものになります。その理由の一部が下記になります。

  • アイテムからのフォーカス先設定が思った動きをしてくれない
  • 随時読み込み型で実装したとき、読み込み中にフォーカスがどこかに飛んでしまった
  • アイテムに変更があったので変更をしたときフォーカスがどこかに飛んでしまった
  • 一番下までスクロールしたのに一番最後のアイテムが半分見えない

実際にはこの倍以上の問題に直面すると思います。これらの多くはユーザー体験がリモコンを通じているところから問題が生じており、Androidのように指を使うと360自由な方向緩急をほぼ離散的に定義可能になるが、リモコンはそう出来ないものが多く、RecyclerViewのフォーカス先はインジケーターではなく、表示する1つ1つのアイテムになります。今現在フォーカスしているアイテムが一瞬消えるとフォーカスを失うことになり、フォーカスを失ったときAndroidTV側がその環境において暗黙的な処理により次のフォーカス先が定義され、フォーカスが移動されるため、よくわからないところに急にフォーカスが飛んだりする現象が起きたりします。また、フォーカスの移動がアイテムからアイテムに対して行われることが多く、RecyclerViewそのもののフォーカス定義ではなくアイテムに対してのフォーカス定義になってくるところもより難しくしているところです。

ストレスなく実装するにはアイテムの更新タイミングや表示タイミングをしっかり理解し、その時のフォーカス状態がどうなっているかを正しく整理することです。アイテムの上にフォーカスが乗っている状態でAdapterのnotifyDataSetChangedを雑にやったらフォーカスは飛んでいくので丁寧にnotifyItemChangedを使ったり、RecyclerViewではなくアイテムに対して次のフォーカス先の定義をしてやるなど、丁寧に状況を整理して実装を進めてみてください。



ギャップロを運営しているアップフロンティア株式会社では、一緒に働いてくれる仲間を随時、募集しています。 興味がある!一緒に働いてみたい!という方は下記よりご応募お待ちしております。
採用情報をみる