LoginSignup
19
15

More than 5 years have passed since last update.

異なるアダプターを1つに繋げたいときのアプローチ

Last updated at Posted at 2016-05-06

はじめに

athornz氏がGistにMergeRecyclerAdapter.javaというのをあげていたので使ってみたけど、便利だったので紹介します。
なお僕が使っているバージョンは少し手を入れています。
MergeRecyclerAdapter.java(Cattaka ver)

MergeRecyclerAdapterの用途

1つのリストに複数の属性のデータを表示したいとき、1つのアダプターのデータクラスをObjectなどにして、インスタンスの型に合わせてRecyclerView.Adapter#getItemViewType(int)の戻り値を切り替えすなど、少々面倒なことをする必要がありました。この場合、表示するデータクラスの組み合わせが変わるたびにアダプターを作り変えることになり、結構なコストが掛かってしまいます。

MergeRecyclerAdapterを使えば、それぞれのデータクラス用のアダプターを個別に作り、それらを連結するということができるので、それらの煩わしさを解消することができます。また他のメリットとしてヘッダーやフッターを付けるのに使うこともできます。デメリットとしてはクリックイベントのハンドリングが少しコツがいるようになります。

ヘッダーとフッターをつける

ヘッダー用、データ用、フッター用の3つのアダプターを作り、MergeRecyclerAdapterを用いて繋げます。
ここではヘッダーとフッターには単独のレイアウトを表示するSingleViewAdapterを使っています。

Screenshot_20160506-160124.png

RecyclerViewHeaderExampleActivity.java より

{   // prepare adapters
    mMergeRecyclerAdapter = new MergeRecyclerAdapter<>(this);
    {   // create header adapter
        mHeaderAdapter = new SingleViewAdapter(this, R.layout.view_header);
        mHeaderAdapter.setOnItemClickListener(this);
        mHeaderAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mHeaderAdapter);
    }
    {   // create items adapter
        List<String> items = new ArrayList<>();
        for (int i = 0; i < 5; i++) {
            items.add("item " + i);
        }
        mItemsAdapter = new SimpleStringAdapter(this, items);
        mItemsAdapter.setOnItemClickListener(this);
        mItemsAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mItemsAdapter);
    }
    {   // create footer adapter
        mFooterAdapter = new SingleViewAdapter(this, R.layout.view_footer);
        mFooterAdapter.setOnItemClickListener(this);
        mFooterAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mFooterAdapter);
    }
    {
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mRecyclerView.setAdapter(mMergeRecyclerAdapter);
    }
}

サンプルコード全体はこちら

複数のデータ型を表示する

次の例はStringのデータとNumberのデータを1つのRecyclerViewに表示しています。
Stringのデータを表示するためにSimpleStringAdapterを、Numberのデータを表示するためにSimpleNumberAdapterを使っています。それぞれのヘッダーにはSingleViewAdapterを使っています。

Screenshot_20160506-151636.png

MultiAdapterExampleActivity.java より

{   // prepare adapters
    mMergeRecyclerAdapter = new MergeRecyclerAdapter<>(this);
    {   // create strings header adapter
        mStringsHeaderAdapter = new SingleViewAdapter(this, R.layout.view_header_string);
        mStringsHeaderAdapter.setOnItemClickListener(this);
        mStringsHeaderAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mStringsHeaderAdapter);
    }
    {   // create strings adapter
        List<String> items = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            items.add("item " + i);
        }
        mStringsAdapter = new SimpleStringAdapter(this, items);
        mStringsAdapter.setOnItemClickListener(this);
        mStringsAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mStringsAdapter);
    }
    {   // create numbers header adapter
        mNumbersHeaderAdapter = new SingleViewAdapter(this, R.layout.view_header_number);
        mNumbersHeaderAdapter.setOnItemClickListener(this);
        mNumbersHeaderAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mNumbersHeaderAdapter);
    }
    {   // create numbers adapter
        List<Number> items = new ArrayList<>();
        for (int i = 0; i < 3; i++) {
            items.add(i);
        }
        mNumbersAdapter = new SimpleNumberAdapter(this, items);
        mNumbersAdapter.setOnItemClickListener(this);
        mNumbersAdapter.setOnItemLongClickListener(this);
        mMergeRecyclerAdapter.addAdapter(mNumbersAdapter);
    }
    {
        mRecyclerView.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false));
        mRecyclerView.setAdapter(mMergeRecyclerAdapter);
    }
}

サンプルコード全体はこちら

クリックイベントのハンドリング

MergeRecyclerAdapterは性質上、複数のアダプターが同居することになるので「何番目の要素がクリックされたときのイベント」をハンドリングするときに、どのアダプターの要素なのか識別することが必要です。その仕組みはMergeRecyclerAdapter#getAdapterOffsetForItem(int)として提供されています。このメソッドは戻り値としてLocalAdapterを返します。LocalAdapterからはオリジナルのアダプターへの参照とそのアダプター内のポジション(インデックス)が取得できます。

僕がクリックイベントをハンドリングするときは自作のCustomRecyclerAdapter.javaと組み合わせて、以下のようなコードでハンドリングしています(CustomRecyclerAdapter.javaについては後述の余談で解説します)。

MultiAdapterExampleActivity.java より

@Override
public void onItemClick(RecyclerView parent, CustomRecyclerAdapter adapter, int position,
                        int id, RecyclerView.ViewHolder vh) {
    if (parent.getId() == R.id.recycler) {
        MergeRecyclerAdapter.LocalAdapter la = mMergeRecyclerAdapter.getAdapterOffsetForItem(position);
        if (la.mAdapter == mStringsHeaderAdapter) {
            Toast.makeText(this, "Strings Header is clicked.", Toast.LENGTH_SHORT).show();
        } else if (la.mAdapter == mStringsAdapter) {
            String item = mStringsAdapter.getItemAt(la.mLocalPosition);
            Toast.makeText(this, item + " is clicked.", Toast.LENGTH_SHORT).show();
        } else if (la.mAdapter == mNumbersHeaderAdapter) {
            Toast.makeText(this, "Numbers Header is clicked.", Toast.LENGTH_SHORT).show();
        } else if (la.mAdapter == mNumbersAdapter) {
            Number item = mNumbersAdapter.getItemAt(la.mLocalPosition);
            Toast.makeText(this, item + " is clicked.", Toast.LENGTH_SHORT).show();
        } else if (la.mAdapter == mFooterAdapter) {
            Toast.makeText(this, "Footer is clicked.", Toast.LENGTH_SHORT).show();
        }
    }
}

サンプルコード全体はこちら

余談

ヘッダーやフッターにわざわざSingleViewAdapterを使う件

僕がヘッダーやフッターを作るときは、わざわざレイアウトXMLを作らなくてはいけないSingleViewAdapterを使っています。正直なところ面倒です。文字列を渡したら表示するだけのアダプターを作っておいて、それを使えばいちいちレイアウトXMLを作らなくても済みます。
でもデザイナさんに外観の調整をお願いしてると、外観を個別に調整したいという声が出てきます。そういった声に対応できるように意図してヘッダーやフッターのレイアウトXMLを分けています。

CustomRecyclerAdapter.javaを噛ませてる件

RecyclerViewには作りの都合上、ListView(というかAdapterView)のOnItemClickListenerOnItemLongClickListenerがありません。だからといって個別にListener用のinterfaceを定義して、そのイベントの引き回しを実装していると面倒くさいです。その辺りの兼ね合いからCustomRecyclerAdapterを使うようにしています。

ただ正直使うときに若干クセがあり、onCreateViewHolderの実装に注意する必要があるので、万人にお薦めできるものでは無いです。onCreateViewHolder内でクリックイベントをハンドリングしたいViewに対して、setTag/setOnClickListenerを呼んであげないといけないです。

サンプルアプリ

19
15
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
19
15