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 の集合で RowsAdapter が構成される。これが BrowseFragment のメインコンテンツとなる。
ListRow
部分に注目してみよう。各コンテンツ (以降 CardInfo や アイテム と表記)は ArrayObjectAdapter
の中に格納される(ここでは RowAdapter
と表記)。
このアイテムはどんなクラス、オブジェクトでもよくそのUI表示は Presenter
クラスによって指定される。
ListRowの構成
まとめ
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
の実装をする。