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
の実装をする。