问题
It's maybe a discussion not a question.
Normal way to implement multiple types
As you know, if we want to implement multiple types in RecyclerView
, we should provide multiple CustomViewHolder
extending RecyclerView.ViewHolder
.
For exmpale,
class TextViewHolder extends RecyclerView.ViewHolder{
TextView textView;
}
class ImageViewHolder extends RecyclerView.ViewHolder{
ImageView imageView;
}
Then we have to override getItemViewType
.And in onCreateViewHolder
to construct TextViewHolder
or 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));
}
}
Above code is normal but there is a another way.
Another way
I think only one CustomViewHolder
is enough.
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);
}
}
}
Which way do you use in your developing work?
回答1:
Personally I like approach suggested by Yigit Boyar in this talk (fast forward to 31:07). Instead of returning a constant int from getItemViewType()
, return the layout id directly, which is also an int and is guaranteed to be unique:
@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;
}
}
This will allow you to have following implementation in 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;
}
Where MyViewHolder
is an abstract class:
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);
}
And FirstViewHolder
is following:
public static class FirstViewHolder extends MyViewHolder {
@BindView
TextView title;
public FirstViewHolder(View itemView) {
super(itemView);
}
@Override
void bind(Item item) {
title.setText(item.getTitle());
}
}
This will make onBindViewHolder()
to be one-liner:
@Override
public void onBindViewHolder(MyViewHolder holder, int position) {
holder.bind(dataList.get(holder.getAdapterPosition()));
}
Thus, you'd have each ViewHolder
separated, where bind(Item)
would be responsible to perform actions specific to that ViewHolder
only.
回答2:
I like to use single responsability classes, as logic is not mixed.
Using the second example, you can quickly turn in spaguetti code, and if you like to check nullability, you are forced to declare "everything" as nullable.
回答3:
I use both, whatever is better for current task. I do respect the Single Responcibility principle. Each ViewHolder should do one task.
If I have different view holder logic for different item types - I implement different view holders.
If views for some different item types can be cast to same type and used without checks (for example, if list header and list footer are simple but different views) -- there is no sence in creating identical view holders with different views.
That's the point. Different logic - different ViewHolders. Same logic - same ViewHolders.
The ImageView and TextView example. If your view holder has some logic (for example, setting value) and it is different for different view types -- you should not mix them.
This is bad example:
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);
}
}
If your ViewHolders don't have any logic, just holding views, it might be OK for simple cases. for example, if you bind views this way:
@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);
}
}
回答4:
It might not be the answer you're expecting but here is an example using Epoxy, which really makes your life easier:
First you define your models:
@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);
}
}
Then you define your 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);
}
}
}
}
Initialize your 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());
And finally you can add your data as easily as this:
final List<Drink> drinks = mManager.getDrinks();
final List<Food> foods = mManager.getFoods();
mController.setData(drinks, foods);
You'll have a list thats looks like:
Drinks
Drink 1
Drink 2
Drink 3
...
Foods
Food1
Food2
Food3
Food4
...
For more informations you can check the wiki.
回答5:
Second one is buggy because when ViewHolders get recycled it produces unexpected behavior. I considered changing visibility during binding but it isn't performant enough for large amount of Views. Recycler inside RecyclerView stores ViewHolders per type so first way is more performant.
回答6:
I kind of use the first one.
I use a companion object to declare the static fields, which I use in my implementation.
This project was written in kotlin, but here is how I implemented an Adapter:
/**
* 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
}
}
}
}
And here is an example of a 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)
}
}
回答7:
Here you can use Dynamic method dispatch. Below i share my idea. //Activity Code
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);
}
}
//Adapter code
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;
}
}
//Base View Holder Code.
public abstract class BaseViewHolder<T> extends RecyclerView.ViewHolder {
public BaseViewHolder(View itemView) {
super(itemView);
}
public abstract void bindData(T data);
}
//View Holder One Source Code.
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);
}
}
//View Holder Two
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);
}
}
For project Source: enter link description here
回答8:
I use this approach intensively: http://frogermcs.github.io/inject-everything-viewholder-and-dagger-2-example/ In short:
- Inject map of view holder factories into adapter.
- Delegate
onCreateViewHolder
to injected factories. - Define
onBind
on similar on base view holder so that you can call it with retrieved data inonBindViewHolder
. - Choose factory depending on
getItemViewType
(by eitherinstanceOf
or comparing field value).
Why?
It cleanly separates every view holder from the rest of app.
If you use autofactory
from google, you can easily inject dependencies required for every view holder. If you need to notify parent of some event, just create new interface, implement it in parent view (activity) and expose it in dagger. (pro tip: instead of initialising factories from their providers, simply specify that each required item's factory depends on factory that autofactory gives you and dagger will provide that for you).
We use it for +15 view holders and adapter has only to grow by ~3 lines for each new added (getItemViewType
checks).
回答9:
I use 2nd method without conditional, works great with 100+ items in list.
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);
}
}
来源:https://stackoverflow.com/questions/46390768/implement-multiple-viewholder-types-in-recycleview-adapter