Implement multiple ViewHolder types in RecycleView adapter

CoXier picture CoXier · Sep 24, 2017 · Viewed 8.3k times · Source

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?

Answer

azizbekian picture azizbekian · Sep 27, 2017

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.