在RecycleView适配器中实现多个ViewHolder类型

这可能是一个讨论不是一个问题。

正常的方式来实现多种类型

如你所知,如果我们想要在RecyclerView实现多种类型,我们应该提供多个CustomViewHolder扩展RecyclerView.ViewHolder

例如,

 class TextViewHolder extends RecyclerView.ViewHolder{ TextView textView; } class ImageViewHolder extends RecyclerView.ViewHolder{ ImageView imageView; } 

然后我们必须重写getItemViewType 。并在onCreateViewHolder中构造TextViewHolderImageViewHolder

 @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就足够了。

  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 Boyar在这个演讲中提出的方法(快进到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; } }
@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; }
@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, eg // ButterKnife.bind(this, itemView); } abstract void bind(Item item); }
public static abstract class MyViewHolder extends RecyclerView.ViewHolder { public MyViewHolder(View itemView) { super(itemView); // perform action specific to all viewholders, eg // 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()); } }
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()成为一线:

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

因此,每个ViewHolder分开的,其中bind(Item)将负责仅执行特定于该ViewHolder操作。

我喜欢使用单一的责任类,因为逻辑不混合。

使用第二个例子,你可以快速地打开spaguetti代码,如果你想检查可空性,你不得不声明“可以为空”。

我同时使用,无论对于当前的任务更好。 我尊重单一责任原则。 每个ViewHolder应该执行一项任务。

如果我对不同的项目类型有不同的查看者逻辑 – 我实现不同的查看者。

如果某些不同的项目类型的视图可以转换为相同的类型并且没有使用检查(例如,如果列表页眉和列表页脚是简单但不同的视图),那么在创建具有不同视图的相同视图持有者方面是没有意义的。

这才是重点。 不同的逻辑 – 不同的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 ... 

欲了解更多信息,你可以检查维基 。

第二个是越野车,因为当ViewHolders得到回收时,它会产生意想不到的行为。 我认为在绑定过程中改变了可见性,但对于大量的视图来说性能不够。 RecyclerView中的Recycler存储每种类型的ViewHolders,所以第一种方式更高效。

我有种使用第一个。

我使用伴随对象来声明我在实现中使用的静态字段。

这个项目是用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) } } 

在这里你可以使用Dynamic method dispatch。 下面我分享我的想法。 //活动代码

 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); 

}

//查看持有者一个源代码。

 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); } 

}

//查看持有者二

 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/总之:

  1. 将视图持有者工厂的映射注入适配器。
  2. 委托onCreateViewHolder注入工厂。
  3. 定义onBind类似的基本视图持有人,以便您可以调用onBindViewHolder检索到的数据。
  4. 根据getItemViewType (通过instanceOf或比较字段值)选择工厂。

为什么?

它干净地分离每个查看持有人从应用程序的其余部分。 如果你使用谷歌的autofactory ,你可以很容易地注入每个视图持有者所需的依赖关系。 如果你需要通知父母一些事件,只需创建新的接口,在父视图(活动)中实现它,并将其暴露在匕首。 (专家提示:不要从提供者初始化工厂,只需指定每个需要的工厂的工厂取决于工厂的自动工厂给你和匕首将提供给你)。

我们使用它为+15视图持有人和适配器只增加了约3线为每个新增( getItemViewType检查)。

我没有条件使用第二种方法,在列表中有100多个项目很好用。

 public class SafeHolder extends RecyclerView.ViewHolder { public final ImageView m_ivImage; public final ImageView m_ivRarity; public final TextView m_tvItem; public final TextView m_tvDesc; public final TextView m_tvQuantity; public SafeHolder(View itemView) { super(itemView); m_ivImage =(ImageView)itemView.findViewById(R.id.safeimage_id); m_ivRarity =(ImageView)itemView.findViewById(R.id.saferarity_id); m_tvItem = (TextView) itemView.findViewById(R.id.safeitem_id); m_tvDesc = (TextView) itemView.findViewById(R.id.safedesc_id); m_tvQuantity = (TextView) itemView.findViewById(R.id.safequantity_id); } }