web-dev-qa-db-ja.com

RecycleViewアダプターに複数のViewHolderタイプを実装する

それはおそらく問題ではなく議論です。

複数の型を実装する通常の方法

ご存知のように、RecyclerViewに複数の型を実装する場合は、複数のCustomViewHolderを拡張する必要がありますRecyclerView.ViewHolder

例として、

class TextViewHolder extends RecyclerView.ViewHolder{
    TextView textView;
}

class ImageViewHolder extends RecyclerView.ViewHolder{
    ImageView imageView;
}

次に、getItemViewTypeをオーバーライドする必要があります。onCreateViewHolderを作成して、TextViewHolderまたはImageViewHolderを構築します。

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (viewType == 0) {
        return new ImageViewHolder(mLayoutInflater.inflate(R.layout.item_image, parent, false));
    } else {
        return new TextViewHolder(mLayoutInflater.inflate(R.layout.item_text, parent, false));
    }
} 

上記のコードは正常ですが、別の方法があります。

別の方法

CustomViewHolderは1つで十分だと思います。

 class MultipleViewHolder extends RecyclerView.ViewHolder{
    TextView textView;
    ImageView imageView;

    MultipleViewHolder(View itemView, int type){
       if(type == 0){
         textView = (TextView)itemView.findViewById(xx);
       }else{
         imageView = (ImageView)itemView.findViewById(xx);
       }
    }
 }

開発作業でどのように使用しますか?

10
CoXier

個人的に、私は Yigit Boyarthis talk で提案されているアプローチが好きです(31:07まで早送り)。 getItemViewType()から定数intを返す代わりに、レイアウトIDを直接返します。これはintであり、一意であることが保証されています:

 _
    @Override
    public int getItemViewType(int position) {
        switch (position) {
            case 0:
                return R.layout.first;
            case 1:
                return R.layout.second;
            default:
                return R.layout.third;
        }
    }
_ 

これにより、onCreateViewHolder()に次の実装を含めることができます。

 _
    @Override
    public MyViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater inflater = LayoutInflater.from(parent.getContext());
        View view = inflater.inflate(viewType, parent, false);

        MyViewHolder holder = null;
        switch (viewType) {
            case R.layout.first:
                holder = new FirstViewHolder(view);
                break;
            case R.layout.second:
                holder = new SecondViewHolder(view);
                break;
            case R.layout.third:
                holder = new ThirdViewHolder(view);
                break;
        }
        return holder;
    }
_ 

ここで、MyViewHolderは抽象クラスです。

 _
    public static abstract class MyViewHolder extends RecyclerView.ViewHolder {

        public MyViewHolder(View itemView) {
            super(itemView);

            // perform action specific to all viewholders, e.g.
            // ButterKnife.bind(this, itemView);
        }

        abstract void bind(Item item);
    }
_ 

そしてFirstViewHolderは次のとおりです:

 _
    public static class FirstViewHolder extends MyViewHolder {

        @BindView
        TextView title;

        public FirstViewHolder(View itemView) {
            super(itemView);
        }

        @Override
        void bind(Item item) {
            title.setText(item.getTitle());
        }
    }
_ 

これにより、onBindViewHolder()が1行になります。

 _
    @Override
    public void onBindViewHolder(MyViewHolder holder, int position) {
        holder.bind(dataList.get(holder.getAdapterPosition()));
    }
_ 

したがって、各ViewHolderは分離されます。ここで、bind(Item)は、そのViewHolderに固有のアクションを実行する責任があります。

29
azizbekian

ロジックが混在していないので、単一の責任クラスを使用するのが好きです。

2番目の例を使用すると、spaguettiコードをすばやく提出でき、null可能性をチェックしたい場合は、「すべて」をnull可能として宣言する必要があります。

2
crgarridos

私は両方を使用していますが、現在のタスクに適したものは何でも使用できます。私は単一責任の原則を尊重します。各ViewHolderは1つのタスクを実行する必要があります。

アイテムタイプごとに異なるビューホルダーロジックがある場合-異なるビューホルダーを実装します。

いくつかの異なるアイテムタイプのビューを同じタイプにキャストしてチェックなしで使用できる場合(たとえば、リストヘッダーとリストフッターがシンプルだが異なるビューの場合)-異なるビューで同一のビューホルダーを作成する必要はありません。

それがポイントです。異なるロジック-異なるViewHolders。同じロジック-同じViewHolders。

ImageViewおよびTextViewの例。ビューホルダーにロジック(たとえば、値の設定)があり、ビュータイプごとに異なる場合は、それらを混在させないでください。

これは悪い例です:

class MultipleViewHolder extends RecyclerView.ViewHolder{
    TextView textView;
    ImageView imageView;

    MultipleViewHolder(View itemView, int type){
        super(itemView);
        if(type == 0){
            textView = (TextView)itemView.findViewById(xx);
        }else{
            imageView = (ImageView)itemView.findViewById(xx);
        }
    }

    void setItem(Drawable image){
        imageView.setImageDrawable(image);
    }

    void setItem(String text){
        textView.setText(text);
    }
}

ViewHoldersにロジックがなく、ビューを保持するだけの場合、単純なケースでは問題ない可能性があります。たとえば、この方法でビューをバインドすると、次のようになります。

@Override
public void onBindViewHolder(ItemViewHolderBase holder, int position) {
    holder.setItem(mValues.get(position), position);
    if (getItemViewType(position) == 0) {
        holder.textView.setText((String)mItems.get(position));
    } else {
        int res = (int)mItems.get(position);
        holder.imageView.setImageResource(res);
    }
}
1
babay

それはあなたが期待している答えではないかもしれませんが、ここに Epoxy を使用した例があります。

まず、モデルを定義します。

@EpoxyModelClass(layout = R.layout.header_view_model)
public abstract class HeaderViewModel extends EpoxyModel<TextView> {

    @EpoxyAttribute
    String title;

    @Override
    public void bind(TextView view) {
        super.bind(view);
        view.setText(title);
    }

}

@EpoxyModelClass(layout = R.layout.drink_view_model)
public abstract class DrinkViewModel extends EpoxyModel<View> {

    @EpoxyAttribute
    Drink drink;

    @EpoxyAttribute
    Presenter presenter;

    @Override
    public void bind(View view) {
        super.bind(view);

        final TextView title = view.findViewById(R.id.title);
        final TextView description = view.findViewById(R.id.description);

        title.setText(drink.getTitle());
        description.setText(drink.getDescription());
        view.setOnClickListener(v -> presenter.drinkClicked(drink));
    }

    @Override
    public void unbind(View view) {
        view.setOnClickListener(null);
        super.unbind(view);
    }

}

@EpoxyModelClass(layout = R.layout.food_view_model)
public abstract class FoodViewModel extends EpoxyModel<View> {

    @EpoxyAttribute
    Food food;

    @EpoxyAttribute
    Presenter presenter;

    @Override
    public void bind(View view) {
        super.bind(view);

        final TextView title = view.findViewById(R.id.title);
        final TextView description = view.findViewById(R.id.description);
        final TextView calories = view.findViewById(R.id.calories);

        title.setText(food.getTitle());
        description.setText(food.getDescription());
        calories.setText(food.getCalories());
        view.setOnClickListener(v -> presenter.foodClicked(food));
    }

    @Override
    public void unbind(View view) {
        view.setOnClickListener(null);
        super.unbind(view);
    }

}

次に、Controllerを定義します。

public class DrinkAndFoodController extends Typed2EpoxyController<List<Drink>, List<Food>> {

    @AutoModel
    HeaderViewModel_ drinkTitle;

    @AutoModel
    HeaderViewModel_ foodTitle;

    private final Presenter mPresenter;

    public DrinkAndFoodController(Presenter presenter) {
        mPresenter = presenter;
    }

    @Override
    protected void buildModels(List<Drink> drinks, List<Food> foods) {
        if (!drinks.isEmpty()) {
            drinkTitle
                    .title("Drinks")
                    .addTo(this);
            for (Drink drink : drinks) {
                new DrinkViewModel_()
                        .id(drink.getId())
                        .drink(drink)
                        .presenter(mPresenter)
                        .addTo(this);
            }
        }

        if (!foods.isEmpty()) {
            foodTitle
                    .title("Foods")
                    .addTo(this);
            for (Food food : foods) {
                new FoodViewModel_()
                        .id(food.getId())
                        .food(food)
                        .presenter(mPresenter)
                        .addTo(this);
            }
        }
    }
}

Controllerを初期化:

DrinkAndFodController mController = new DrinkAndFoodController(mPresenter);
mController.setSpanCount(1);

final GridLayoutManager layoutManager = new GridLayoutManager(getContext(), 1);
layoutManager.setSpanSizeLookup(mController.getSpanSizeLookup());
mRecyclerView.setLayoutManager(layoutManager);
mRecyclerView.setAdapter(mController.getAdapter());

そして最後に、次のように簡単にデータを追加できます。

final List<Drink> drinks = mManager.getDrinks();
final List<Food> foods = mManager.getFoods();
mController.setData(drinks, foods);

次のようなリストが表示されます。

Drinks
Drink 1
Drink 2
Drink 3
...
Foods
Food1
Food2
Food3
Food4
...

詳細については、 wiki を確認してください。

1
Benjamin

最初のものを使用します。

静的オブジェクトを宣言するためにコンパニオンオブジェクトを使用します。静的フィールドは、実装で使用します。

このプロジェクトはkotlinで作成されましたが、次のようにしてアダプターを実装しました。

/**
 * Created by Geert Berkers.
 */
class CustomAdapter(
    private val objects: List<Any>,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        const val FIRST_CELL          = 0
        const val SECOND_CELL         = 1
        const val THIRD_CELL          = 2
        const val OTHER_CELL          = 3

        const val FirstCellLayout     = R.layout.first_cell
        const val SecondCellLayout    = R.layout.second_cell
        const val ThirdCellLayout     = R.layout.third_cell
        const val OtherCellLayout     = R.layout.other_cell
    }

    override fun getItemCount(): Int  = 4

    override fun getItemViewType(position: Int): Int = when (position) {
        objects[0] -> FIRST_CELL
        objects[1] -> SECOND_CELL
        objects[2] -> THIRD_CELL
        else -> OTHER_CELL
    }

    override fun onCreateViewHolder(parent: ViewGroup?, viewType: Int): RecyclerView.ViewHolder {
        when (viewType) {

            FIRST_CELL -> {
                val view = inflateLayoutView(FirstCellLayout, parent)
                return FirstCellViewHolder(view)
            }

            SECOND_CELL -> {
                val view = inflateLayoutView(SecondCellLayout, parent)
                return SecondCellViewHolder(view)
            }

            THIRD_CELL -> {
                val view = inflateLayoutView(ThirdCellLayout, parent)
                return ThirdCellViewHolder(view)
            }

            else -> {
                val view = inflateLayoutView(OtherCellLayout, parent)
                return OtherCellViewHolder(view)
            }
        }
    }

    fun inflateLayoutView(viewResourceId: Int, parent: ViewGroup?, attachToRoot: Boolean = false): View =
        LayoutInflater.from(parent?.context).inflate(viewResourceId, parent, attachToRoot)

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder?, position: Int) {
        val itemViewTpe = getItemViewType(position)

        when (itemViewTpe) {

            FIRST_CELL -> {
                val firstCellViewHolder = holder as FirstCellViewHolder
                firstCellViewHolder.bindObject(objects[position])
            }

            SECOND_CELL -> {
                val secondCellViewHolder = holder as SecondCellViewHolder
                secondCellViewHolder.bindObject(objects[position])
            }

            THIRD_CELL -> {
                val thirdCellViewHolder = holder as ThirdCellViewHolder
                thirdCellViewHolder.bindObject(objects[position])
            }

            OTHER_CELL -> {
                // Do nothing. This only displays a view
            }
        }
    }
}

そして、これがViewHolderの例です。

class FirstCellViewHolder(view: View) : RecyclerView.ViewHolder(view) {

    fun bindMedication(object: Object) = with(object) {
        itemView.setOnClickListener {
            openObject(object)
        }
    }

    private fun openObject(object: Object) {
        val context = App.instance
        val intent = DisplayObjectActivity.intent(context, object)
        intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK
        context.startActivity(intent)
    }

}
0
Geert Berkers

2つ目は、ViewHoldersがリサイクルされると予期しない動作が発生するため、バグがあります。バインド中に可視性を変更することを検討しましたが、大量のビューを表示するには十分ではありません。 RecyclerView内のRecyclerはタイプごとにViewHoldersを格納するため、最初の方法の方がパフォーマンスが高くなります。

0
Tuby

ここでは、動的メソッドディスパッチを使用できます。以下に私の考えを共有します。 //アクティビティコード

public class MainActivity extends AppCompatActivity {

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    RecyclerView recyclerView = (RecyclerView) findViewById(R.id.recycler_view);
    recyclerView.setLayoutManager(new LinearLayoutManager(this));
    ArrayList<Object> dataList = new ArrayList<>();
    dataList.add("Apple");
    dataList.add("Orange");
    dataList.add("Cherry");
    dataList.add("Papaya");
    dataList.add("Grapes");
    dataList.add(100);
    dataList.add(200);
    dataList.add(300);
    dataList.add(400);
    ViewAdapter viewAdapter = new ViewAdapter(dataList);
    recyclerView.setAdapter(viewAdapter);

}

}

//アダプターコード

public class ViewAdapter extends RecyclerView.Adapter<BaseViewHolder> {
private ArrayList<Object> dataList;
public ViewAdapter(ArrayList<Object> dataList) {
    this.dataList = dataList;
}

@Override
public BaseViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    BaseViewHolder baseViewHolder;

    if(viewType == 0) {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_one,parent,false);
        baseViewHolder  = new ViewHolderOne(view);
    }else  {
        View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_two,parent,false);
        baseViewHolder  = new ViewHolderSecond(view);
    }
    return baseViewHolder;
}

@Override
public void onBindViewHolder(BaseViewHolder holder, int position) {
    holder.bindData(dataList.get(position));
}

@Override
public int getItemViewType(int position) {
    Object obj = dataList.get(position);
    int type = 0;
    if(obj instanceof Integer) {
        type = 0;
    }else if(obj instanceof String) {
        type = 1;
    }
    return type;
}

@Override
public int getItemCount() {
    return dataList != null ? dataList.size() : 0;
}

}

//ベースビューホルダーコード。

public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
    super(itemView);
}

public abstract void bindData(T data);

}

//ホルダー1のソースコードを表示します。

public class ViewHolderOne extends BaseViewHolder<Integer> {

private TextView txtView;
public ViewHolderOne(View itemView) {
    super(itemView);
    txtView = itemView.findViewById(R.id.txt_number);
}

@Override
public void bindData(Integer data) {
    txtView.setText("Number:" + data);
}

}

//ホルダー2を表示

public class ViewHolderSecond extends BaseViewHolder<String> {

private TextView textView;
public ViewHolderSecond(View itemView) {
    super(itemView);
    textView = itemView.findViewById(R.id.txt_string);
}

@Override
public void bindData(String data) {
    textView.setText("Text:" + data);
}

}

プロジェクトのソース: ここにリンクの説明を入力

0
user2851150

私はこのアプローチを集中的に使用しています: http://frogermcs.github.io/inject-everything-viewholder-and-dagger-2-example/ つまり:

  1. ビューホルダーファクトリのマップをアダプターに挿入します。
  2. 注入された工場にonCreateViewHolderを委任します。
  3. onBind内の取得されたデータを使用して呼び出すことができるように、ベースビューホルダーの類似のonBindViewHolderを定義します。
  4. getItemViewTypeに応じて(instanceOfまたはフィールド値の比較により)ファクトリを選択します。

どうして?

すべてのビューホルダーを他のアプリから明確に分離します。 googleのautofactoryを使用すると、すべてのビューホルダーに必要な依存関係を簡単に挿入できます。イベントを親に通知する必要がある場合は、新しいインターフェースを作成し、それを親ビュー(アクティビティ)に実装して、短剣で公開します。 (プロのヒント:プロバイダーからファクトリーを初期化する代わりに、必要な各アイテムのファクトリーがオートファクトリーから提供され、ダガーが提供するファクトリーに依存することを指定するだけです)。

これを+15のビューホルダーに使用し、新しく追加するたびに(getItemViewTypeチェック)、アダプターは約3行成長するだけです。

0
dominik4142