2014/08/03

AndroidTV: Get Started

AndroidTVについてメモ.
*本稿はAndroid L Developer Previewの情報がベースであり,正式版では変更されている可能性に注意

開発環境: AndroidStudio v0.8.3

TV Design

  • TVデバイス向けに効果的で魅力的なUIを構築するために,リビングルームで何がうまく機能するのかを理解すること.
  • TV向けアプリケーションはリモコンを使って簡単に操作できるシンプルなUIを提供する必要がある.
  • ユーザはTVデバイスからおよそ10フィート(およそ3メートル)離れた場所から操作する.
  • TVは友人や家族等,大勢と画面を共有するデバイスである.
  • ユーザのアクティビティはコンテンツの内容によって変化する.
  • レコメンドはユーザの嗜好を表現する.
  • TVデバイスのサイズと解像度は大きくなる一方,ユーザがTVに求めるエクスペリエンスは”シンプルで整頓されていること”である.
  • AndroidTVでのサウンド効果は映画のような相互作用をもたらす.
  • TVデバイスでのエクスペリエンスの良し悪しは,画面上にある要素の数,間隔,サイズに左右される.
  • TVデザインは常に横向きで使用される.
  • LCDやプラズマTVではしばしばスムーシングとシャープ化のフィルタをかけられ,カラーレンダリングはPCモニターでの見た目とマッチしない.
  • スクリーンのサイズは他のAndroidデバイスよりもはるかに大きいものの,色やディテールはそれに劣る.
  • TVアプリケーションはTheme.Leanbackのテーマ適用が推奨される.
  • UIが欠けないようOverscanの影響を考慮する.レイアウトの周囲余白として10%のマージンを設けるべき.
  • シンプルなsans-serifフォントを使用し,アンチエイリアシングを適用する.
  • タッチスクリーン,カメラ,GPSは多くのデバイスに搭載されるがTVには搭載されていない.

Design for TV

Home

ホームスクリーンはTVの始点であり,検索,レコメンドコンテンツ,アプリケーション一覧,セッティングを提供する場でもある.

TVプラットフォームにはGoogleの検索エンジンを活かした検索機能が搭載されている.
異なるコンテンツ間のコネクションを築き,お気に入りの映画から新たなアーティストを発見したり,
旅行を計画する際にはYoutube上のコンテンツや写真に出会えるかもしれない.
アプリケーション内検索をさせるためのヒント

Recommendations

レコメンドはユーザのメディア消費活動を促進するため,ユーザに関連するコンテンツを動的に表示する.
レコメンド一覧はパーソナライズされ,他のデバイスで活動再開する手段も提供する.
ユーザに有意義なコンテンツをオススメするなどホームスクリーンの中心的な機能となる.

レコメンドはユーザの最近の行動や,繰り返しアクションをベースとするだけでなく,ユーザの嗜好を表現する.
システムまたはアプリケーションからのアクションやNotification情報も表示される.
TV向けアプリケーションはコンテンツに注目させるためにレコメンド一覧に情報を提供することもできる.
Making Recommendations

Application And Game

アプリケーションとGameの一覧はホームスクリーンの中でも特別な領域になる.
アプリケーション,Gamesそれぞれの領域はユーザの使用状況を反映するように表示される.

Settings

ホームスクリーンの下部にはAndroidとTVデバイスに関する設定にアクセスできる.

Creative Vision for TV

Casual Consumption

TVはエンターテインメントインターフェイスであり,コンピュータやモバイルデバイスとは異なる.
ユーザのアクティビティはコンテンツの内容によって変化する.
映画を見ているときはリラックスして,Gameの時は夢中になり,リビングで友人といる時は置き物になる.

ユーザがTVを点ける時は,コンテンツにすぐにでもアクセスしたいと考えている.
コンテンツには1クリックまたは2クリック程で手軽にアクセスさせるように.

Cinematic Experience

夢中にさせるようなユーザ体験を目指すこと.
少しのUIで多くのコンテンツを表示する.視覚映像,動き,音を使ってユーザを満足させる.
情報を伝えるために長々としたテキストは使わず,音と映像で表現すること.

Simplicity

AndroidTVはシンプルで魅力的なものである.それは抵抗なくコンテンツやアプリケーションを探して楽しめることに関係する.
少ないナビゲーションでアクションできることが理想である.
できる限り少ない画面数でアプリケーションにエントリさせ,コンテンツに夢中になれるよう工夫すること.
できる限りユーザにテキスト入力はさせない.テキスト入力させたい場合はボイスインプットの使用を考える.

Pattern for TV

TV向けアプリケーションに関するいくつかのパターン.

一般的にTVデバイスの操作にはD-PAD(Directional pad)を使用する.
このタイプのコントローラは上下左右の移動に制限されている.
アプリケーションをデザインする際はリストやグリッドUIで,上下左右の移動経路が明確になっているかを確認すること.

D-PADによる操作が基本となることで,当然UI部品はフォーカスを持つ.
どの部品がフォーカスを受けるのか一目でわかるように,フォーカスさせるために複雑な操作を必要としないように心がけること.
スケール,光と影,透明度,アニメーションやそれらの組み合わせでフォーカスを表現する.

Icon

TVデバイス上で実行されるアプリケーションはシステムUIで表示するために次のアイコン画像を追加する必要がある.

アプリケーションバナーはホームスクリーンで表示され,アプリケーションを起動するランチャーアイコンとして使用される.
下記はバナーアイコンの要件

  • 320 x 180px, xhdpi resource.
  • 画像にテキストを含めるべき.もし複数の言語をサポートするなら各言語毎のリソースを用意する.

Recommendation Icons

レコメンドアイコンは,レコメンドカードにある有色背景の上に小さなアイコンで表示される.
下記はレコメンドアイコンの要件.

  • モノカラー:サイズは16 x 16dp. 白色(#fff)で背景透過の.png形式
  • アイコンはセンタリングされていること

アプリケーションアイコンはいくつかのカードで低彩度化,レイヤー表示されることがある.

Background Image

背景画像はアプリケーションの裏で表示される. 背景画像は視覚的に好奇心,情報,ブランディングを提供する.
Leanback support libraryに含まれるBrowseFragmentとDetailsFragmentクラスはフォーカスを失った際に背景画像を更新する等のサポートを持つ.
下記は背景画像への要件

  • フルカラーで1920 x 1080px

背景画像が要件に適合しない場合はスクリーンにフィットするようにスケールされる.

Audio Feedback

AndroidTVでのサウンド効果は映画のような相互作用をもたらす.
よく熟考し,ユーザアクションや,積極的にインタラクションしないユーザに対してフィードバックサウンドを追加する.
(e.g., 他のことに気が散っているユーザやマルチタスクの場合など)
それ以外では,視覚的メッセージの代替えとして,たとえばリストの終端に達した場合や領域外に移動しようとした場合等が該当する.

Layout

TVデバイスでのエクスペリエンスの良し悪しは,画面上にある要素の数,間隔,サイズに左右される.
TVデバイスのサイズと解像度は大きくなる一方,ユーザがTVに求めるエクスペリエンスは”シンプルで整頓されていること”である.

現状,巨大化する解像度や画面領域は,より多くの情報を表示するためではなく,より良い品質で情報を表示するために使用される.
例えば,レイアウトを大きく見せてコンテンツを美しく見せたり,読みやすいように文字を大きくしたり,間隔を設ける.

もしコンテンツの閲覧や再生するアプリケーションを作成する場合,Leanback support libraryにあるFragmentを使用できる.
これらのレイアウトはTVデバイス向けに特化されたレイアウトを持っている.

TV向けアプリケーションのための機能デザインと魅力的なレイアウトを作成するためのポイント.

  • 横向きレイアウトデザインであること. TVデザインは常に横向きで使用される.
  • 挿絵に使うassetsはHD解像度(1920 x 1080px)向けにデザインすること.
  • ナビゲーションコントロールはスクリーンの左か右側に配置する. 上下はコンテンツ領域として確保する.
  • セクションで区切られたUIを作成するのにFragmentを活用する.そしてListViewよりGridViewの方がより水平領域を使える.
  • レイアウトの間に間隔を設けて雑然としたインタフェースを避ける.

Overscan

OverscanはTVテクノロジ発展の過程で,TVコンテンツが表示される安全地帯より外側の領域を指す用語.
今でもフラットなHDTVスクリーンのいくつかは外側の領域が表示されない.

TVスクリーンのデザインには,表示されない可能性のあるOverscan領域のことを考えて10%の余白を設けること.
1920 x 1080pxのスクリーンでは,この余白幅は画面端上下から27px,画面端左右から48pxになる.

Color

TVでのカラーレンダリングはPCモニターやモバイルのそれと比べて不正確である.
LCDやプラズマTVではしばしばスムーシングとシャープ化のフィルタをかけられ,カラーレンダリングはPCモニターでの見た目とマッチしない.

この微妙な色合いや明るさの違いが,要素間の違いを打ち消したり必要以上の強調を生む.
彩度の高い色(赤,緑,青)のためにも,広域に白が使われることは避けた方が良い.
TVのコントラスト設定により,非常に暗い色やマッドな色は区別がつかなくなる恐れがあるため使用を避けるべき.

Typography

TVアプリケーションUIでのテキストはTVを見ている距離からでも確認できるようにしなければならない.
最低要件としてフォントサイズは12sp.デフォルトのフォントサイズ設定は18spであるべき.
下記はTVアプリケーションとして推奨されるガイドライン.

  • Card Title: Roboto Condensed 16sp
  • Card Subtext: Roboto Condensed 12sp
  • Browse Screen Title: Roboto Regular 44sp
  • Browse Category Title: Roboto Condensed 20sp
  • Details Content Titles: Roboto Regular 34sp
  • Details Subtext: Roboto Regular 14sp

いくつかのTVは強いシャープ化とコントラスト設定をデフォルトにしている.
これらの設定では,細い書体にはジャギーが発生し読みづらくなる.
そのため,細い書体はTV用としては避けた方が良い.

Text

TVアプリケーションではテキストを使いすぎないように.
ユーザはTVからおよそ10フィート離れたところに位置するため文字が読みづらい.
そのため,TV上で多くのテキストを読むのは好まれない.

次のポイントをおさえておくこと.

  • テキストはいくつかの塊に分けることで認識しやすくなる
  • 暗い背景に明るいテキストの組み合わせで.このスタイルはTVでは読みやすい
  • 細い部分と太い部分があるようなフォントは避け,簡単なsans-serifのフォントやアンチエイリアシングを使うと読みやすい
  • 絶対サイズよりレイアウトにあわせて相対サイズ変化させる方がよい.また,px指定よりdp/sp指定とするのが良い

AndroidTV Project on AndroidStudio

2014.07.25時点ではAndroid L Preview に対応するPreview SDKを使用する.
build.gradleへSdkVersionを設定.

android {
    compileSdkVersion 'android-L'
    ...
    defaultConfig {
        applicationId "tv.android.myandroidtv"
        minSdkVersion 'L'

AndroidTVアプリケーションの基本構造はPhoneやTabletと同じであり,既存のアプリケーションを変更する形でAndroidTVアプリケーション化することもできる.
ただし,次の2点はAndroidTVの為に必要な対応となる.

  • AndroidTV用のActivityを作成し,AndroidManifestに登録する (Required)
  • AndroidTV向けのUI部品を提供するSupport Librariesの適用 (Optional)

TV Activity

TVで動作するActivityを作成するにはandroid.intent.category.LEANBACK_LAUNCHERカテゴリを持つIntentFilterをマニフェストに宣言する.
このIntentFilterを持つアプリケーションはTVデバイスに対応していると識別され,GooglePlay上でTVアプリケーションで動作可能として扱われる.
たとえadb経由等で強制的にアプリケーションをインストールしてもTVデバイス上のUIにアプリケーションが表示されることはない.

対応するIntentFilterを持つManifest定義は下記.

<activity
        android:name="com.example.android.TvActivity"
        android:label="@string/app_name"
        android:theme="@style/Theme.Leanback">

    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LEANBACK_LAUNCHER" />
    </intent-filter>
</activity>

TV Support Libraries

Preview SDKに含まれているTV向けのライブラリにはAPIとUI部品が含まれている.
これらのライブラリの使用は必須でないものの,強く使用を推奨されている.

  • v17 leanback support library
    • TV向けUI部品, BrowseFragment, DetailsFragment, SearchFragmentが含まれる.
    • SDK Location: <sdk>/extras/android/support/v17/leanback
    • Gradle dependency: com.android.support:leanback-v17:20.0.+
    • Contains Resource: yes
  • v7 recyclerview library
    • 長いリストでもメモリを効率的に使用するための管理クラスを提供する
    • v17 leanback libraryのいくつかがこのライブラリに依存する
    • SDK Location: <sdk>/extras/android/support/v7/recyclerview
    • Gradle dependency: com.android.support:recyclerview-v7:20.0.+
    • Contains resources: no

v17 leanback support libraryはv4 support libraryに依存している.
そのため,v17 leanback support libraryを使うには3つのライブラリを内包する必要がある.

compile 'com.android.support:recyclerview-v7:+'
compile 'com.android.support:leanback-v17:+'
compile 'com.android.support:appcompat-v7:+'

Layout for TV

TVは一般的に約10フィート離れた場所から使用される.
スクリーンのサイズは他のAndroidデバイスよりもはるかに大きいものの,色やディテールはそれに劣る.
これらの要因を念頭においてTVデバイスアプリケーションのレイアウトを作成すること.

Themes

TV向けの基本レイアウトはThemeとして提供される.TV向けのActivityはこのテーマを適用すべきである.

Leanback Theme

Leanback support libraryはActivityの標準テーマTheme.Leanbackを備え,TVアプリケーションとして一貫したビジュアルを提供する.
多くのアプリケーションでこのThemeの使用が推奨される.
次のコードはActivityにLeanback themeを適用する方法.

<activity
    android:name="com.example.android.TvActivity"
    android:label="@string/app_name"
    android:theme="@style/Theme.Leanback">

NoTitleBar Theme

タイトルバーはAndroidアプリケーションとして標準的なUIだが,TVアプリケーションはそうではない.
もしLeanback themeを適用しない場合はNoTitleBar Themeを適用すること.
次のコードはActivityにNoTitleBar Themeを適用する方法.

<activity
        android:name="com.example.android.TvActivity"
        android:label="@string/app_name"
        android:theme="@android:style/Theme.NoTitleBar">

Layout Structure

TVデバイスには使いやすく,有用なガイドラインがいくつかある.
TVスクリーン向けのレイアウトを構築する際は次のTipsを参考にすること.

  • Landscapeレイアウトとして構築すること.TVスクリーンは常に横向き表示される.
  • 画面の左側または右側にナビゲーションコントロールを配置し,垂直方向のスペースを確保する.
  • セクションごとに分割されたUIを作成し,フラグメントで表現する.
  • 画面スペースはレイアウトを水平方向に展開するためListViewの代わりにGridViewを使用する.
  • Viewを配置する際にはRelativeLayoutやLinearLayoutのViewGroupを使用する.
    このアプローチでシステムのサイズ,整列,アスペクト比,ピクセル密度によってViewの位置を調節できる.
  • 雑然としたUIを避けるために,レイアウトコントロールの間隔は十分にとること.

Overscan

TVには常にフルスクリーン映像を提供するためのユニークな標準仕様がある.
TVはスクリーン全体に映像を広げるためにコンテンツレイアウトの外枠をクリッピングする.
これは一般にOverscanと呼ばれる.

Overscanの影響を考慮し,UIが欠けることなく画面上に表示されることを保証するために,
レイアウトの周囲余白として10%のマージンを設けるべきである.
マージンは上下27dp,左右48dpに変換される.

下記はマージンを設けるサンプルコード.

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:id="@+id/base_layout"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  android:layout_marginTop="27dp"
  android:layout_marginLeft="48dp"
  android:layout_marginRight="48dp"
  android:layout_marginBottom="27dp" >
</LinearLayout>

もしLeanback support libraryのBrowserFragmentや関連するwidgetを使用している場合,
これらは既にOverscan用マージンを設けているため,マージンを指定する必要はない.

Text and Controls Visibility

TVアプリケーションのレイアウトでは離れた場所からでも見やすく,操作しやすいようにテキストや操作性に注意する.
次のTipsで離れた場所からでもそれらを容易にする.

  • テキストは小さく区切り,まとめることで読みやすくなる
  • 暗い背景に明るいテキストを.TV上ではこのスタイルが見やすい
  • 細いフォントを避けること,または細い部分と太い部分がある書体も避ける.
  • シンプルなsans-serifフォントを使用し,アンチエイリアシングを適用すると読みやすくなる.
  • Android標準のフォントを使用すること
  • 全てのViewが10フィート離れた場所からでも十分に見えるよう大きくすること.
    そのためには絶対的レイアウトではなく相対的レイアウトで組み,ピクセル依存ではなく密度依存のサイズ指定をする.

Android標準のフォントを使用するサンプルは下記.

<TextView
      android:id="@+id/atext"
      android:layout_width="wrap_content"
      android:layout_height="wrap_content"
      android:gravity="center_vertical"
      android:singleLine="true"
      android:textAppearance="?android:attr/textAppearanceMedium"/>

Screen Density and Image Resource

一般的な高精細TVのスクリーン解像度は720p, 1080i, 1080p.
TVアプリケーションがターゲットとすべきスクリーンサイズは1920x1080px.
システムは必要に応じてレイアウト要素を720p向けにダウンスケールする.
一般的にダウンスケーリング(ピクセル除去)はレイアウトの品質を下げないが,アップスケーリングは品質を下げ,UXに悪影響となる.

最適な画像スケーリングのために9-patchイメージが提供されている.
もし低品質な画像や小さな画像を使用していた場合,それらはぼやけたり粗くなる.
これらもUXに悪影響を与えるため,代わりに高品質な画像を提供すること.

レイアウトと大きなスクリーンのためのカスタマイズについて,より詳細な情報はDesigning for multiple screensを参照.

Layout Anti-Patterns

TV向けレイアウトを構築するにあたり避けるべきいくつかのアプローチを紹介.
次の内容はうまく動作せず,悪いUXを招く.

  • PhoneまたはTabletレイアウトの再利用. Phone,Tablet向けレイアウトを再利用しないこと.
    PhoneやTabletのファクタ向けに構築されたレイアウトはTVデバイスには適さない.
    TVに適合させるため簡略化する必要がある.
  • ActionBar. PhoneやTabletでは慣例になっているActionBarはTVでは適していない.
    リモコン操作で,ActionBar上にあるオプションメニューやプルダウンメニューにアクセスするのは困難である.
  • ViewPager. スライディングによりスクリーンを行き来するこのUIはTVでは使用しないこと.

Handling Large Bitmap

TVデバイスは他のAndroidデバイスのようにメモリの上限を持っている.
もしアプリケーションで高精細の画像を使うレイアウトを構築する時,または高解像度の画像をいくつも使う場合,
すぐにメモリ上限に達しOutOfMemoryによりが発生する.これらの問題を回避するために次のTipsを参考にする.

  • イメージは表示される時にだけ読み込む.例えばGridViewやGalleryで複数のイメージを表示する時,
    ViewのAdapterのgetView()が呼ばれた時にだけ画像を読み込む.
  • 長時間使用しないBitmapに対してはrecycle()を呼び出す.
  • CollectionでBitmapオブジェクトをメモリ上で確保する場合はWeakReferenceを使用する.
  • ネットワークから画像を取得するにはAsyncTaskを使用し,デバイス上に保存する.
  • ダウンロードした大きな画像は適切なサイズにスケールダウンすること.そのまま表示するとOutOfMemoryエラーにつながる.

ダウンロードしながらスケールダウンするコードは次の通り.

// Get the source image's dimensions
BitmapFactory.Options options = new BitmapFactory.Options();
// This does not download the actual image, just downloads headers.
options.inJustDecodeBounds = true;
BitmapFactory.decodeFile(IMAGE_FILE_URL, options);
// The actual width of the image.
int srcWidth = options.outWidth;
// The actual height of the image.
int srcHeight = options.outHeight;

// Only scale if the source is bigger than the width of the destination view.
if(desiredWidth > srcWidth)
  desiredWidth = srcWidth;

// Calculate the correct inSampleSize/scale value. This approach helps reduce
// memory use. This value should be a power of 2.
int inSampleSize = 1;
while(srcWidth / 2 > desiredWidth){
  srcWidth /= 2;
  srcHeight /= 2;
  inSampleSize *= 2;
}

float desiredScale = (float) desiredWidth / srcWidth;

// Decode with inSampleSize
options.inJustDecodeBounds = false;
options.inDither = false;
options.inSampleSize = inSampleSize;
options.inScaled = false;
// Ensures the image stays as a 32-bit ARGB_8888 image.
// This preserves image quality.
options.inPreferredConfig = Bitmap.Config.ARGB_8888;

Bitmap sampledSrcBitmap = BitmapFactory.decodeFile(IMAGE_FILE_URL, options);

// Resize
Matrix matrix = new Matrix();
matrix.postScale(desiredScale, desiredScale);
Bitmap scaledBitmap = Bitmap.createBitmap(sampledSrcBitmap, 0, 0,
    sampledSrcBitmap.getWidth(), sampledSrcBitmap.getHeight(), matrix, true);
sampledSrcBitmap = null;

// Save
FileOutputStream out = new FileOutputStream(LOCAL_PATH_TO_STORE_IMAGE);
scaledBitmap.compress(Bitmap.CompressFormat.JPEG, 100, out);
scaledBitmap = null;

TVデバイスでアプリケーションのナビゲーションコントロールには限りがある.
TVアプリケーションで有効なナビゲーションを作成するには限定された操作性とわかりやすさを理解すること.
ユーザがタッチ操作の代わりにD-PADを使ってどのように操作するのか特に注意してアプリケーションを作成する必要がある.

D-PAD Navigation

TVデバイス上でユーザはD-PADあるいは矢印キーを使ってリモート操作を行う.
このタイプのコントローラでは移動が上下左右に制限される.
TV向けにカスタマイズされたアプリケーションを提供するには,コントローラでどう操作すれば良いか直ぐに理解できるように作ること.

TVデバイスにおいてD-PADでのナビケーションシステム向けガイドラインは次の通り.

  • 表示されたコントロール全てに対してD-PADナビゲーションが有効であること.
  • スクロールできるリストにフォーカスがある時,D-PAD上下はリストのスクロールになる.
    Enterキーはアイテム選択に,要素にフォーカスしてリストをスクロールできるように保証すること.
  • コントロール間の移動は直感的かつ予測できること.

AndroidフレームワークはD-PADのような指向ナビゲーションにおいてレイアウト要素間の移動を自動でサポートする.
通常,アプリケーションで特別必要な対応は必要ない.
しかし,D-PADを使用したナビゲーションに問題がないかの確認試験は実施すること.
もし画面の中でナビゲーションが困難な箇所を発見したり,通常とは異なる方法で操作させたい場合は,明示的にナビゲーションを指定できる.

次のコードはTextViewがフォーカスを受けた状態で,次にフォーカスを受けるViewを指定した例.

<TextView android:id="@+id/Category1"
        android:nextFocusDown="@+id/Category2"\>

次のリストはAndroidのUIウィジェットで有効なナビゲーション属性.

  • nextFocusDown: ダウンナビゲーションでフォーカスを受けるViewを定義
  • nextFocusLeft: レフトナビゲーションでフォーカスを受けるViewを定義
  • nextFocusRight: ライトナビゲーションでフォーカスを受けるViewを定義
  • nextFocusUp: アップナビゲーションでフォーカスを受けるViewを定義

これらの属性はシステムが設定するデフォルトの動作で上手く機能しない場合に限り使用すること.

Focus and Selection

スクリーン上にあるUI要素に思ったようにフォーカスをあてられることがTVナビゲーションを成功させるコツである.
わかり辛い(どのようなアクションがとれるか明確でない)とユーザにはストレスになり早々にアプリケーションから離れる.
同様に,アプリケーション起動後すぐにアクションできるように常にアイテムがフォーカスされていることが大切.

色,サイズ,アニメーションまたはそれらの組み合わせとレイアウトでユーザが次に何のアクションをすれば良いかわかるようにすること.
アプリケーション全体で統一されたフォーカスの見せ方をすること.

AndroidはDrawable State List Resourcesで選択時とフォーカスされた際ののハイライト制御を提供する.
次はボタンにそれらを適用したコード.

<!-- res/drawable/button.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="@drawable/button_pressed" /> <!-- pressed -->
    <item android:state_focused="true"
          android:drawable="@drawable/button_focused" /> <!-- focused -->
    <item android:state_hovered="true"
          android:drawable="@drawable/button_focused" /> <!-- hovered -->
    <item android:drawable="@drawable/button_normal" /> <!-- default -->
</selector>

次のレイアウトはこのstate list drawableを適用する例.

<Button
    android:layout_height="wrap_content"
    android:layout_width="wrap_content"
    android:background="@drawable/button" />

フォーカスと選択可能なコントロールの周囲にハイライトが見えるように周囲余白を設けること.

BrowseFragment

Leanback support libraryはTVでバイスでメディアカタログを表示・閲覧するためのAPIをいくつか提供する.
音楽,ビデオを閲覧するためのUIを提供するこれらAPIの使い方を紹介する.

Media Browser Layout

Leanback support libraryにはカテゴリとメディアを少ないコードで実現するための主要レイアウトBrowseFragmentクラスを提供する.
どうやってBrowserFragmentでコンテンツをレイアウトするかの例を次ぎに記す.

<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  android:layout_width="match_parent"
  android:layout_height="match_parent"
  android:orientation="vertical"
  >

  <fragment
      android:name="android.support.v17.leanback.app.BrowseFragment"
      android:id="@+id/browse_fragment"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      />
</LinearLayout>

BrowserFragmentにレイアウトパラメータを設定するサンプルコードは下記.

public class BrowseMediaActivity extends Activity {
    public static final String TAG ="BrowseActivity";
    protected BrowseFragment mBrowseFragment;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.browse_fragment);

        final FragmentManager fragmentManager = getFragmentManager();
        mBrowseFragment = (BrowseFragment) fragmentManager.findFragmentById(
                R.id.browse_fragment);

        // Set display parameters for the BrowseFragment
        mBrowseFragment.setHeadersState(BrowseFragment.HEADERS_ENABLED);
        mBrowseFragment.setTitle(getString(R.string.app_name));
        mBrowseFragment.setBadgeDrawable(getResources().getDrawable(R.drawable.ic_launcher));
        mBrowseFragment.setBrowseParams(params);

    }
}

Displaying Media Lists

BrowseFragmentはAdapterとPresenterでメディアコンテンツカテゴリから閲覧可能なカテゴリとメディア項目を表示できる.

次のコードはPresenterで文字データを表示するサンプル.

public class StringPresenter extends Presenter {
    private static final String TAG = "StringPresenter";

    public ViewHolder onCreateViewHolder(ViewGroup parent) {
        TextView textView = new TextView(parent.getContext());
        textView.setFocusable(true);
        textView.setFocusableInTouchMode(true);
        textView.setBackground(
                parent.getContext().getResources().getDrawable(R.drawable.text_bg));
        return new ViewHolder(textView);
    }

    public void onBindViewHolder(ViewHolder viewHolder, Object item) {
        ((TextView) viewHolder.view).setText(item.toString());
    }

    public void onUnbindViewHolder(ViewHolder viewHolder) {
        // no op
    }
}

メディア項目を表示するためにpresenterを構築し,BrowseFragmentにAdapterをアタッチする.
カテゴリとメディア項目をStringPresenterを使って表示するためのAdapterを構築する方法を次に示す.

private ArrayObjectAdapter mRowsAdapter;
private static final int NUM_ROWS = 4;

@Override
protected void onCreate(Bundle savedInstanceState) {
    ...

    buildRowsAdapter();
}

private void buildRowsAdapter() {
    mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());

    for (int i = 0; i < NUM_ROWS; ++i) {
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
                new StringPresenter());
        listRowAdapter.add("Media Item 1");
        listRowAdapter.add("Media Item 2");
        listRowAdapter.add("Media Item 3");
        HeaderItem header = new HeaderItem(i, "Category " + i, null);
        mRowsAdapter.add(new ListRow(header, listRowAdapter));
    }

    mBrowseFragment.setAdapter(mRowsAdapter);
}

このサンプルコードはAdapterに直接データを埋め込んでいるが,通常はオンラインデータベースやWebサービスからデータを取得する.
Web上のデータを利用してブラウジングするサンプルアプリケーションはAndroid TVを参照.

Updating the Background

TVのメディアブラウジングアプリケーションで興味を誘う視覚効果を追加するために,コンテンツの閲覧を通して背景画像を更新する.
このテクニックはアプリケーションとのインタラクションをよりシネマティックに,楽しいものにする.

Leanback support libraryはTVアプリケーションのActivityが背景を変更できるようにBackgroundManagerクラスを提供する.
次のコードは背景を更新する簡易な方法.

protected void updateBackground(Drawable drawable) {
    BackgroundManager.getInstance(this).setDrawable(drawable);
}

メディア一覧でのメディアブラウズアプリケーションはユーザ操作を通して自動で背景が更新される.
これを実現するには,Selection listenerを設定することで背景を更新する.
次はOnItemSelectedListenerクラスで選択イベントをキャッチし,背景を更新するサンプルコード.

protected void clearBackground() {
    BackgroundManager.getInstance(this).setDrawable(mDefaultBackground);
}

protected OnItemSelectedListener getDefaultItemSelectedListener() {
    return new OnItemSelectedListener() {
        @Override
        public void onItemSelected(Object item, Row row) {
            if (item instanceof Movie ) {
                URI uri = ((Movie)item).getBackdropURI();
                updateBackground(uri);
            } else {
                clearBackground();
            }
        }
    };
}

上記はあくまで一例である.
このファンクションを実装する場合,より良いパフォーマンスを得るためにバックグラウンドスレッドで更新すべき.
さらにアイテムをスクロールしながら閲覧させる場合,背景画像をアップデートするのにアイテムの選択から多少のディレイを加えること.
これにより背景画像の過度な更新を回避できる.

DetailsFragment

Leanback support libraryはメディアブラウジングのためのインタフェースクラスを提供する.
このクラスはメディアアイテムの説明やレビュー,そのアイテムに対してのアクション,購入,再生などの付加情報を表示する.
このセクションではメディア項目詳細のためのPresenterクラスの作り方,詳細画面のためのDetailsFragmentの拡張方法を論じる.

以降のサンプルコードではDetailsFragmentのためにActivityを作成しているが,
BrowserFragmentとDetailsFragmentをFragmentトランザクションを使って置き換えることでActivityを1つで済ませることもできる.

Build a Details Presenter

メディアブラウジングフレームワークはLeanback support libraryで提供される.
詳細情報を含めたスクリーン上のデータ表示を制御するためにPresenterオブジェクトを使用できる.
メディア項目詳細のためのPresenterとしてフレームワークはほぼ完全な実装をAbstractDetailsDescriptionPresenterクラスとして提供する.
ViewにデータをバインドするためのonBindDescription()メソッドを実装すること.
次はそのサンプルコード.

public class DetailsDescriptionPresenter
        extends AbstractDetailsDescriptionPresenter {

    @Override
    protected void onBindDescription(ViewHolder viewHolder, Object itemData) {
        MyMediaItemDetails details = (MyMediaItemDetails) itemData;
        // In a production app, the itemData object contains the information
        // needed to display details for the media item:
        // viewHolder.getTitle().setText(details.getShortTitle());

        // Here we provide static data for testing purposes:
        viewHolder.getTitle().setText(itemData.toString());
        viewHolder.getSubtitle().setText("2014   Drama   TV-14");
        viewHolder.getBody().setText("Lorem ipsum dolor sit amet, consectetur "
                + "adipisicing elit, sed do eiusmod tempor incididunt ut labore "
                + " et dolore magna aliqua. Ut enim ad minim veniam, quis "
                + "nostrud exercitation ullamco laboris nisi ut aliquip ex ea "
                + "commodo consequat.");
    }
}

Extends the Details Fragment

メディア項目詳細の表示にDetailsFragmentクラスを使用している場合,メディアアイテムへのアクションとプレビューを拡張できる.
また,関連するコンテンツリストとして追加でコンテンツを表示できる.

前章でのPresenterクラスの使い方についてサンプルコードを示す.
メディアアイテムが表示される際のプレビューとアクションの追加を行う.
また,このサンプルは関連するメディアコンテンツのリストも追加している.

public class MediaItemDetailsFragment extends DetailsFragment {
    private static final String TAG = "MediaItemDetailsFragment";
    private ArrayObjectAdapter mRowsAdapter;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        Log.i(TAG, "onCreate");
        super.onCreate(savedInstanceState);

        buildDetails();
    }

    private void buildDetails() {
        ClassPresenterSelector selector = new ClassPresenterSelector();
        // Attach your media item details presenter to the row presenter:
        DetailsOverviewRowPresenter rowPresenter =
            new DetailsOverviewRowPresenter(new DetailsDescriptionPresenter());

        selector.addClassPresenter(DetailsOverviewRow.class, rowPresenter);
        selector.addClassPresenter(ListRow.class,
                new ListRowPresenter());
        mRowsAdapter = new ArrayObjectAdapter(selector);

        Resources res = getActivity().getResources();
        DetailsOverviewRow detailsOverview = new DetailsOverviewRow(
                "Media Item Details");

        // Add images and action buttons to the details view
        detailsOverview.setImageDrawable(res.getDrawable(R.drawable.jelly_beans));
        detailsOverview.addAction(new Action(1, "Buy $9.99"));
            detailsOverview.addAction(new Action(2, "Rent $2.99"));
        mRowsAdapter.add(detailsOverview);

        // Add a Related items row
        ArrayObjectAdapter listRowAdapter = new ArrayObjectAdapter(
                new StringPresenter());
        listRowAdapter.add("Media Item 1");
        listRowAdapter.add("Media Item 2");
        listRowAdapter.add("Media Item 3");
        HeaderItem header = new HeaderItem(0, "Related Items", null);
        mRowsAdapter.add(new ListRow(header, listRowAdapter));

        setAdapter(mRowsAdapter);
    }
}

Creating a Details Activity

DetailsFragmentなどのFragmentはActivityに含める必要がある.
ブラウズ用Activityと詳細表示用Activityを分ける場合,Intentを使って後者のActivityを呼び出すことができる.
このセクションでは詳細表示するためのActivityの実装方法について述べる.

まずは詳細表示用のActivityのためのDetailsFragmentを実装したレイアウトを作成する.

<!-- file: res/layout/details.xml -->

<fragment xmlns:android="http://schemas.android.com/apk/res/android"
    android:name="com.example.android.mediabrowser.MediaItemDetailsFragment"
    android:id="@+id/details_fragment"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
/>

次にActivityを作成し,先ほどのレイアウトを設定する.

public class DetailsActivity extends Activity
{
    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.details);
    }
}

最後に作成したActivityをAndroidManifest.xmlに追加.この時,Leanbackテーマの適用を忘れないこと.

<application>
  ...

  <activity android:name=".DetailsActivity"
    android:exported="true"
    android:theme="@style/Theme.Leanback"/>

</application>

Listener for Clicked Items

DetailsFragmentを実装した後は,ユーザがメディアアイテムを選択した時にそこへ遷移させるように変更する.
これを実装するにはOnItemClickedListenerをBrowserFragmentに実装し,詳細表示ActivityのIntentを発行させる.

下記は閲覧用Activityでユーザがメディア項目をクリックした時に詳細表示用Activityに遷移させるリスナーを実装したコード.

public class BrowseMediaActivity extends Activity {
    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        ...

        // create the media item rows
        buildRowsAdapter();

        // add a listener for selected items
        mBrowseFragment.setOnItemClickedListener(
            new OnItemClickedListener() {
                @Override
                public void onItemClicked(Object item, Row row) {
                    System.out.println("Media Item clicked: " + item.toString());
                    Intent intent = new Intent(BrowseMediaActivity.this,
                            DetailsActivity.class);
                    // pass the item information
                    intent.getExtras().putLong("id", item.getId());
                    startActivity(intent);
                }
            });
    }
}

Adding Search to TV アプリケーション

ユーザはしばしば特定のコンテンツを思い浮かべながらメディアアプリケーションを使用する.
検索インタフェースはコンテンツ一覧から探すより素早く目的のコンテンツを探すことができる方法を提供する.
Leanback support libraryは標準の検索用インタフェースをアプリケーションから使用できるようにクラス群を提供する.
これは他の機能と一貫したUIとボイスインプットによる検索機能を提供する.

Add Search User Interface

メディア閲覧のインタフェースにBrowseFragmentクラスを使用している時,BrowseFragmentに検索アイコンのOnClickListenerを設定できる.
次はそのサンプルコード.

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.browse_activity);

    mBrowseFragment = (BrowseFragment)
            getFragmentManager().findFragmentById(R.id.browse_fragment);

    ...

    mBrowseFragment.setOnSearchClickedListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            Intent intent = new Intent(BrowseActivity.this, SearchActivity.class);
            startActivity(intent);
        }
    });

    mBrowseFragment.setAdapter(buildAdapter());
}

検索アイコンにはBrowseFragmentのsetSearchAffordanceColor()メソッド色を設定できる.

ユーザが検索アイコンを選択したとき,システムは定義済みIntentを介してSearchActivityを開始する.
検索用ActivityはLinerLayoutベースで検索用Fragmentを内包すること.
このFragmentはSearchFragment.SearchResultProviderインタフェースを実装し検索結果を表示する.
次はSearchFragmentを拡張し,検索インタフェースと検索結果を提供するコードサンプル.

public class MySearchFragment extends SearchFragment
        implements SearchFragment.SearchResultProvider {

    private static final int SEARCH_DELAY_MS = 300;
    private ArrayObjectAdapter mRowsAdapter;
    private Handler mHandler = new Handler();
    private SearchRunnable mDelayedLoad;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
        setSearchResultProvider(this);
        setOnItemClickedListener(getDefaultItemClickedListener());
        mDelayedLoad = new SearchRunnable();
    }

    @Override
    public ObjectAdapter getResultsAdapter() {
        return mRowsAdapter;
    }

    @Override
    public boolean onQueryTextChange(String newQuery) {
        mRowsAdapter.clear();
        if (!TextUtils.isEmpty(newQuery)) {
            mDelayedLoad.setSearchQuery(newQuery);
            mHandler.removeCallbacks(mDelayedLoad);
            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
        }
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        mRowsAdapter.clear();
        if (!TextUtils.isEmpty(query)) {
            mDelayedLoad.setSearchQuery(query);
            mHandler.removeCallbacks(mDelayedLoad);
            mHandler.postDelayed(mDelayedLoad, SEARCH_DELAY_MS);
        }
        return true;
    }
}

このサンプルコードは検索実行コードを別スレッドで実行すべきことを示唆している.
このテクニックは時間のかかるクエリ処理がメインスレッドをブロックしないようにする.

Making Recommendations

コンテンツレコメンドはTVのランチャーとしてまず最初にユーザが目にするリストである.
このリストはユーザが手軽にコンテンツを楽しむことを支援する.
あなたのアプリケーションのコンテンツカタログを提供することで,ユーザをあなたのアプリケーションに連れてくる助けにもなる.

Create a Recommendations Service

コンテンツレコメンドはバックグラウンドプロセスで作成される.
あなたのアプリケーションがレコメンドに寄与するために,サービスを作成して周期的にアプリケーションのカタログをシステムレコメンドリストへ追加できる.

次のコードはIntentServiceを拡張してレコメンドサービスを作成する例である.

public class RecommendationsService extends IntentService {
    private static final int MAX_RECOMMENDATIONS = 3;

    public RecommendationsService() {
        super("RecommendationService");
    }

    @Override
    protected void onHandleIntent(Intent intent) {
        MovieDatabase database = MovieDatabase.instance(getApplicationContext());
        List recommendations = database.recommendations();

        int count = 0;

        try {
            for (Movie movie : recommendations) {
                // build the individual content recommendations
                buildRecommendation(getApplicationContext(), movie);

                if (++count >= MAX_RECOMMENDATIONS) {
                    break;
                }
            }
        } catch (IOException e) {
            Log.e(TAG, "Unable to update recommendation", e);
        }
    }
}

このサービスをアプリケーションに登録するコード.

<manifest ... >
  <application ... >
    ...

    <service android:name=".RecommendationsService"
             android:enabled="true" android:exported="true"/>
  </application>
</manifest>

Build Recommendations

サービスを開始したらレコメンドを作成しAndroid Frameworkに渡すこと.
Frameworkはレコメンドを特殊スタイル,特殊カテゴリでマークされたNotificationオブジェクトとして受信する.

次のコードは,レコメンドを作成しNotificationManagerへポストするまでのサンプル.

public class RecommendationsService extends IntentService {

    ...

    public Notification buildRecommendation(Context context, Movie movie)
            throws IOException {

        if (mNotificationManager == null) {
            mNotificationManager = (NotificationManager)
                    mContext.getSystemService(Context.NOTIFICATION_SERVICE);
        }

        Bundle extras = new Bundle();
        if (mBackgroundUri != movie.getBackgroundUri()) {
            extras.putString(EXTRA_BACKGROUND_IMAGE_URL, movie.getBackgroundUri());
        }

        // build the recommendation as a Notification object
        Notification notification = new NotificationCompat.BigPictureStyle(
                new NotificationCompat.Builder(context)
                        .setContentTitle(movie.getTitle())
                        .setContentText(movie.getDescription())
                        .setPriority(movie.getPriority())
                        .setOngoing(true)
                        .setCategory("recommendation")
                        .setLargeIcon(movie.getImage())
                        .setSmallIcon(movie.getSmallIcon())
                        .setContentIntent(buildPendingIntent(movie.getId()))
                        .setExtras(extras))
                .build();

        // post the recommendation to the NotificationManager
        mNotificationManager.notify(movie.getId(), notification);
        mNotificationManager = null;
        return notification;
    }

    private PendingIntent buildPendingIntent(long id) {
        Intent detailsIntent = new Intent(this, DetailsActivity.class);
        detailsIntent.putExtra("id", id);

        TaskStackBuilder stackBuilder = TaskStackBuilder.create(this);
        stackBuilder.addParentStack(DetailsActivity.class);
        stackBuilder.addNextIntent(detailsIntent);
        // Ensure each PendingIntent is unique
        detailsIntent.setAction(Long.toString(id));

        PendingIntent intent = stackBuilder.getPendingIntent(
                0, PendingIntent.FLAG_UPDATE_CURRENT);
        return intent;
    }
}

Run Recommendations Service

アプリケーションのレコメンドサービスは現在のレコメンドを作成する為に周期的に実行されるべきである.
そのためにタイマーか一定時間置きにサービスを実行する.
次のコードはBroadcastReceiverを拡張し,12時間毎に定期的に実行するサンプルコードである.

public class BootupReceiver extends BroadcastReceiver {
    private static final String TAG = "BootupActivity";

    private static final long INITIAL_DELAY = 5000;

    @Override
    public void onReceive(Context context, Intent intent) {
        if (intent.getAction().endsWith(Intent.ACTION_BOOT_COMPLETED)) {
            scheduleRecommendationUpdate(context);
        }
    }

    private void scheduleRecommendationUpdate(Context context) {
        AlarmManager alarmManager = (AlarmManager)context.getSystemService(
                Context.ALARM_SERVICE);
        Intent recommendationIntent = new Intent(context,
                UpdateRecommendationsService.class);
        PendingIntent alarmIntent = PendingIntent.getService(context, 0,
                recommendationIntent, 0);

        alarmManager.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
                INITIAL_DELAY,
                AlarmManager.INTERVAL_HALF_DAY,
                alarmIntent);
    }
}

BroadcastReceiverクラスがTVデバイス開始時に実行されるよう,マニフェストに次のコードを追加する.

<manifest ... >
  <application ... >
    <receiver android:name=".BootupReceiver" android:enabled="true"
              android:exported="false">
      <intent-filter>
        <action android:name="android.intent.action.BOOT_COMPLETED"/>
      </intent-filter>
    </receiver>
  </application>
</manifest>

important: Boot complete通知を受けるにはRECEIVE_BOOT_COMPLETEパーミッションが必要になる.

Games on TV

TVスクリーンはモバイルGame開発者にとって熟考が必要なことがいくつかある.
巨大なスクリーンサイズ,制御方式,複数のユーザが同時にプレイしている可能性.

Display

2つのポイントを念頭に置いておく.
TVスクリーンで開発をする際には画面が共有されているということと,横画面モードでのデザインが必要であること.

Shared display

リビングなどで大人数でGameをする場合,スクリーンが共有されることに依る障害に注意が必要.
たとえば各プレーヤーが情報を隠すことに意味のあるGame(カードGameやストラテジGame)が該当する.

この対応として,1プレイヤーが別プレイヤーの情報を見られなくするためのメカニズムを実装する必要があります.

  • 例えば,ユーザAのカード情報を表示している間はBユーザのカード情報を伏せること.
    Aユーザが操作をしている間Bユーザは画面を見ないようにし,Bユーザが操作をしている間Aユーザは画面を見ないようにする.
    重要なのはAが操作している時にBの情報は画面上から伏せられていること(逆もしかり)
  • あるいは,PhoneやTablet上でそれらの情報を表示して情報を手の中の秘密にすることもできる.

Landscape display

TVは常に横向きで画面回転することができない.そして縦向きは存在しない.
常にGameを横向きとしてデザインすること.

Input Device

TVはタッチインタフェースを持っていない.
これはアプリ制御を理解する上で,Gameを直感的に楽しませるために非常に重要である.
また,コントローラがTVデバイスから分離されたおり,複数プレイヤーからのコントロールを確立することについても障害が多くある.

D-PAD

D-PADがAndroidTVの標準コントローラとなるため,D-PADによる制御方式を基本に設計すること.
プレイヤーが全ての画面でD-PADによる操作ができるようにすること.コアとなるGameプレイ以外にもメニュー,広告への操作も含まれる.

プレイヤーのコントローラとのインタラクションをどのように形成していくかはUX成功の鍵になるかもしれない.

  • Communicate Controller Requirements up Front: Gameプレイにコントローラが必要な旨をPlayStoreに記載すること.
    Game操作にD-PADよりジョイスティックを備えたコントローラの方がより適合する場合はそれを明記する.
    不適当なコントローラを利用するプレイヤーはあなたのアプリ評価を悪くする.
  • Use Consistent Button Mapping: 直感的かつ柔軟なボタンマッピングはより良いUXへの鍵となる.
    例えばAボタンはAccept, BボタンはCancelといった具合に.
    さらにはボタンマッピングをカスタマイズさせる柔軟性を持たせることもできる.
    より詳細なボタンマッピングに関する情報はHandling Controller Actionsを参考.
  • Detect Controller Capabilities and Adjust Accordingly: Gameとコントローラを最適化させるために,コントローラの仕様を確認すること.
    例えば,ユーザにコントローラを振って操作させたいつもりでもコントローラが加速度計やジャイロスコープを搭載していなければ動作しない.
    それらを事前に検知して代替可能なコントローラ操作に切り替えることもできる.
    コントローラの仕様について問い合わせる方法の詳細はSupporting Controllers Across Android Versionsを参照.

Back-button behavior

バックボタンはトグル動作させないこと.例えば,メニューのopen/close等.
バックボタンはパンくずリストでの前場面に戻るために使用されるべき.
例えば Game play > Game pause screen > Game main screen > Android home screen といった具合に.

バックボタンは後方への直線的なナビゲーションであるべきで,Gameのメニューを閉じてメインのプレイ画面に戻る操作として使用してもよい.
ナビゲーションについてのより詳細な情報についてはNavigation with Back and Upを参照.
実装を学びたいならProviding Proper Back Navigationを参照.

Handling multiple controllers

マルチプレイヤーに対応する場合,各々のプレイヤーが持つコントローラペアを意識するのは重要.
どのようにしてコントローラを識別するか,実装についてはInput Devicesを参照.

Handling disconnects

Gameプレイ中にコントローラの接続が解除された場合はGameを一時停止し,その旨をユーザにダイアログで伝えること.

ダイアログはトラブルシューティングとして表示する(例えばBluetooth接続を確認させるダイアログをポップアップ表示するなど).
より詳細な入力デバイスに関する情報はをSupporting Game Controllers参照.
Bluetooth接続はBluetoothを参照.

Manifest

ランチャーでGameはそれ専用のリスト上に表示される.
AndroidTVはandroid:isGameフラグを使用してGameかそうでないアプリかを区別する.
下記はisGameフラグを指定するサンプルソース.

<application>
 ...
< android:isGame=["true" | "false"] >
 ...
</application>

Google Play Game Services

作成するGameをGooglePlayGameServiceに統合させる場合は実績,サインオン,セーブ,マルチプレイヤー等いくつか熟考すべきことがある.

Achievements

Gameには得ることができる実績を含んできる必要がある.
Gameをコントロールしたユーザだけが実績を得るべき.実績の実装方法についてはAchievements in Androidを参照.

Sign-in

Game起動時にユーザのサインインを試みるように.
ユーザがサインインを数回断るようであればその試みはやめるべき.
サインインについての情報はImplementing Sign-in on Androidを参照.

Saving

Gameデータの保存はPlayServiceのCloudSaveを推奨する.
GameのセーブデータはGoogleアカウントと紐づけられ,複数のデバイスで個人を識別できる.
Phone,TabletやTVからでもアカウント情報からセーブデータをpullできる.

アプリはGameプレイヤーにローカルとクラウドそれぞれに保存されたデータの削除を許容するUIをもうけるべき.
あなたはGame中の設定画面にこの機能を配置するかもしれない.これらの実装方法についてはCloud Save in Androidを参照.

Multiplayer experience

マルチプレイ可能なGameは少なくとも2人以上のプレイヤーが参加する.
AndroidでのマルチプレイヤーGameについての詳細情報は[Real-time Multiplayer]https://developers.google.com/games/services/android/realtimeMultiplayer)とTurn-based Multiplayerを参照.

Web

GoogleAndroidチームはGameにWebブラウジングを持ち込むことを推奨しない.
それはTVスクリーンやリモコンでWebブラウジングするには適していないためである.
note: WebViewをGoogle+やFacebookへのログインサービスとして使用することはできる.

Hardware Feature

TVは他のAndroidデバイスにあるものを搭載していない.
例えばタッチスクリーン,カメラ,GPSは多くのデバイスに搭載されるがTVには搭載されていない.
TV向けアプリケーションを作成する際にはこれらの機能がなくても動作するように設計すること.

ここでは有効でないHardware Feature制限下でどうやってTVアプリケーションを動作させるかを議論する.

Unsupported Hardware Features

TVは他のデバイスとは異なる目的を持つ.そして他のAndroidデバイスでは通常持ち得るHardwareを持っていない.
下記はサポートしていないHardware feature.

  • Camera: android.hardware.camera
  • GPS: android.hardware.location.gps
  • Microphone: android.hardware.microphone
  • NFC: android.hardware.nfc
  • Telephony: android.hardware.telephony
  • Touchscreen: android.hardware.touchscreen

Checking Available Features

実行環境で有効なFeatureを確認するにはhasSystemFeature(String)を呼ぶ.
このメソッドは単一のStringを引数にとり,指定されたfeatureをチェックできる.
たとえば,タッチスクリーンが有効であるかを確認したい場合は引数にFEATURE_TOUCHSCREENを指定する.

下記は実行環境でHardware featureの有効チェックを行うコード.

// Check if the telephony hardware feature is available.
if (getPackageManager().hasSystemFeature("android.hardware.telephony")) {
    Log.d("Mobile Test", "Running on phone");
// Check if android.hardware.touchscreen feature is available.
} else if (getPackageManager().hasSystemFeature("android.hardware.touchscreen")) {
    Log.d("Tablet Test", "Running on devices that don't support telephony but "+
            "do have a touch screen.");
} else {
    Log.d("TV Test", "Running on a TV!");
}

UiModeManager.getCurrentModeType()メソッドを利用して現在のプラットフォームの種類を判断できる.
TVでバイス上ではこのメソッドはConfiguration.UI_MODE_TYPE_TELEVISIONを返す.

Checking Available and Handling Unsupported Features

アプリケーションが特定のHardware featureが有効でなくなった場合の対処法.

Touch screen

AndroidTVではタッチスクリーンをサポートしない.
そもそもTVは10フィート離れて座った状態から使用されるためこの要件はマッチしない.
TVデバイスではこの制限に対してDirectionPad(D-PAD)によるリモートコントロールを採用する.
より詳しいTVコントロールの情報はNavigation for TVを参照.

アプリケーションがタッチスクリーンFeatureを必要とするかどうかについては次のコードをマニフェストに定義する.

<uses-feature android:name="android.hardware.touchscreen"
        android:required="false"/>

Camera

TVは一般的にカメラを備えていない.
もし,写真撮影アプリケーションの開発でカメラ機能の搭載を必須とせず,写真の閲覧と編集機能だけでも提供したいような場合は,
カメラ機能をアプリケーション動作の必須要件としないようにマニフェストへ次の定義を追加する.

<uses-feature android:name="android.hardware.camera" android:required="false" />

もし,アプリケーションにカメラ機能が搭載されていた場合に有効としたい機能がある場合,
次のカメラの有効性をチェックする方法が有効.

// Check if the camera hardware feature is available.
if (getPackageManager().hasSystemFeature("android.hardware.camera")) {
    Log.d("Camera test", "Camera available!");
} else {
    Log.d("Camera test", "No camera available. View and edit features only.");
}

GPS

TVは屋内据え付けのデバイスでありGPS受信機を持たない.
アプリケーションがロケーション情報を必要とする場合,ユーザが場所を検索するか,
TVセットアップ時に設定されるZIPコードを対象とするStaticLocationProviderを使用することができる.

LocationManager locationManager = (LocationManager) this.getSystemService(
        Context.LOCATION_SERVICE);
Location location = locationManager.getLastKnownLocation("static");
Geocoder geocoder = new Geocoder(this);
Address address = null;

try {
  address = geocoder.getFromLocation(location.getLatitude(),
          location.getLongitude(), 1).get(0);
  Log.d("Zip code", address.getPostalCode());

} catch (IOException e) {
  Log.e(TAG, "Geocoder error", e);
}

Portions of this page are modifications based on work created and shared by the Android Open Source Project
and used according to terms described in the Creative Commons 2.5 Attribution License.