ご存知のように、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);
}
}
}
開発作業でどのように使用しますか?
個人的に、私は 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
に固有のアクションを実行する責任があります。
ロジックが混在していないので、単一の責任クラスを使用するのが好きです。
2番目の例を使用すると、spaguettiコードをすばやく提出でき、null可能性をチェックしたい場合は、「すべて」をnull可能として宣言する必要があります。
私は両方を使用していますが、現在のタスクに適したものは何でも使用できます。私は単一責任の原則を尊重します。各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);
}
}
それはあなたが期待している答えではないかもしれませんが、ここに 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 を確認してください。
最初のものを使用します。
静的オブジェクトを宣言するためにコンパニオンオブジェクトを使用します。静的フィールドは、実装で使用します。
このプロジェクトは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)
}
}
2つ目は、ViewHoldersがリサイクルされると予期しない動作が発生するため、バグがあります。バインド中に可視性を変更することを検討しましたが、大量のビューを表示するには十分ではありません。 RecyclerView内のRecyclerはタイプごとにViewHoldersを格納するため、最初の方法の方がパフォーマンスが高くなります。
ここでは、動的メソッドディスパッチを使用できます。以下に私の考えを共有します。 //アクティビティコード
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);
}
}
プロジェクトのソース: ここにリンクの説明を入力
私はこのアプローチを集中的に使用しています: http://frogermcs.github.io/inject-everything-viewholder-and-dagger-2-example/ つまり:
onCreateViewHolder
を委任します。onBind
内の取得されたデータを使用して呼び出すことができるように、ベースビューホルダーの類似のonBindViewHolder
を定義します。getItemViewType
に応じて(instanceOf
またはフィールド値の比較により)ファクトリを選択します。どうして?
すべてのビューホルダーを他のアプリから明確に分離します。 googleのautofactory
を使用すると、すべてのビューホルダーに必要な依存関係を簡単に挿入できます。イベントを親に通知する必要がある場合は、新しいインターフェースを作成し、それを親ビュー(アクティビティ)に実装して、短剣で公開します。 (プロのヒント:プロバイダーからファクトリーを初期化する代わりに、必要な各アイテムのファクトリーがオートファクトリーから提供され、ダガーが提供するファクトリーに依存することを指定するだけです)。
これを+15のビューホルダーに使用し、新しく追加するたびに(getItemViewType
チェック)、アダプターは約3行成長するだけです。