開発者コンソール

Fire TV対応アプリにタッチ機能を追加する方法

Fire TV対応アプリにタッチ機能を追加する方法

Amazon Fire TV対応アプリに、リモコンやD-Padによる操作に加えて、タッチ操作もセットアップできるようになりました。車載用Fire TVの登場に伴い、この機能はますます重要になってきています。このチュートリアルでは、リモコンおよびD-Pad用に設計された既存のFire TV対応アプリを修正してタッチ機能を追加する方法と、タッチ操作の優れたUXを提供する方法について説明します。詳細については、Automotive UXガイドラインを参照してください。

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が複数含まれています。これらには、再生可能なすべての映画とコンテンツが含まれています。
黒のmenuLayout(左)とグレーのrowsLayout(右)
黒のmenuLayout(左)とグレーのrowsLayout(右)

実際のアプリではビューの入れ子がさらに複雑になる可能性がありますが、ここではメディア/TV向けアプリの動的UIの概要を示しています。

重要: スクロール可能なコンポーネント(ScrollViewなど)を使用してレイアウトを構築すると、スクロールが有効になります。

レイアウトで方向ナビゲーションの動作を定義していきましょう。まず、カテゴリーメニューからコンテンツ行に移動できるようにします。これを行うには、LinearLayoutでnextFocusRightを先頭行のRecyclerViewに設定します。

<LinearLayout   
    android:id="@+id/menuLayout"
    [...]
    android:nextFocusRight="@id/rowRecyclerView1">

これで、ユーザーが右ボタンを押すと、フォーカスが右側の最初のRecyclerViewに自動的に移動するようになります。

次に、RecyclerViewアイテム間のナビゲーションの動作をセットアップします。RecyclerViewのビューはランタイム時に動的に作成されるため、個々のビューでナビゲーションの方向を手動で設定することは現実的ではありません。つまり、XMLレイアウトによる設定はできません。代わりに、RecyclerViewdescendantFocusabilityタグを使用します。

<androidx.recyclerview.widget.RecyclerView   
    android:id="@+id/recyclerViewMenu"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:descendantFocusability="afterDescendants" />

descendantFocusabilityafterDescendantsに設定することで、いったんビューが動的に生成されたら、RecyclerView自体がRecyclerView内のアイテムにフォーカスを自動的に提供するようになります。この場合、RecyclerViewメニューで定義されているカテゴリーにフォーカスが置かれます。

右側のレイアウトのすべてのRecyclerViewに対して、この動作を同様に適用します。その後、各RecyclerView間の方向ナビゲーションを定義します。わかりやすくするために、ここでは4つの専用のRecyclerViewを使用して4つの行を定義しています。

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のコンテンツを変更してインターフェイスをタッチ対応にすることができるようになりました。

ビューが動的に生成される場合でもUIがリモコンによって完全にナビゲート可能になっている
ビューが動的に生成される場合でもUIがリモコンによって完全にナビゲート可能になっている

手順2: OnClickListenerを使用してタッチ機能を追加する

Androidはタッチ操作を考慮して構築されているため、TV対応Androidアプリにタッチ機能を追加するのは、実際には非常に簡単です。アプリのUIにタッチ機能を追加する場合は、標準のAndroidコンポーネントを使用できます。これにより、D-Pad操作とタッチ操作の両方が可能になります。

ベストプラクティスとして、OnClickListenerを使用して、ビューにクリックアクションまたはタッチアクションを実装することをお勧めします。OnClickListenerは、onClick()というメソッドをトリガーし、任意の操作を実行できるようにします。

ビューのサイズは、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ビューがクリック可能になり、クリックされた場合(青)やフォーカスを受け取った場合(オレンジ)などに、異なる背景色が適用されるようになりました。

リモコンを介して「onClickListener」が正しくトリガーされている
リモコンを介して「onClickListener」が正しくトリガーされている

手順3: D-Pad操作とタッチ操作間のビューフォーカスを管理する

D-Pad操作とタッチ操作との間に一貫性を持たせることが重要です。したがって、リモコンとタッチ機能のどちらを使用している場合でも、方向ナビゲーションが一貫して機能するようにする必要があります。

Androidはタッチ操作を考慮して構築されています。つまり、UIのビューを管理する基盤レイヤーは、ほとんどが既にタッチ対応になっています。

Androidのほとんどのビューは、デフォルトで表示およびフォーカス可能になっています。ビューはfocusableというパラメーターを継承します。これは、デフォルトでは「auto」に設定されています。つまり、ビューがフォーカス可能かどうかはプラットフォームによって決定されます。ButtonTextViewEditTextなどのビューは、主要なUIコンポーネントであるため、デフォルトでフォーカス可能になっています。レイアウトとレイアウトインフレーター(LayoutInflater)は、ほとんどの場合UI構造を定義するため、デフォルトでフォーカス可能になっていることはまずありません。

アプリを完全にタッチ対応にするには、最も重要なビューがフォーカス可能な状態で、ユーザーがタッチ操作を使用したときにフォーカスを取得できるようにします。これには、各ビューの2つのパラメーター(focusablefocusableInTouchMode)を編集する必要があります。

サンプルアプリでは、2つの新しいレイアウトを作成して、「カテゴリー」のRecyclerViewと「行」のRecyclerView内の各アイテムを設定しています。そのレイアウトファイルがmenuLayout.xmlcardLayout.xmlです。

「カテゴリー」のRecyclerView(左側、黒い背景)と「行」のRecyclerView(グレーの背景)
「カテゴリー」のRecyclerView(左側、黒い背景)と「行」のRecyclerView(グレーの背景)

タッチやD-Padでのフォーカスを許可するには、focusablefocusableInTouchModeの両方の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には、右側の映画行の各コンテンツが定義されています。各カードにはImageViewTextViewが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に接続する方法です。マウスによってタッチ操作をシミュレートします。手順は以下のとおりです。

  1. [設定] > [コントローラーとBluetoothデバイス] > [その他のBluetoothデバイス] の順に選択します。
  2. 画面の指示に従ってBluetoothマウスを接続します。
  3. マウスを接続したら、アプリに戻ります。マウスで画面上のカーソルを制御して、タッチ操作をシミュレートできます。クリックやジェスチャーの動作も併せてシミュレートできます。
  4. レイアウトのインタラクティブ部分をすべてテストします。

ベストプラクティス

上記の手順をすべて完了すると、アプリのUIで最も重要なコンポーネントがタッチ対応になります。タッチナビゲーションとD-Padナビゲーションの両方で優れたユーザーエクスペリエンスを提供できるよう、以下のベストプラクティスも参考にしてください。

  • 対話が必要なビューにOnClickListenerが割り当てられており、フォーカスを取得できることを確認してください。
  • タッチ操作としては、アイテムを選択できるだけでは十分ではありません。可能であれば、ScrollViewRecyclerViewを使用して、ジェスチャーでスクロールする方法も実装するようにしてください。
  • 詳細ページや再生UIなど、アプリのセカンダリアクティビティも同様にタッチ対応にする必要があります。上記のパターンを使用してタッチ操作を有効にしてください。
  • 一部のサードパーティのコンポーネントでは、ユーザーがアクセスできる領域の外にレイアウトアイテム(チェックボックス、ボタンなど)が作成されることがあります。また、D-Padが表示されない場合もあります。このような場合は、ScrollViewコンポーネントを使用してユーザーが確実にアイテムにアクセスできるようにするなどの方法を検討してください。

サンプルアプリのダウンロード

サンプルアプリをまだダウンロードしていない場合は、GitHubからダウンロードすることで、セットアップから実行までをすばやく行うことができます (Fire TV対応サンプルアプリ:Fire TV Sample App Android - Touch and D-Pad)。このサンプルアプリには上に示したすべてのコードが含まれているため、Fire TV対応アプリにタッチ機能をセットアップする際の土台として活用できます。ご不明な点がある場合は、お問い合わせください。

リソース