BrowseFragmentの構成
本章では、Leanbackライブラリのメインとして使われるBrowseFragmentについて説明する。BrowseFragmentに、HeaderとListの組み合わせを格納していくことによって、コンテンツをリスト上に表示することができる。ちなみに、BrowseFragmentのソースコードは sdk上 (android/support/v17/leanback/app/) で見ることができる。
下の写真は Android TV のサンプルアプリのメインページで、これがBrowseFragmentによって作成されている。 コンテンツがグリッド上に並べられ、ヘッダーごとにカテゴリ分けされているのがわかる。それぞれのヘッダーに対応してコンテンツの列があり、1対1対応している。この “ヘッダー + コンテント列” の組み合わせは ListRow で作られる。 BrowseFragment の中身(本体)は ListRow のセットだということだ。今後このセットのことを RowsAdapter と呼ぶ。
下の写真に対応関係を図示した。 ListRow は青丸で囲ってある部分、RowsAdapter はListRowすべてを囲っている青い四角の部分に相当する。

ListRow 部分に注目してみよう。各コンテンツ (以降 CardInfo や アイテム と表記)は ArrayObjectAdapter の中に格納される(ここでは RowAdapter と表記)。
このアイテムはどんなクラス、オブジェクトでもよくそのUI表示は Presenter クラスによって指定される。

まとめ
ArrayObjectAdapter (RowsAdapter) ← ListRow の集合
ListRow = HeaderItem + ArrayObjectAdapter (RowAdapter)
ArrayObjectAdapter (RowAdapter) ← アイテムの集合
Presenter クラス
アイテム(カード)のデザインは Presenter が担う。 Presenter はその名の通り、どのようにアイテムを表示するかを決めるクラスだ。 Presenter クラス自体は抽象クラスで、このPresenterクラスを継承したサブクラスに、アプリに適した具体的なUI表示の実装を行う。
Presenter クラスを継承するときには、以下の3つのメソッドをオーバーライド(実装)が必須となる。
onCreateViewHolder(Viewgroup parent)onBindViewHolder(ViewHolder viewHolder, Object cardInfo/item)onUnbindViewHolder(ViewHolder viewHolder)
それぞれのメソッドの詳細は Presenter クラスのソースコードに記載されているので参照してほしい。 Presenter クラスは ViewHolder をインナークラスとして持っていて、これが View の参照を保持する。 アイテムオブジェクトに応じて、 View のUIを実装したい場合は viewHolder を介してViewにアクセスすることができる。アクセスするタイミングは onBind, onUnbind といったそれぞれのイベントにリスナーが用意されていて、その中でUI処理を行う(後述)。
実はこれはModel-View-Presenter (MVP)アーキテクチャによるもの。コンテンツをModel Objectで扱う際、そのUI表示のテンプレートデザインをViewが担当し、ModelとViewのつなぎ役をPresenterが担当する。より細かくMVPに関して知りたい方は14章を参照。
HeadersFragment & RowsFragment (GridItemPresenter) の実装
説明はここまでにして、ここから実装。ここではPresenterの1例として GridItemPresenter クラスを実装する。
ここでは”アイテム”(Model)は String クラス、 viewHolder は TextView (View)を保持する。
Viewに関する設定を、Presenterが行う。初期化処理は onCreateViewHolder()にて。
実際にアイテムをViewと結びつける処理は onBindViewHolder() にて行われる。onBindViewHolderでは、第1引数で viewHolder を受け取ることができ、これが onCreateViewHolder 時に作成したViewを保持している。また第2引数で実際に表示するアイテムのインスタンスを受け取っている。
private class GridItemPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
TextView view = new TextView(parent.getContext());
view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.setBackgroundColor(getResources().getColor(R.color.default_background));
view.setTextColor(Color.WHITE);
view.setGravity(Gravity.CENTER);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
((TextView) viewHolder.view).setText((String) item);
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
}
}
}
実際にPresenterクラスの実装ができたら、これを RowsAdapter にセットすることで適用できる。これはActivityの作成時に行う。 以下では MainFragment の に実装onActivityCreated()
@Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.i(TAG, "onActivityCreated");
super.onActivityCreated(savedInstanceState);
setupUIElements();
loadRows();
}
...
private void loadRows() {
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
/* GridItemPresenter */
HeaderItem gridItemPresenterHeader = new HeaderItem(0, "GridItemPresenter");
GridItemPresenter mGridPresenter = new GridItemPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add("ITEM 1");
gridRowAdapter.add("ITEM 2");
gridRowAdapter.add("ITEM 3");
mRowsAdapter.add(new ListRow(gridItemPresenterHeader, gridRowAdapter));
/* set */
setAdapter(mRowsAdapter);
}
最終的に、MainFragment の全ソースコードはこんな感じになる。
package com.corochann.androidtvapptutorial;
import android.graphics.Color;
import android.os.Bundle;
import android.support.v17.leanback.app.BrowseFragment;
import android.support.v17.leanback.widget.ArrayObjectAdapter;
import android.support.v17.leanback.widget.HeaderItem;
import android.support.v17.leanback.widget.ListRow;
import android.support.v17.leanback.widget.ListRowPresenter;
import android.support.v17.leanback.widget.Presenter;
import android.util.Log;
import android.view.Gravity;
import android.view.ViewGroup;
import android.widget.TextView;
/**
* Created by corochann on 2015/06/28.
*/
public class MainFragment extends BrowseFragment {
private static final String TAG = MainFragment.class.getSimpleName();
private ArrayObjectAdapter mRowsAdapter;
private static final int GRID_ITEM_WIDTH = 300;
private static final int GRID_ITEM_HEIGHT = 200;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
Log.i(TAG, "onActivityCreated");
super.onActivityCreated(savedInstanceState);
setupUIElements();
loadRows();
}
private void setupUIElements() {
// setBadgeDrawable(getActivity().getResources().getDrawable(R.drawable.videos_by_google_banner));
setTitle("Hello Android TV!"); // Badge, when set, takes precedent
// over title
setHeadersState(HEADERS_ENABLED);
setHeadersTransitionOnBackEnabled(true);
// set fastLane (or headers) background color
setBrandColor(getResources().getColor(R.color.fastlane_background));
// set search icon color
setSearchAffordanceColor(getResources().getColor(R.color.search_opaque));
}
private void loadRows() {
mRowsAdapter = new ArrayObjectAdapter(new ListRowPresenter());
/* GridItemPresenter */
HeaderItem gridItemPresenterHeader = new HeaderItem(0, "GridItemPresenter");
GridItemPresenter mGridPresenter = new GridItemPresenter();
ArrayObjectAdapter gridRowAdapter = new ArrayObjectAdapter(mGridPresenter);
gridRowAdapter.add("ITEM 1");
gridRowAdapter.add("ITEM 2");
gridRowAdapter.add("ITEM 3");
mRowsAdapter.add(new ListRow(gridItemPresenterHeader, gridRowAdapter));
/* set */
setAdapter(mRowsAdapter);
}
private class GridItemPresenter extends Presenter {
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent) {
TextView view = new TextView(parent.getContext());
view.setLayoutParams(new ViewGroup.LayoutParams(GRID_ITEM_WIDTH, GRID_ITEM_HEIGHT));
view.setFocusable(true);
view.setFocusableInTouchMode(true);
view.setBackgroundColor(getResources().getColor(R.color.default_background));
view.setTextColor(Color.WHITE);
view.setGravity(Gravity.CENTER);
return new ViewHolder(view);
}
@Override
public void onBindViewHolder(ViewHolder viewHolder, Object item) {
((TextView) viewHolder.view).setText((String) item);
}
@Override
public void onUnbindViewHolder(ViewHolder viewHolder) {
}
}
}
なお、MainFragmentの背景色として
<color name="default_background">#3d3d3d</color>
をcolor.xmlに追加した。
(III) ビルド・実行
ヘッダーとコンテンツの組み合わせが見えればOK。


1点注意。ここでは、 Presenterクラスと実際に表示したいアイテム(ただのString)を用意しただけだった。その他のアニメーション(アイテムを選択すると大きくなって表示される)などはすべてSDKのなかで実装されているので、特に手間をかけなくても大丈夫なようになっている。
UIデザイナーでなくても、 Android TV アプリのUIは
ここまでのソースコード: github.
次の章では How to use Presenter and ViewHolder? – Android TV application hands on tutorial 3、 ImageCardView を用いてカードに画像とタイトル・サブタイトルを載せた表示を行う CardPresenter の実装をする。
