Components­Recycler­Adapter

RecyclerView で複数の view type や複雑なデータ構造を扱う

id:nobuoka / @nobuoka
関西モバイルアプリ研究会 #11

こんにちは!

  • はてな id:nobuoka
  • ソフトウェア開発者
    • モバイルアプリ開発 (Android、UWP)
    • Web 開発 (Scala、Java、TypeScript、Perl)
  • 仕事は Android アプリ 「はてなブックマーク」 開発
  • 今日は RecyclerView を使う際に便利な自作のライブラリを紹介

RecyclerView 使ってますよね?

Adapter がデータセットと View をつなぐ

Adapter の役割

単純なデータセット1 個や 2 個程度の view type で扱う分には普通に RecyclerView.Adapter のサブクラスを定義してやればよい。 (困ることはない。)

だがしかし……!!

問題 1 : 複数の view type を扱うのは面倒

  • 項目、view type、ViewHolder、binding 間の関係の記述が散らばる
    • getItemViewType メソッド
    • onCreateViewHolder メソッド
    • onBindViewHolder メソッド
  • 単一データセットでもそうだし、複数データセットの複雑な構造ではさらに

問題 2 : 表示用のデータ構造が複雑になるとメンテナンスが難しい

  • 複数のデータセットの内容を続けて画面上に表示
    • 「人気」 の項目と 「おすすめ」 の項目をセクション分けして表示、とか
  • ドメインの関心事以外の項目
    • 広告を途中に挿入
    • セクションヘッダとか 「もっと読む」 ボタンとか

これらの問題を解決する ComponentsRecyclerAdapter

  • 「はてなブックマーク」 Android アプリのために開発
  • 各データセットを扱う部分をコンポーネント化
  • 類似のライブラリと比較してプレゼンテーションのためのデータ構造が記述しやすい (と思ってる)
    • コンポーネント同士の組み合わせで構造を記述
  • View type と ViewHolder 生成処理の関係は ViewHolderFactoryRegistry に宣言的に記述
  • 表示用のデータ構造を Component で記述
    • 各項目の item view と binding 方法の指定は Component の役割

使い方

build.gradle に依存記述

repositories {
  jcenter()
}

dependencies {
  compile 'info.vividcode.android:components-recycler-adapter:0.1.0'
  compile 'com.android.support:recyclerview-v7:23.1.1'
}

ViewHolder (+ Factory) 定義

レイアウトファイルと ViewHolder のクラスを一対一で対応させるのがおすすめ

public class ContentViewHolder extends RecyclerView.ViewHolder {
  // こういう Factory も必要。 (この例では lambda 式を使用。)
  public static final ViewHolderFactory<ContentViewHolder> FACTORY =
      (parent) -> {
        TextView v = (TextView) LayoutInflater.from(parent.getContext())
            .inflate(R.layout.item_content, parent, false);
        return new ContentViewHolder(v);
      };

  public final TextView textView;

  public ContentViewHolder(TextView itemView) {
    super(itemView);
    textView = itemView;
  }
}

Binder 定義

特定の ViewHolder と特定の型の項目の binding 処理。

public class StringContentBinder
    implements Binder<ContentViewHolder, String> {
  // インスタンスを static フィールドで保持しておく必要はない。
  // (後で使うのでここでは定義してある。)
  public static final StringContentBinder INSTANCE =
      new StringContentBinder();

  @Override
  public void bindViewHolder(
      ContentViewHolder h, Component<String> c, int posInComponent) {
    h.textView.setText(c.getItem(posInComponent);
  }
}

Adapter 定義

Adapter は ComponentsRecyclerAdapter を継承して、以下の内容を実装する。

public class MyAdapter extends ComponentsRecyclerAdapter {

  // View type と ViewHolderFactory の関連付け
  // (ViewHolderFactoryRegistry)

  // コンポーネントの定義

  // コンストラクタで ViewHolderFactoryRegistry とコンポーネントを登録

}

View type と ViewHolderFactory の関連付け

public class MyAdapter extends ComponentsRecyclerAdapter {

  public static final ViewTypes VIEW_TYPES = new ViewTypes();
  public static class ViewTypes extends ViewHolderFactoryRegistry {
    // ContentViewHolder を生成する Factory を登録し、
    // その view type をフィールド変数に保持
    public final ViewType<ContentViewHolder> content =
        register(ContentViewHolder.FACTORY);
    // ...
    // (同じようにして複数の Factory を登録して view type を変数に保持する)
  }

宣言的に結び付きを記述。

コンポーネントの定義

public class MyAdapter extends ComponentsRecyclerAdapter {
  // ...

  // 文字列のリストを参照するコンポーネント。
  private final ListReferenceComponent<String> mStringListComponent =
      ListReferenceComponent.create(
        // データセット内の各項目の view type と binder を指定。
        // (ここでは、全ての項目について view type を `content` に、
        // binder を `StringContentBinder` にしている。)
        new FixedViewTypeBinderPairProvider<>(
            VIEW_TYPES.content,
            StringContentBinder.INSTANCE)
      );
  // ...
  // (表示用に複雑なデータ構造が必要な場合は、同様にして
  // コンポーネントを定義していく。)

各項目の view type と binder の指定はここで。

コンストラクタ

public class MyAdapter extends ComponentsRecyclerAdapter {
  // ...

  public MyAdapter(List<String> list) {
    // スーパークラスのコンストラクタに ViewHolderFactoryRegistry を渡す。
    super(VIEW_TYPES);

    // コンポーネントにリストをセット。
    mStringListComponent.setList(list);
    // ルートコンポーネントとして文字列のリストを参照する
    // コンポーネントをセット。
    setComponent(mStringListComponent);
    // ...
    // (複雑なデータ構造が必要ならここで作る。)
  }

ぜひご利用ください

https://github.com/nobuoka/ComponentsRecyclerAdapter