Amazon Fire TV対応アプリを作成する場合は、リモコンによるアプリUIのナビゲーションと操作を実装することをお勧めします。これを行うにはさまざまな方法がありますが、重要なポイントは、アプリ内の移動や操作の制御をFire TVリモコンのD-Padやボタンにマッピングすることです。 アプリのナビゲーションと入力の基本事項については、UXのベストプラクティスに関するFire TVドキュメントを参照してください。
Fire TVの自動車での利用が広がる中、D-Padナビゲーションに加えて、Fire TV対応アプリにタッチ操作を追加することが重要になってきました。こうしたデバイスでは、ユーザーはリモコンを使用できるだけでなく、タッチ操作もできるようになります。
このチュートリアルでは、D-Pad向けに設計されたAndroidベースのFire TV対応アプリを変更してタッチ操作を追加する方法と、優れたタッチベースのUXを提供する方法について説明します。
このチュートリアルでは、5つの主な手順について説明します。
サンプルアプリ - GitHubリポジトリ:
このチュートリアルの手順に沿って読み進めるにあたり、GitHubからFire TV Sample App Android - Touch and D-Pad(https://github.com/amzn/firetv-sample-touch-app)をダウンロードまたはクローンすることもできます。
このサンプルアプリには、以下に示すすべてのコードが統合されており、実際の動作をすばやく確認できるので、本チュートリアルの補足資料として使用できます。また、アプリの編集や作成の際に、参照資料としても利用できます。
手順1: D-PadによるUI方向ナビゲーションとフォーカスについて理解する
AndroidベースのFire TV対応アプリは、リモコンによるナビゲーションについてプラットフォームレベルの明確なパターンに従う必要があります。Fire OSはAndroidをベースに構築されているため、標準のAndroidアプリと同じレイアウトやデザインパターンに従います。
アプリのUIナビゲーションをリモコンのD-Padに自動的にマッピングし、各種Androidビューのナビゲーション順序を指定するには、Androidの方向ナビゲーションを使用する必要があります(Androidのドキュメントを参照)。これがベストプラクティスですが、Androidアプリがこのパターンに従っていない場合は、タッチ動作とD-Pad動作の接続に影響するため以下の変更を適用する必要があります。
方向ナビゲーションでは、「フォーカス可能な」ビューごとに次または前に選択されるべきビューを指定する必要があります。これにより、ユーザーがリモコンのD-Padナビゲーションボタン(上、下、左、右)を押したときにフォーカスされる次のビューをシステムが自動的にマッピングできます。
これを実行するには、レイアウトのXMLファイルに次のタグを追加します。
android:nextFocusDown, android:nextFocusRight, android:nextFocusLeft, android:nextFocusUp
これに続けて、順序に基づいてアプリが次に選択するビューのIDを指定します。
例:
<TextView android:id="@+id/Item1"
android:nextFocusRight="@+id/Item2"/>
手順2: TV用アプリのレイアウトに方向ナビゲーションを適用する
Fire TV対応アプリにタッチ操作を適用する前に、D-Padとタッチナビゲーションの間で方向ナビゲーションの一貫したエクスペリエンスを作り出す必要があります。
これは、基本的なアプリインターフェイスではかなりシンプルですが、メディアやエンターテインメントTVアプリのインターフェイスでは非常に複雑になることがあります。多くの場合、動的コンテンツを表示する必要があり、したがって、ほとんどのビューが実行時に生成されるためです。
これを実現するため、多くの開発者は動的コンテンツを簡単に保持できるAndroidビューを使います。この良い例がRecyclerViewです。RecyclerViewは、動的コンテンツをアダプターから解析できるコンポーネントです。RecyclerViewは効率が良く、標準的なAndroidのパターンであるViewHolderを実装します。
ただし、RecyclerViewのコンテンツは動的なため、RecyclerView内で生成されたビュー間のナビゲーションが適切かどうかを確認する必要があります。
これを示すために、TVインターフェイスの標準的な実装をシミュレートするシンプルなアプリを作成しました。このアプリには、主に2つのUIコンポーネントがあります。
左側には黒でmenuLayoutが、右側には灰色でrowsLayoutが表示されています。
アプリによってはビューの入れ子がもっと複雑になる場合もありますが、このチュートリアルでは極度に単純化し、メディアやTV用アプリの動的UIの基本的構造を説明します。
次に、このレイアウトで方向ナビゲーションがどのように動作するかを定義します。
まずは、カテゴリーメニューからコンテンツ行に移動できるようにしなければなりません。これには、LinearLayoutでnextFocusRightを最初の行のRecyclerViewに設定します。
<LinearLayout
android:id="@+id/menuLayout"
[...]
android:nextFocusRight="@id/rowRecyclerView1">
これにより、ユーザーが右ボタンをクリックすると、フォーカスが右側の最初のRecyclerViewに移動します。
次に、RecyclerViewのアイテム間のナビゲーションの仕様を設定します。RecyclerViewのビューは実行時に動的に作成されるため、個々のビューごとに手動でナビゲーションの方向を設定するのは現実的ではありません。また、XMLレイアウトで実現できるものでもありません。その代わり、RecyclerViewでdescendantFocusabilityというタグを使用する必要があります。
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recyclerViewMenu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants" />
descendantFocusabilityをafterDescendantsに設定することで、いったんビューが動的に生成されたら、RecyclerView自体がRecyclerView内のアイテムにフォーカスを提供するようになります(この場合、RecyclerViewメニューで定義されているカテゴリーにフォーカスが置かれます)。
注:すべてのRecyclerViewに対してこの設定を適用することが重要です。これにより、アイテム間の関係が自動的に定義されるからです。Androidはフレームワークレベルの機能として各アイテム間の関係を自動的に処理するため、手動で定義する必要はありません。
この設定は右側のレイアウト内すべてのRecyclerViewにも適用する必要があります。また、各RecyclerViewの間の方向ナビゲーションも定義します(簡略化のため、この例では4個の専用RecyclerViewを利用して4行の構成を定義します)。
RecyclerViewsは次のようになります。
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView1"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:nextFocusDown="@id/rowRecyclerView2" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:nextFocusDown="@id/recyclerView3" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView3"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"
android:nextFocusDown="@id/rowRecyclerView4" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/rowRecyclerView4"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:descendantFocusability="afterDescendants"/>
最後のRecyclerViewのrowRecyclerView4には、nextFocusDownのターゲットがないことに注意してください。これは、次に移動するRecyclerViewがないためです。
この手順を完了すると、D-Padを使用して完全にナビゲーション可能なUIが作成されます。これで、RecyclerViewのコンテンツを変更することでインターフェイスのタッチ操作を可能にする適切な方法について説明する準備ができました。
ビューが動的に生成される場合でも、リモコンで完全にUIをナビゲーションできることがわかります。
手順3: クリックにタッチ操作を追加する: OnClickListener
次の手順では、ユーザーがアイテムをクリックした際のタッチ操作を追加します。Androidはタッチ操作を前提に開発されているので、アプリUIへのクリックアクション追加には、Androidの標準コンポーネントを使用できます。これにより、D-Padの操作とタッチ操作の両方に対応できます。
ビューにクリックアクションやタッチアクションを実装する最も簡単な方法は、Android標準のOnClickListenerを使用することです。OnClickListenerを使用すると、タッチクリックとD-Padボタンクリックの両方をビューで実行できるようになります。onClickListenerはonClick()というメソッドをトリガーするので、そこで任意の操作を実行できます。
注:使用しているUIまたはD-PadベースのUIにおいてほかの方法でクリックアクションを実装した場合、カスタム実装に加えてOnClickListenerを追加することが必要な場合があります。これは、D-Padによるクリックとタッチクリックの両方で目的の操作が実行されるようにするためです。
D-Padスタイルのナビゲーションではビューのサイズは重要ではありませんが、タッチ操作では非常に重要です。このため、優れたユーザーエクスペリエンスを提供するには、UIでより大きなビューと領域を使用することをお勧めします。
ここで見てきたシンプルなアプリでは、OnClickListenerをレイアウト内のビューではなくレイアウト自体に適用します。 これを実現するため、内部ビューをレイアウト領域全体に拡大してOnClickListenerをTextViewやImageViewなどの個々のビューに適用することも可能です。ただし、レイアウト全体に適用すると、UIのルックアンドフィールをまったく変更しなくても目的を達成できるシンプルな解決策になります。
ビューは動的で、RecyclerViewによって作成されるため、各RecyclerViewで作成される各要素に対してクリックリスナーをそれぞれ適用する必要があります。これを行うには、RecyclerViewアダプターのコードを変更し、RecyclerViewの各アイテムのレイアウトの参照を取得して、アダプターのonBindViewHolder()メソッドでonClickListenerを次のように適用します。
public class MenuItemsAdapter extends RecyclerView.Adapter<MenuItemsAdapter.ViewHolder> {
private String[] localDataSet;
/**
*使用しているビューの種類への参照を指定します
* (custom ViewHolder).
*/
public class ViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
private final ConstraintLayout menuConstraintLayout;
public ViewHolder(View view) {
super(view);
//ViewHolderのビューのクリックリスナーを定義します
textView = (TextView) view.findViewById(R.id.textView);
menuConstraintLayout = view.findViewById(R.id.menuconstraintLayout);
}
public TextView getTextView() {
return textView;
}
public ConstraintLayout getMenuConstraintLayout() {
return menuConstraintLayout;
}
}
/**
*Adapterのデータセットを初期化します。
*
*@param dataSet String[]には、RecyclerViewで使用されるビューに設定するための
*データが含まれています。
*/
public MenuItemsAdapter(String[] dataSet) {
localDataSet = dataSet;
}
//新しいビューを作成します(レイアウトマネージャーによる呼び出し)
@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
//リストアイテムのUIを定義する新しいビューを作成します
View view = LayoutInflater.from(viewGroup.getContext())
.inflate(R.layout.menulayout, viewGroup, false);
return new ViewHolder(view);
}
//ビューのコンテンツを置き換えます(レイアウトマネージャーによる呼び出し)
@Override
public void onBindViewHolder(ViewHolder viewHolder, final int position) {
//データセットからこの位置の要素を取得し、
//その要素でビューのコンテンツを置き換えます
viewHolder.getTextView().setText(localDataSet[position]);
viewHolder.getMenuConstraintLayout().setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//このサンプルアプリでは、クリックの記録のみを行っていますが、
//ここで新しいアクティビティを開いたり、
//特定のカテゴリーを選択したりすることもできます
Log.e("Click ", "Clicked "+localDataSet[position]);
}
});
}
//データセットのサイズを返します(レイアウトマネージャーによる呼び出し)
@Override
public int getItemCount() {
return localDataSet.length;
}
}
アイテムがフォーカスまたはクリックされていることを示すには、特定のビューが取る可能性のあるさまざまな状態を表す背景とドローワブルを使用する必要があります。
これは、フォーカスされた状態や押された(クリックされた)状態など、複数の状態を含めることができるセレクタードローワブルの使用により簡単に実現できます。
Menuselectordrawable.xml(メニューレイアウトの背景として使用)
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:state_pressed="true"
android:drawable="@android:color/holo_blue_bright" /> <!-- pressed -->
<item android:state_focused="true"
android:drawable="@android:color/holo_orange_light" /> <!-- focused -->
<item android:state_hovered="true"
android:drawable="@android:color/holo_green_light" /> <!-- hovered -->
<item android:drawable="@android:color/background_dark" /> <!-- default -->
</selector>
menulayout.xmlでは次のようにします。
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/menuconstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/menuselectordrawable"
android:focusable="true"
android:focusableInTouchMode="true">
ここまでで、すべてのUIビューがクリック可能になり、クリックしたときは青、フォーカスしたときはオレンジという、異なる背景色が表示されるようになりました。
onClickListenerが正しくトリガーされていることをリモコンで確認できます。
手順4: D-Padとタッチ操作間のビューフォーカスを管理する
次の手順では、D-Padとタッチの操作に一貫したエクスペリエンスを構築します。つまり、リモコンを使用する場合にもタッチ操作を使用する場合にも、方向ナビゲーションが一貫性のある動作をするようにします。
前述のとおり、Androidはタッチスクリーンとタッチ操作を前提に開発されました。 このため、アプリのUIとビューを管理する基盤レイヤーは、ほとんどが既にタッチ対応になっています。
Androidでは、ほとんどのビューがデフォルトで表示され、フォーカス可能です。実際には、ビューはfocusableというパラメーターを継承します。このパラメーターは、デフォルトで「auto」に設定されており、このビューがフォーカス可能かどうかはプラットフォーム自体が判断します。Button、TextView、EditTextなどのビューはメインのUIコンポーネントであり、デフォルトでフォーカス可能です。一方、レイアウトやレイアウトインフレーターは、UI構造を定義するためだけに使用されるので通常デフォルトではフォーカス可能ではありません。
アプリを完全にタッチ対応にするには、ユーザーがタッチを使用する場合も含めて、アプリで最も重要なビューがフォーカス可能となるようにする必要があります。
このため、ビューの2つのパラメーター(focusableとfocusableInTouchMode)を編集します。
サンプルアプリでは、「カテゴリー」RecyclerViewと「行」RecyclerView内で個々のアイテムを表示するのに使用される、2つの新しいレイアウトを作成しました。
次に、以下の点に留意する必要があります。
これを行うには、focusableとfocusableInTouchModeの両方をtrueに設定します。
menuLayout.xml(左側のカテゴリーの個々のコンテンツを定義、含まれるのは1つのTextViewのみ)
<androidx.constraintlayout.widget.ConstraintLayout
[...]
android:id="@+id/menuconstraintLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@drawable/menuselectordrawable"
android:focusable="true"
android:focusableInTouchMode="true">
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
[...] />
cardLayout.xml(右側の映画の行の個々のコンテンツを定義、各カードにImageViewとTextViewが含まれる)
<?xml version="1.0" encoding="utf-8"?>
<androidx.constraintlayout.widget.ConstraintLayout
[...]
android:id="@+id/cardconstraintLayout"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/selectordrawable"
android:focusable="true"
android:focusableInTouchMode="true"
>
<ImageView
android:id="@+id/imageView"
android:layout_width="150dp"
android:layout_height="100dp"
[...] />
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
[...] />
</androidx.constraintlayout.widget.ConstraintLayout>
これにより、ユーザーがUIにタッチした場合にも、D-Padコントローラーで移動した場合にも、正しいUI要素にフォーカスが置かれるようになります。以下のクリップでデモンストレーションをご覧ください。
このクリップでは、タッチ操作でビューが正しくクリックされ、フォーカスされる様子が確認できます。
手順5: そのほかのベストプラクティスを確認しFire TVでタッチ機能をテストする
上記の手順を完了すると、アプリUIの最も重要なコンポーネントにタッチ機能が正常に追加されます。
タッチナビゲーションとD-Padナビゲーションの両方で優れたユーザーエクスペリエンスを確実に提供するには、ほかにも簡単な手順がいくつかあります。
タッチスクリーンのないFire TVデバイスでタッチ機能をテストする方法
最も簡単な方法は、ワイヤレスマウスをFire TVに接続することです。Androidでマウスを使用すると、タッチ操作のシミュレーションになります。次の手順に従います。
タッチおよびD-Pad対応Fire TVサンプルアプリのダウンロードとテスト
Fire TV Sample App Android - Touch and D-Padをhttps://github.com/amzn/firetv-sample-touch-appからダウンロードしてください。 このアプリには上記のすべてのコードが含まれています。AndroidベースのFire TV対応アプリをタッチ操作向けに最適化する第一歩としてお役立てください。
まとめ
このチュートリアルでは、Fire TV対応アプリのタッチ機能を有効にする方法について実用的な概要を説明しました。
詳細については、以下のドキュメントを参照してください。