LoginSignup
14
8

More than 5 years have passed since last update.

AndroidTV leanbackライブラリ 導入

Last updated at Posted at 2016-11-14

leanbackライブラリとは

  • Android Support Library の1つ
  • TV アプリ開発において Google のガイドラインに則した標準的な UI/UX を実現する

screen.png

登場人物

ss_20161114_151245.png

幾つかのパッケージに大きく分かれているが利用側が意識して使うものはappwidgetに纏められている

app

  • Fragment
    • leanback の UI は Fragment 単位で利用するため基本となる
  • それぞれについて Fragment と SupportFragment の両方が用意されているため実質的にはここにある半分くらいの種類がある
  • Baseクラスなども含まれるため主に使うものは数種類のみ

ss_20161114_151305.png

widget

大量にあるが理解するべき主なもののみに注目するとこれらは以下に分類される
それぞれについての役割については次項以降で述べる

  • Adapter
  • Presenter
  • PresenterSelector
  • View
  • DataModel
    • Row
    • Action

ss_20161114_151601_comined.png

構造

screen.png

各 Fragment は View の繰り返し構造になっているのでイメージとしては RecyclerView に近い
RecyclerView での View のレンダリングを考えると、以下のようになっており Adapter の責務の範囲が広い

(図1)
leanback001.jpg

従って例えば 何種類かの異なった View を Data の種類に応じて出し分けしようとすると以下のように Adapter が肥大化していく

(図2)
leanback002.jpg

一方 leanback ではMVPモデルを採用しており View をレンダリングする部分については Presenter として Adapter から独立している

(図3)
leanback003.jpg

Data の種類を見て表示を切り替える役割についても PresenterSelector という専用のクラスが用意されている
よって Adapter の役割は以下の2点のみになる

  • あらゆる型の Data を受け取り、登録されている Observer に通知する
  • 受け取った Data を内部で保持し、保持したデータ集合に対する外部からの add/remove/replace などの操作を処理する

但し用意されている Adapter をそのまま使う事が殆どであるためこれらを利用者が意識して実装する事は殆どない
各 View 毎の表示内容を調整したい時には、各 View とその View を扱う Presenter のみに着目すれば良いため、スコープを狭く修正ができる

実装イメージ

Googleが用意している以下のサンプルコードを元にして、先ずは実装をイメージだけしてみる

ここでは Card を並べて垂直方向にスクロールしていく View を作りたいため、appパッケージの Framgent のうちVerticalGridFragmentを利用する

screen.png

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());
    }
}

纏め

今回はライブラリの中の登場人物の役割の理解と、実装をイメージする所まで
次回以降で詳細な実装について記述していきたい

14
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
14
8