leanbackライブラリとは
- Android Support Library の1つ
- TV アプリ開発において Google のガイドラインに則した標準的な UI/UX を実現する
登場人物
幾つかのパッケージに大きく分かれているが利用側が意識して使うものはapp
とwidget
に纏められている
app
- Fragment
- leanback の UI は Fragment 単位で利用するため基本となる
- それぞれについて Fragment と SupportFragment の両方が用意されているため実質的にはここにある半分くらいの種類がある
- Baseクラスなども含まれるため主に使うものは数種類のみ
widget
大量にあるが理解するべき主なもののみに注目するとこれらは以下に分類される
それぞれについての役割については次項以降で述べる
- Adapter
- Presenter
- PresenterSelector
- View
- DataModel
- Row
- Action
構造
各 Fragment は View の繰り返し構造になっているのでイメージとしては RecyclerView に近い
RecyclerView での View のレンダリングを考えると、以下のようになっており Adapter の責務の範囲が広い
従って例えば 何種類かの異なった View を Data の種類に応じて出し分けしようとすると以下のように Adapter が肥大化していく
一方 leanback ではMVPモデルを採用しており View をレンダリングする部分については Presenter として Adapter から独立している
Data の種類を見て表示を切り替える役割についても PresenterSelector という専用のクラスが用意されている
よって Adapter の役割は以下の2点のみになる
- あらゆる型の Data を受け取り、登録されている Observer に通知する
- 受け取った Data を内部で保持し、保持したデータ集合に対する外部からの add/remove/replace などの操作を処理する
但し用意されている Adapter をそのまま使う事が殆どであるためこれらを利用者が意識して実装する事は殆どない
各 View 毎の表示内容を調整したい時には、各 View とその View を扱う Presenter のみに着目すれば良いため、スコープを狭く修正ができる
実装イメージ
Googleが用意している以下のサンプルコードを元にして、先ずは実装をイメージだけしてみる
ここでは Card を並べて垂直方向にスクロールしていく View を作りたいため、app
パッケージの Framgent のうちVerticalGridFragment
を利用する
leanback の Fragment を利用する際には常に以下のようなイメージになる
public class MyFragment extends VerticalGridFragment {
(略)
setAdapter(Adapter);
Adapter#add(DataA, DataB, DataC);
繰り返しの単純なデータを扱うために Adapter には一般的には ArrayObjectAdapter を用いる事が多い
また図3
にあるように Adapter は PresenterSelector を内包するためこれを Adapter に渡してやる
すると上のイメージは以下のようになる
ArrayObjectAdapter adapter = new ArrayObjectAdapter(PresenterSelector);
setAdapter(adapter);
adapter.add(DataA, DataB, DataC);
実際のソースコード
上でのイメージを踏まえて実際のソースコードを見てみる
PresenterSelector cardPresenterSelector = new CardPresenterSelector(getActivity());
ArrayObjectAdapter mAdapter = new ArrayObjectAdapter(cardPresenterSelector);
setAdapter(mAdapter);
String json = Utils.inputStreamToString(getResources().openRawResource(R.raw.grid_example));
CardRow row = new Gson().fromJson(json, CardRow.class);
mAdapter.addAll(0, row.getCards());
PresenterSelector の中で Data の種類によって Presenter を出し分けしている (簡単のため一部省略)
public class CardPresenterSelector extends PresenterSelector {
@Override
public Presenter getPresenter(Object item) {
Card card = (Card) item;
switch (card.getType()) {
case SINGLE_LINE:
presenter = new SingleLineCardPresenter(mContext);
break;
case VIDEO_GRID:
presenter = new VideoCardViewPresenter(mContext, R.style.VideoGridCardTheme);
break;
case MOVIE:
case MOVIE_BASE:
case MOVIE_COMPLETE:
case SQUARE_BIG:
case GRID_SQUARE:
case GAME: {
presenter = new ImageCardViewPresenter(mContext, themeResId);
break;
}
case SIDE_INFO:
presenter = new SideInfoCardPresenter(mContext);
break;
case TEXT:
presenter = new TextCardPresenter(mContext);
break;
case ICON:
presenter = new IconCardPresenter(mContext);
break;
case CHARACTER:
presenter = new CharacterCardPresenter(mContext);
break;
default:
presenter = new ImageCardViewPresenter(mContext);
break;
return presenter;
}
}
最後に各 Presenter の中で View をレンダリングしている (簡単のため一部省略)
public class ImageCardViewPresenter extends AbstractCardPresenter<ImageCardView> {
@Override
protected ImageCardView onCreateView() {
ImageCardView imageCardView = new ImageCardView(getContext());
return imageCardView;
}
@Override
public void onBindViewHolder(Card card, final ImageCardView cardView) {
cardView.setTag(card);
cardView.setTitleText(card.getTitle());
cardView.setContentText(card.getDescription());
int resourceId = getContext().getResources().getIdentifier(card.getLocalImageResourceName(), "drawable", getContext().getPackageName())
Picasso.with(getContext()).load(resourceId).into(cardView.getMainImageView());
}
}
纏め
今回はライブラリの中の登場人物の役割の理解と、実装をイメージする所まで
次回以降で詳細な実装について記述していきたい