Android
qnoteDay 16

MergeRecyclerAdapter使って、複雑なデータ構造のRecyclerViewを簡単に実装しよう

さてMergeRecyclerAdapterを知っていますか?
これはRecyclerViewで使うRecyclerView.Adapterを拡張したものです。
https://github.com/cattaka/AdapterToolbox/blob/master/adapter-toolbox/src/main/java/net/cattaka/android/adaptertoolbox/thirdparty/MergeRecyclerAdapter.java

確か1年位前に見つけ、今でもバリバリ使っています。
実際にどのように使うのかお伝えしたいと思います。

導入

MergeRecyclerAdapterはGitHabで公開されております。
ライセンスもapache2.0なので問題いなく使えますね。
僕の場合は直接ファイルを直接取り込んで使っています。

        RecyclerView recyclerView = ((RecyclerView) findViewById(R.id.list));
        recyclerView.setLayoutManager(new LinearLayoutManager(this));

        MergeRecyclerAdapter<BaseAdapter> mergeRecyclerAdapter = new MergeRecyclerAdapter<>(this);

        mergeRecyclerAdapter.addAdapter(new HeaderAdapter("aHeader"));
        AAdapter aAdapter = new AAdapter(aList, "aTag");
        mergeRecyclerAdapter.addAdapter(aAdapter);
        mergeRecyclerAdapter.addAdapter(new FooterAdapter("aFooter"));

        mergeRecyclerAdapter.addAdapter(new HeaderAdapter("bHeader"));
        BAdapter bAdapter = new BAdapter(bList, "bTag");
        mergeRecyclerAdapter.addAdapter(bAdapter);
        mergeRecyclerAdapter.addAdapter(new FooterAdapter("bFooter"));

        mergeRecyclerAdapter.addAdapter(new HeaderAdapter("cHeader"));
        AAdapter cAdapter = new AAdapter(cList, "cTag");
        mergeRecyclerAdapter.addAdapter(cAdapter);
        mergeRecyclerAdapter.addAdapter(new FooterAdapter("cFooter"));
        recyclerView.setAdapter(mergeRecyclerAdapter);

簡単に書くとこのような感じです。
A要素B要素C要素があり、それぞれの塊ごとにheader,footerを持つリストを作っています。
A要素とC要素は同じデータ構造をしています。

HeaderAdapter、FooterAdapter、A要素を表示するAAdapter、B要素を表示するBAdapterを作成しておきます。
C要素に関してはA要素と同じデータ構造なのでAAdapterを使いまわします。
これらのadapterはBaseAdapterというTagを保持できるRecyclerView.Adapterを継承しています。

まず各adapterを生成してMergeRecyclerAdapterに追加していきます。
そしてMergeRecyclerAdapterをrecyclerViewにセットしてあげます。

これで表示までは完了です。
とても簡単ですね。

リストにクリックに関してはDataBinding+ViewModelを用いて行のレイアウトに直接onClickListenerを設定し、EventBusにてActivityやFragmentに通知を行いハンドリングを行っています。

メリット

使った上で気がついたメリットを何点かあげます。

Adapterが大きくならない
 1つのadapter内で別のデータを扱わないので、データの型の確認をしてlayoutやviewModel指定しなくてよい
汎用性が増す
 データの型ごとにadapterを作るので、1画面1adapterにならず、adapterの使い回しが容易
複数の配列を組み合わせる時のpositionを考慮しなくて良い
 adapterごとにpositionが0から取れるのでIndexOutOfBoundsExceptionが起きにくい
全件分のデータ取得を待つ必要がない
 adapterごとにデータがあればいいので、複数APIでデータを取得するとしても取得したところから表示ができる
 その場合は先にadapterを生成してMergeRecyclerAdapterに渡し、データ取得後にnotifyDataSetChanged()などで変更を通知すれば良い

デメリット

クラスの数がとても増える
 これがデメリットになるかは毎度問題になりますが、しいて挙げるならこれくらいになるでしょうか。

まとめ

サンプルでは各adapterがtagを持っていますが実際には使う機会は少ないです。
例えばpagingをする必要があり、なおかつpagingしたブロックごとに何かをしないといけない場合とか。
adapterの持つデータに関して何か行う場合に、同じ型のadapterが複数ある可能性があるのでその時の対応策です。

これを使って思ったのがiOSのtableViewのセクションと同じような動作をさせられること
分割してデータを入れられたり簡単にheder,footerが入れられたり、メリットが沢山あるものです。

ご覧の通りAdvent Calendar 2017の記事になるのですが、本当はGoogle Homeのことを書きたかったのですが、思ったとおりに動かすことができず、断念。
急遽この内容に変更しました。
近いうちにつまずいたところも含めGoogle Homeの記事を書きたいなと思っています。