Fire TV対応アプリにタッチ機能を追加する方法
Amazon Fire TV対応アプリに、リモコンやD-Padによる操作に加えて、タッチ操作もセットアップできるようになりました。車載用Fire TVの登場に伴い、この機能はますます重要になってきています。このチュートリアルでは、リモコンおよびD-Pad用に設計された既存のFire TV対応アプリを修正してタッチ機能を追加する方法と、タッチ操作の優れたUXを提供する方法について説明します。詳細については、Automotive UXガイドラインを参照してください。
- 前提条件: リモコンのD-PadへのUIナビゲーションのマッピング(方向ナビゲーションを使用)
- 手順1: Fire TVの動的UIに方向ナビゲーションを適用する
- 手順2: OnClickListenerを使用してタッチ機能を追加する
- 手順3: D-Pad操作とタッチ操作間のビューフォーカスを管理する
- 手順4: Fire TVでタッチ機能をテストする
- ベストプラクティス
- サンプルアプリのダウンロード
- リソース
Fire TV対応サンプルアプリ(Fire TV Sample App Android - Touch and D-Pad)をダウンロードすることをお勧めします。このサンプルアプリには以下に示すすべてのコードが統合されているため、実際の動作をすばやく確認することができます。このサンプルアプリは、アプリを作成・編集する際の土台として活用してください。
前提条件: リモコンの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"/>
上記のコードでは、ユーザーがD-Padの「右」ボタンを押した場合に、TextView(Item1
)が次のビュー(Item2
)に移動できるようになります。すべてのナビゲーション方向について、この形式に従ってください。
手順1: Fire TVの動的UIに方向ナビゲーションを適用する
Fire TV対応アプリにタッチ機能を適用する前に、D-Padナビゲーションとタッチナビゲーションとの間に一貫性のある方向ナビゲーションを作成する必要があります。
このナビゲーションは、基本的なアプリインターフェイスでは通常シンプルですが、メディアおよびエンターテインメントTV向けアプリのインターフェイスでは非常に複雑になることがあります。多くの場合、こうしたインターフェイスでは動的なコンテンツを表示するため、ランタイム生成が必要になります。
この理由から、ほとんどの開発者はAndroidビューを使用して動的なコンテンツを保持しています。RecyclerView
はその実例です。RecyclerView
コンポーネントを使用すると、アダプターから動的なコンテンツを解析できます。効率の良いこのRecyclerView
には、標準のAndroidパターンの1つであるViewHolder
が実装されています。
RecyclerView
のコンテンツは動的であるため、各RecyclerView
間のナビゲーションが正しく生成されるようにする必要があります。
前述のサンプルアプリのUIは次のようになっています。ここでは、TVインターフェイスの標準実装をシミュレートします。このサンプルアプリには、主に2つのUIコンポーネントが含まれています。
- 「menuLayout」という名前の
LinearLayout
。「recyclerViewMenu」という名前のRecyclerView
が1つ含まれています。これ自体には、すべてのカテゴリーを示す左側のメニューが含まれています。 - 「rowsLayout」という名前の2つ目の
LinearLayout
。その他のRecyclerView
が複数含まれています。これらには、再生可能なすべての映画とコンテンツが含まれています。
実際のアプリではビューの入れ子がさらに複雑になる可能性がありますが、ここではメディア/TV向けアプリの動的UIの概要を示しています。
重要: スクロール可能なコンポーネント(ScrollView
など)を使用してレイアウトを構築すると、スクロールが有効になります。
レイアウトで方向ナビゲーションの動作を定義していきましょう。まず、カテゴリーメニューからコンテンツ行に移動できるようにします。これを行うには、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
に対して、この動作を同様に適用します。その後、各RecyclerView
間の方向ナビゲーションを定義します。わかりやすくするために、ここでは4つの専用のRecyclerView
を使用して4つの行を定義しています。
RecyclerView
にこの動作を適用することで、アイテム間の関係が定義されます。関係はAndroidによってフレームワークレベルで定義されるため、開発者が手動で定義する必要はありません。RecyclerViewは次のようになります。
<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"/>
rowRecyclerView4にはnextFocusDown
のターゲットがないことに注意してください。これは、このRecyclerView
が一番下にあり、次に移動できるRecyclerView
がないためです。
これで、UIがD-Padによって完全にナビゲート可能になったので、RecyclerView
のコンテンツを変更してインターフェイスをタッチ対応にすることができるようになりました。
手順2: OnClickListenerを使用してタッチ機能を追加する
Androidはタッチ操作を考慮して構築されているため、TV対応Androidアプリにタッチ機能を追加するのは、実際には非常に簡単です。アプリのUIにタッチ機能を追加する場合は、標準のAndroidコンポーネントを使用できます。これにより、D-Pad操作とタッチ操作の両方が可能になります。
ベストプラクティスとして、OnClickListener
を使用して、ビューにクリックアクションまたはタッチアクションを実装することをお勧めします。OnClickListener
は、onClick()
というメソッドをトリガーし、任意の操作を実行できるようにします。
OnClickListener
を追加する必要があります。こうすることで、タッチでもD-Padのクリックでも目的の操作を確実に実行できます。ビューのサイズは、D-Padナビゲーションの場合は重要ではありませんが、タッチ操作の場合は重要になります。タッチ機能を有効にする際は、優れたユーザーエクスペリエンスを提供するために、大きいサイズのビューとUIコンポーネントを使用するようにしてください。
このシンプルなアプリでは、レイアウト内のビューではなく、レイアウト自体にOnClickListener
を適用します。こうすることで、UIのルックアンドフィールを変更する必要がなくなります。
ビューはRecyclerView
によって動的に作成されるので、各RecyclerView
のそれぞれの要素に個別のOnClickListener
を適用する必要があります。それには、RecyclerView
の各アイテムのレイアウトへの参照を取得するようにRecyclerView
アダプターのコードを変更し、アダプターのonBindViewHolder()
メソッドでonClickListener
を適用します。
public class MenuItemsAdapter extends RecyclerView.Adapter < MenuItemsAdapter.ViewHolder > {
private String[] localDataSet;
/**
* 使用しているビューの型への参照を提供します
* (カスタム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;
}
}
/**
* アダプターのデータセットを初期化します。
*
* @param dataSet:RecyclerViewで使用されるビューに設定するデータを含む
* String[]。
*/
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;
}
}
アイテムがフォーカスを受け取った、またはクリックされたことを確認するには、ビューのさまざまな状態を含む背景やドローワブルを使用します。ドローワブルアイテムはselector要素で使用します。これには、フォーカス状態や押下状態(クリック状態)など、複数の状態を含めることができます。
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" /> <!-- 押下状態 -->
<item android:state_focused="true"
android:drawable="@android:color/holo_orange_light" /> <!-- フォーカス状態 -->
<item android:state_hovered="true"
android:drawable="@android:color/holo_green_light" /> <!-- ホバー状態 -->
<item android:drawable="@android:color/background_dark" /> <!-- デフォルト -->
</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ビューがクリック可能になり、クリックされた場合(青)やフォーカスを受け取った場合(オレンジ)などに、異なる背景色が適用されるようになりました。
手順3: D-Pad操作とタッチ操作間のビューフォーカスを管理する
D-Pad操作とタッチ操作との間に一貫性を持たせることが重要です。したがって、リモコンとタッチ機能のどちらを使用している場合でも、方向ナビゲーションが一貫して機能するようにする必要があります。
Androidはタッチ操作を考慮して構築されています。つまり、UIのビューを管理する基盤レイヤーは、ほとんどが既にタッチ対応になっています。
Androidのほとんどのビューは、デフォルトで表示およびフォーカス可能になっています。ビューはfocusableというパラメーターを継承します。これは、デフォルトでは「auto」に設定されています。つまり、ビューがフォーカス可能かどうかはプラットフォームによって決定されます。Button
、TextView
、EditText
などのビューは、主要なUIコンポーネントであるため、デフォルトでフォーカス可能になっています。レイアウトとレイアウトインフレーター(LayoutInflater
)は、ほとんどの場合UI構造を定義するため、デフォルトでフォーカス可能になっていることはまずありません。
アプリを完全にタッチ対応にするには、最も重要なビューがフォーカス可能な状態で、ユーザーがタッチ操作を使用したときにフォーカスを取得できるようにします。これには、各ビューの2つのパラメーター(focusable
とfocusableInTouchMode
)を編集する必要があります。
サンプルアプリでは、2つの新しいレイアウトを作成して、「カテゴリー」のRecyclerView
と「行」のRecyclerView
内の各アイテムを設定しています。そのレイアウトファイルがmenuLayout.xml
とcardLayout.xml
です。
タッチやD-Padでのフォーカスを許可するには、focusable
とfocusableInTouchMode
の両方のXML属性を「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
が1つずつ含まれています。
<?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要素がフォーカスを取得するようになります。実際の動作については、以下のアニメーションを参照してください。
手順4: Fire TVでタッチ機能をテストする
テストに使用できるタッチスクリーンがあれば使用してください。画面の各部分を確認して、タッチ機能が有効であり、D-Pad操作とタッチ操作の切り替えが可能であることを確認します。
タッチスクリーンを使用せずにFire TVデバイスでタッチ機能をテストする方法
最も簡単な方法は、BluetoothマウスをFire TVに接続する方法です。マウスによってタッチ操作をシミュレートします。手順は以下のとおりです。
- [設定] > [コントローラーとBluetoothデバイス] > [その他のBluetoothデバイス] の順に選択します。
- 画面の指示に従ってBluetoothマウスを接続します。
- マウスを接続したら、アプリに戻ります。マウスで画面上のカーソルを制御して、タッチ操作をシミュレートできます。クリックやジェスチャーの動作も併せてシミュレートできます。
- レイアウトのインタラクティブ部分をすべてテストします。
ベストプラクティス
上記の手順をすべて完了すると、アプリのUIで最も重要なコンポーネントがタッチ対応になります。タッチナビゲーションとD-Padナビゲーションの両方で優れたユーザーエクスペリエンスを提供できるよう、以下のベストプラクティスも参考にしてください。
- 対話が必要なビューに
OnClickListener
が割り当てられており、フォーカスを取得できることを確認してください。 - タッチ操作としては、アイテムを選択できるだけでは十分ではありません。可能であれば、
ScrollView
とRecyclerView
を使用して、ジェスチャーでスクロールする方法も実装するようにしてください。 - 詳細ページや再生UIなど、アプリのセカンダリアクティビティも同様にタッチ対応にする必要があります。上記のパターンを使用してタッチ操作を有効にしてください。
- 一部のサードパーティのコンポーネントでは、ユーザーがアクセスできる領域の外にレイアウトアイテム(チェックボックス、ボタンなど)が作成されることがあります。また、D-Padが表示されない場合もあります。このような場合は、
ScrollView
コンポーネントを使用してユーザーが確実にアイテムにアクセスできるようにするなどの方法を検討してください。
サンプルアプリのダウンロード
サンプルアプリをまだダウンロードしていない場合は、GitHubからダウンロードすることで、セットアップから実行までをすばやく行うことができます (Fire TV対応サンプルアプリ:Fire TV Sample App Android - Touch and D-Pad)。このサンプルアプリには上に示したすべてのコードが含まれているため、Fire TV対応アプリにタッチ機能をセットアップする際の土台として活用できます。ご不明な点がある場合は、お問い合わせください。