By Pongpat


2014-10-07 20:55:30 8 Comments

From https://developer.android.com/preview/material/ui-widgets.html

When we create RecyclerView.Adapter we have to specify ViewHolder that will bind with the adapter.

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {

    private String[] mDataset;

    public MyAdapter(String[] myDataset) {
        mDataset = myDataset;
    }

    public static class ViewHolder extends RecyclerView.ViewHolder {
        public TextView mTextView;
        public ViewHolder(TextView v) {
            super(v);
            mTextView = v;
        }
    }

    @Override
    public MyAdapter.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.some_layout, parent, false);

        //findViewById...

        ViewHolder vh = new ViewHolder(v);
        return vh;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.mTextView.setText(mDataset[position]);
    }

    @Override
    public int getItemCount() {
        return mDataset.length;
    }
}

So, is it possible to create RecyclerView with multiple view types?

12 comments

@Rohit Singh 2018-07-03 11:07:48

Create different ViewHolder for different layout

enter image description here
RecyclerView can have any number of viewholders you want but for better readability lets see how to create one with two ViewHolders.

It can be done in three simple steps

  1. Override public int getItemViewType(int position)
  2. Return different ViewHolders based on the ViewType in onCreateViewHolder() method
  3. Populate View based on the itemViewType in onBindViewHolder() method

Here is a small code snippet

public class YourListAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

   private static final int LAYOUT_ONE= 0;
   private static final int LAYOUT_TWO= 1;

   @Override
   public int getItemViewType(int position)
   {
      if(position==0)
        return LAYOUT_ONE;
      else
        return LAYOUT_TWO;
   }

   @Override
   public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

      View view =null;
      RecyclerView.ViewHolder viewHolder = null;

      if(viewType==LAYOUT_ONE)
      {
          view = LayoutInflater.from(parent.getContext()).inflate(R.layout.one,parent,false);
          viewHolder = new ViewHolderOne(view);
      }
      else
      {
          view = LayoutInflater.from(parent.getContext()).inflate(R.layout.two,parent,false);
          viewHolder= new ViewHolderTwo(view);
      }

      return viewHolder;
   }

   @Override
   public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {

      if(holder.getItemViewType()== LAYOUT_ONE)
      {
            // Typecast Viewholder 
            // Set Viewholder properties 
            // Add any click listener if any 
      }
      else {

        ViewHolderOne vaultItemHolder = (ViewHolderOne) holder;
        vaultItemHolder.name.setText(displayText);
        vaultItemHolder.name.setOnClickListener(new View.OnClickListener() {
           @Override
           public void onClick(View v) {
            .......
           }
         });

       }

   }

  //****************  VIEW HOLDER 1 ******************//

   public class ViewHolderOne extends RecyclerView.ViewHolder {

      public TextView name;

      public ViewHolderOne(View itemView) {
         super(itemView);
         name = (TextView)itemView.findViewById(R.id.displayName);
     }
   }


   //****************  VIEW HOLDER 2 ******************//

   public class ViewHolderTwo extends RecyclerView.ViewHolder{

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

        ..... Do something
      }
   }
}

getItemViewType(int position) is the key

In my opinion,the starting point to create this kind of recyclerView is the knowledge of this method. Since this method is optional to override therefore it is not visible in RecylerView class by default which in turn makes many developers(including me) wonder where to begin. Once you know that this method exists, creating such RecyclerView would be a cakewalk.

Lets see one example to prove my point. If you want to show two layout at alternate positions do this

@Override
public int getItemViewType(int position)
{
   if(position%2==0)       // Even position 
     return LAYOUT_ONE;
   else                   // Odd position 
     return LAYOUT_TWO;
}

Relevant Links:

Check out the project where I have implemented this

@Link182 2018-12-22 14:53:21

View types implementation becomes easier with kotlin, here is a sample with this light library https://github.com/Link184/KidAdapter

recyclerView.setUp {
    withViewType {
        withLayoutResId(R.layout.item_int)
        withItems(mutableListOf(1, 2, 3, 4, 5, 6))
        bind<Int> { // this - is adapter view hoder itemView, it - current item
            intName.text = it.toString()
        }
    }


    withViewType("SECOND_STRING_TAG") {
        withLayoutResId(R.layout.item_text)
        withItems(mutableListOf("eight", "nine", "ten", "eleven", "twelve"))
        bind<String> {
            stringName.text = it
        }
    }
}

@Sayan Manna 2018-04-03 11:48:04

Yes, it is possible. In your adapter getItemViewType Layout like this ....

  public class MultiViewTypeAdapter extends RecyclerView.Adapter {

        private ArrayList<Model>dataSet;
        Context mContext;
        int total_types;
        MediaPlayer mPlayer;
        private boolean fabStateVolume = false;

        public static class TextTypeViewHolder extends RecyclerView.ViewHolder {

            TextView txtType;
            CardView cardView;

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

                this.txtType = (TextView) itemView.findViewById(R.id.type);
                this.cardView = (CardView) itemView.findViewById(R.id.card_view);
            }
        }

        public static class ImageTypeViewHolder extends RecyclerView.ViewHolder {

            TextView txtType;
            ImageView image;

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

                this.txtType = (TextView) itemView.findViewById(R.id.type);
                this.image = (ImageView) itemView.findViewById(R.id.background);
            }
        }

        public static class AudioTypeViewHolder extends RecyclerView.ViewHolder {

            TextView txtType;
            FloatingActionButton fab;

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

                this.txtType = (TextView) itemView.findViewById(R.id.type);
                this.fab = (FloatingActionButton) itemView.findViewById(R.id.fab);
            }
        }

        public MultiViewTypeAdapter(ArrayList<Model>data, Context context) {
            this.dataSet = data;
            this.mContext = context;
            total_types = dataSet.size();
        }

        @Override
        public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {

            View view;
            switch (viewType) {
                case Model.TEXT_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.text_type, parent, false);
                    return new TextTypeViewHolder(view);
                case Model.IMAGE_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.image_type, parent, false);
                    return new ImageTypeViewHolder(view);
                case Model.AUDIO_TYPE:
                    view = LayoutInflater.from(parent.getContext()).inflate(R.layout.audio_type, parent, false);
                    return new AudioTypeViewHolder(view);
            }
            return null;
        }

        @Override
        public int getItemViewType(int position) {

            switch (dataSet.get(position).type) {
                case 0:
                    return Model.TEXT_TYPE;
                case 1:
                    return Model.IMAGE_TYPE;
                case 2:
                    return Model.AUDIO_TYPE;
                default:
                    return -1;
            }
        }

        @Override
        public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int listPosition) {

            Model object = dataSet.get(listPosition);
            if (object != null) {
                switch (object.type) {
                    case Model.TEXT_TYPE:
                        ((TextTypeViewHolder) holder).txtType.setText(object.text);

                        break;
                    case Model.IMAGE_TYPE:
                        ((ImageTypeViewHolder) holder).txtType.setText(object.text);
                        ((ImageTypeViewHolder) holder).image.setImageResource(object.data);
                        break;
                    case Model.AUDIO_TYPE:

                        ((AudioTypeViewHolder) holder).txtType.setText(object.text);

                }
            }
        }

        @Override
        public int getItemCount() {
            return dataSet.size();
        }
    }

for reference link : https://www.journaldev.com/12372/android-recyclerview-example

@user462990 2019-01-04 16:03:26

I reformatted my code to mimic this snippet and it now works perfectly. The problem I was having was that on swipe beyond the current page it would crash. Crash no More ! Excellent model,. Thank you . Well done.

@Vitaly 2017-01-17 08:12:09

You can use the library: https://github.com/vivchar/RendererRecyclerViewAdapter

mRecyclerViewAdapter = new RendererRecyclerViewAdapter(); /* included from library */
mRecyclerViewAdapter.registerRenderer(new SomeViewRenderer(SomeModel.TYPE, this));
mRecyclerViewAdapter.registerRenderer(...); /* you can use several types of cells */

`

For each item, you should to implement a ViewRenderer, ViewHolder, SomeModel:

ViewHolder - it is a simple view holder of recycler view.

SomeModel - it is your model with ItemModel interface

public class SomeViewRenderer extends ViewRenderer<SomeModel, SomeViewHolder> {

  public SomeViewRenderer(final int type, final Context context) {
    super(type, context);
  }
  @Override
 public void bindView(@NonNull final SomeModel model, @NonNull final SomeViewHolder holder) {
    holder.mTitle.setText(model.getTitle());
 }
 @NonNull
 @Override
 public SomeViewHolder createViewHolder(@Nullable final ViewGroup parent) {
    return new SomeViewHolder(LayoutInflater.from(getContext()).inflate(R.layout.some_item, parent, false));
 }
}

For more details you can look documentations.

@Tulsi 2017-01-02 14:01:39

It is very simple and straight forward.

Just Override getItemViewType() method in your adapter. On the basis of data return different itemViewType values. e.g Consider an object of type Person with a member isMale, if isMale is true, return 1 and isMale is false, return 2 in getItemViewType() method.

Now comes to the createViewHolder (ViewGroup parent, int viewType), on the basis of different viewType yon can inflate the different layout file. like the following

 if (viewType ==1){
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.male,parent,false);
    return new AdapterMaleViewHolder(view);
}
else{
    View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.female,parent,false);
    return new AdapterFemaleViewHolder(view);
}

in onBindViewHolder (VH holder,int position) check where holder is instance of AdapterFemaleViewHolder or AdapterMaleViewHolder by instanceof and accordingly assign the values.

ViewHolder May be like this

    class AdapterMaleViewHolder extends RecyclerView.ViewHolder {
            ...
            public AdapterMaleViewHolder(View itemView){
            ...
            }
        }

    class AdapterFemaleViewHolder extends RecyclerView.ViewHolder {
         ...
         public AdapterFemaleViewHolder(View itemView){
            ...
         }
    }

@Anton Savin 2014-10-07 21:17:46

Yes, it's possible. Just implement getItemViewType(), and take care of the viewType parameter in onCreateViewHolder().

So you do something like:

public class MyAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    class ViewHolder0 extends RecyclerView.ViewHolder {
        ...
        public ViewHolder0(View itemView){
        ...
        }
    }

    class ViewHolder2 extends RecyclerView.ViewHolder {
        ...
        public ViewHolder2(View itemView){
        ...
    }

    @Override
    public int getItemViewType(int position) {
        // Just as an example, return 0 or 2 depending on position
        // Note that unlike in ListView adapters, types don't have to be contiguous
        return position % 2 * 2;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
         switch (viewType) {
             case 0: return new ViewHolder0(...);
             case 2: return new ViewHolder2(...);
             ...
         }
    }

    @Override
    public void onBindViewHolder(final RecyclerView.ViewHolder holder, final int position) {
        switch (holder.getItemViewType()) {
            case 0:
                ViewHolder0 viewHolder0 = (ViewHolder0)holder;
                ...
                break;

            case 2:
                ViewHolder2 viewHolder2 = (ViewHolder2)holder;
                ...
                break;
        }
    }
}

@Pongpat 2014-10-07 22:45:20

What is the code in viewholder will look like? Is one viewholder suppose to hold one view type?

@Anton Savin 2014-10-07 22:53:30

@GZ95 generally yes, because different view types can correspond to completely different view layouts. ViewHolder is a common design pattern in android described here. Before it was optional to use, now RecyclerView enforces its usage.

@Pongpat 2014-10-07 22:56:21

That's my point since only one ViewHolder is available in one RecyclerView.Adapter how you going to add multiple to it?

@Anton Savin 2014-10-07 23:33:41

@GZ95 you can create a subclass of MyAdapter.ViewHolder for each view type.

@Pongpat 2014-10-08 04:53:55

Then you have to cast viewholder type in onBindViewHolder() method which I think it defeat the purpose of generic type. By the way, thank you for your answer.

@Anton Savin 2014-10-08 07:12:49

Well, using ListView you would also perform a cast (moreover, you do it even if you have only one view type, so here there is some improvement). To my knowledge, Java doesn't have variadic generics, so they have to use what they have.

@DariusL 2014-12-05 14:02:04

You can create a BaseHolder and extend it for all required types. Then add an abstract setData, which would be overriden (overrode?) in implementation holders. This way you let the language handle type differences. Though it only works if you have a single set of data that all list items can interpret.

@Pratik Butani 2015-02-23 06:59:59

What about different layout file? I want to change layout on optionMenuItem. How its possible? @AntonSavin

@Anton Savin 2015-02-28 19:50:18

@PratikButani-AndroidButs when you create particular ViewHolder you initialize it with some View which you can create in whatever way you prefer - in particular, you can inflate it from whatever XML layout resource you want.

@Pratik Butani 2015-03-02 04:32:20

stackoverflow.com/q/28581712/1318946 @AntonSavin I am just stuck with this problem

@ticofab 2015-03-31 07:10:22

@Hemant Shori 2015-04-28 06:45:21

this works fine adding multiple viewTypes but the problem is with the binding data .while scrolling the data changes randomly.

@Anton Savin 2015-04-28 08:25:31

@Hemant probably you implemented it wrong. If you have a specific problem, it's better to ask a new question.

@Rajan Bhavsar 2015-07-10 04:39:44

@AntonSavin Please check my post and help me in this post i was worked on the CardView with CheckBoxes and I am new in Material Development Thanks In Advance.:stackoverflow.com/questions/31319225/…

@JakeWilson801 2015-12-22 01:27:39

Enums were nice to model the switch statement

@Y.S. 2015-12-27 10:24:20

@AntonSavin: I am facing a related problem ... could you please help me out with my question here ?

@Samer 2016-02-01 09:53:47

Your ViewHolders should be static if they reside in your RecyclerView Adapter

@delive 2016-04-15 12:28:51

have you example about 3 or more ?

@Anton Savin 2016-04-15 17:13:44

@delive What's a problem about 3 or more? Create appropriate number of view holders and that's it.

@AJW 2016-07-03 18:34:43

@Samer, can you explain why ViewHolders should be static?

@Martin Erlic 2016-07-06 08:37:37

What is the difference between getItemViewType and getViewType? getItemViewType does not seem to be a default method in RecyclerAdapter.

@Anton Savin 2016-07-07 01:01:22

@bluemunch I don't understand your question, AFAIK there's no such thing as getViewType() in Android.

@Ravindra Kushwaha 2016-10-19 13:40:14

Check another solution in these link which can also useful for you stackoverflow.com/a/39972276/3946958

@drojokef 2017-02-12 23:55:36

How about position of onBindViewHolder. For example, view1 has 8 items and view2 has 23 items.

@A. Wolf 2017-04-24 16:21:37

Thank you @AntonSavin! I don't understand how can I specify what view has to use a particolar ViewHolder. I mean, I create the adapter passing the layout for the inflate(), I don't understand what the getItemViewType() should get as parameter. How can I say 'if I use the adapter for the recyclerView with id R.id.r1 use ViewHolder0, while if I use recyclerView with id R.id.r2 use ViewHolder1'?

@Anton Savin 2017-04-24 17:10:33

@A.Wolf getItemViewType()'s parameter is item's index in the list. It's up to your internal logic to decide what type that item should have. If you have two different instances of RecyclerView you probably will create two different adaptor classes for them, so you implement different logic.

@A. Wolf 2017-04-24 17:20:54

My idea is to have only one Adapter for recyclerview which has one image and one textview and i infilate a different layout from the adapter constructor. I want to change behaviour based on that value. I have to do a switch in that method based on resource layout id?

@Vahid Amiri 2017-09-02 17:07:21

A better way is to get the actual item from your list and then decide what you want done with it. itemsList.get(position)

@Dave Thomas 2017-11-20 23:29:25

The 0 or 2 really confused me at first reading this answer. I suggest simplifying it to returning 0 or 1, use constants, and remove a bunch of the code to make this answer really dead simple to grok at first glance.

@lannyf 2015-01-11 23:00:13

following Anton's solution, come up with this ViewHolder which holds/handles/delegates different type of layouts. But not sure if the replacing new layout would work when the recycling view's ViewHolder is not type of the data roll in.

So basically, onCreateViewHolder(ViewGroup parent, int viewType) is only called when new view layout is needed;

getItemViewType(int position) will be called for the viewType;

onBindViewHolder(ViewHolder holder, int position) is always called when recycling the view (new data is brought in and try to display with that ViewHolder).

So when onBindViewHolder is called it needs to put in the right view layout and update the ViewHolder.

Is the way correct to replacing the view layout for that ViewHolder to be brought in, or any problem? Appreciate any comment!

public int getItemViewType(int position) {
    TypedData data = mDataSource.get(position);
    return data.type;
}

public ViewHolder onCreateViewHolder(ViewGroup parent, 
    int viewType) {
    return ViewHolder.makeViewHolder(parent, viewType);
}

public void onBindViewHolder(ViewHolder holder, 
    int position) {
    TypedData data = mDataSource.get(position);
    holder.updateData(data);
}

///
public static class ViewHolder extends 
    RecyclerView.ViewHolder {

    ViewGroup mParentViewGroup;
    View mCurrentViewThisViewHolderIsFor;
    int mDataType;

    public TypeOneViewHolder mTypeOneViewHolder;
    public TypeTwoViewHolder mTypeTwoViewHolder;

    static ViewHolder makeViewHolder(ViewGroup vwGrp, 
        int dataType) {
        View v = getLayoutView(vwGrp, dataType);
        return new ViewHolder(vwGrp, v, viewType);
    }

    static View getLayoutView(ViewGroup vwGrp, 
        int dataType) {
        int layoutId = getLayoutId(dataType);
        return LayoutInflater.from(vwGrp.getContext())
                             .inflate(layoutId, null);
    }

    static int getLayoutId(int dataType) {
        if (dataType == TYPE_ONE) {
            return R.layout.type_one_layout;
        } else if (dataType == TYPE_TWO) {
            return R.layout.type_two_layout;
        }
    }

    public ViewHolder(ViewGroup vwGrp, View v, 
        int dataType) {
        super(v);
        mDataType = dataType;
        mParentViewGroup = vwGrp;
        mCurrentViewThisViewHolderIsFor = v;

        if (data.type == TYPE_ONE) {
            mTypeOneViewHolder = new TypeOneViewHolder(v);
        } else if (data.type == TYPE_TWO) {
            mTypeTwoViewHolder = new TypeTwoViewHolder(v);
        }
    }

    public void updateData(TypeData data) {
        mDataType = data.type;
        if (data.type == TYPE_ONE) {
            mTypeTwoViewHolder = null;
            if (mTypeOneViewHolder == null) {
                View newView = getLayoutView(mParentViewGroup,
                               data.type);

                /**
                 *  how to replace new view with 
                    the view in the parent 
                    view container ???
                 */
                replaceView(mCurrentViewThisViewHolderIsFor, 
                            newView);
                mCurrentViewThisViewHolderIsFor = newView;

                mTypeOneViewHolder = 
                    new TypeOneViewHolder(newView);
            }
            mTypeOneViewHolder.updateDataTypeOne(data);

        } else if (data.type == TYPE_TWO){
            mTypeOneViewHolder = null;
            if (mTypeTwoViewHolder == null) {
                View newView = getLayoutView(mParentViewGroup, 
                               data.type);

                /**
                 *  how to replace new view with 
                    the view in the parent view 
                    container ???
                 */
                replaceView(mCurrentViewThisViewHolderIsFor, 
                            newView);
                mCurrentViewThisViewHolderIsFor = newView;

                mTypeTwoViewHolder = 
                    new TypeTwoViewHolder(newView);
            }
            mTypeTwoViewHolder.updateDataTypeOne(data);
        }
    }
}

public static void replaceView(View currentView, 
    View newView) {
    ViewGroup parent = (ViewGroup)currentView.getParent();
    if(parent == null) {
        return;
    }
    final int index = parent.indexOfChild(currentView);
    parent.removeView(currentView);
    parent.addView(newView, index);
}

Edit: ViewHolder has member mItemViewType to hold the view

Edit: looks like in onBindViewHolder(ViewHolder holder, int position) the ViewHolder passed in has been picked up (or created) by looked at getItemViewType(int position) to make sure it is a match, so may not need to worry there that ViewHolder's type does not match the data[position]'s type. Does anyone knows more how the ViewHolder in the onBindViewHolder() is picked up?

Edit: Looks like The recycle ViewHolder is picked by type, so no warrior there.

Edit: http://wiresareobsolete.com/2014/09/building-a-recyclerview-layoutmanager-part-1/ answers this question.

It gets the recycle ViewHolder like:

holder = getRecycledViewPool().getRecycledView(mAdapter.getItemViewType(offsetPosition));

or create new one if not find recycle ViewHolder of right type.

public ViewHolder getRecycledView(int viewType) {
        final ArrayList<ViewHolder> scrapHeap = mScrap.get(viewType);
        if (scrapHeap != null && !scrapHeap.isEmpty()) {
            final int index = scrapHeap.size() - 1;
            final ViewHolder scrap = scrapHeap.get(index);
            scrapHeap.remove(index);
            return scrap;
        }
        return null;
    }

View getViewForPosition(int position, boolean dryRun) {
    ......

    if (holder == null) {
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            if (offsetPosition < 0 || offsetPosition >= mAdapter.getItemCount()) {
                throw new IndexOutOfBoundsException("Inconsistency detected. Invalid item "
                        + "position " + position + "(offset:" + offsetPosition + ")."
                        + "state:" + mState.getItemCount());
            }

            final int type = mAdapter.getItemViewType(offsetPosition);
            // 2) Find from scrap via stable ids, if exists
            if (mAdapter.hasStableIds()) {
                holder = getScrapViewForId(mAdapter.getItemId(offsetPosition), type, dryRun);
                if (holder != null) {
                    // update position
                    holder.mPosition = offsetPosition;
                    fromScrap = true;
                }
            }
            if (holder == null && mViewCacheExtension != null) {
                // We are NOT sending the offsetPosition because LayoutManager does not
                // know it.
                final View view = mViewCacheExtension
                        .getViewForPositionAndType(this, position, type);
                if (view != null) {
                    holder = getChildViewHolder(view);
                    if (holder == null) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view which does not have a ViewHolder");
                    } else if (holder.shouldIgnore()) {
                        throw new IllegalArgumentException("getViewForPositionAndType returned"
                                + " a view that is ignored. You must call stopIgnoring before"
                                + " returning this view.");
                    }
                }
            }
            if (holder == null) { // fallback to recycler
                // try recycler.
                // Head to the shared pool.
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition(" + position + ") fetching from shared "
                            + "pool");
                }
                holder = getRecycledViewPool()
                        .getRecycledView(mAdapter.getItemViewType(offsetPosition));
                if (holder != null) {
                    holder.resetInternal();
                    if (FORCE_INVALIDATE_DISPLAY_LIST) {
                        invalidateDisplayListInt(holder);
                    }
                }
            }
            if (holder == null) {
                holder = mAdapter.createViewHolder(RecyclerView.this,
                        mAdapter.getItemViewType(offsetPosition));
                if (DEBUG) {
                    Log.d(TAG, "getViewForPosition created new ViewHolder");
                }
            }
        }
        boolean bound = false;
        if (mState.isPreLayout() && holder.isBound()) {
            // do not update unless we absolutely have to.
            holder.mPreLayoutPosition = position;
        } else if (!holder.isBound() || holder.needsUpdate() || holder.isInvalid()) {
            if (DEBUG && holder.isRemoved()) {
                throw new IllegalStateException("Removed holder should be bound and it should"
                        + " come here only in pre-layout. Holder: " + holder);
            }
            final int offsetPosition = mAdapterHelper.findPositionOffset(position);
            mAdapter.bindViewHolder(holder, offsetPosition);
            attachAccessibilityDelegate(holder.itemView);
            bound = true;
            if (mState.isPreLayout()) {
                holder.mPreLayoutPosition = position;
            }
        }

        final ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();
        final LayoutParams rvLayoutParams;
        if (lp == null) {
            rvLayoutParams = (LayoutParams) generateDefaultLayoutParams();
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else if (!checkLayoutParams(lp)) {
            rvLayoutParams = (LayoutParams) generateLayoutParams(lp);
            holder.itemView.setLayoutParams(rvLayoutParams);
        } else {
            rvLayoutParams = (LayoutParams) lp;
        }
        rvLayoutParams.mViewHolder = holder;
        rvLayoutParams.mPendingInvalidate = fromScrap && bound;
        return holder.itemView;
}

@Dragas 2016-08-05 10:36:23

Actually, I'd like to improve on Anton's answer.

Since getItemViewType(int position) returns an integer value, you can return the layout resource ID you'd need to inflate. That way you'd save some logic in onCreateViewHolder(ViewGroup parent, int viewType) method.

Also, I wouldn't suggest doing intensive calculations in getItemCount() as that particular function is called at least 5 times while rendering the list, as well as while rendering each item beyond the visible items. Sadly since notifyDatasetChanged() method is final, you can't really override it, but you can call it from another function within the adapter.

@Ioane Sharvadze 2017-10-09 13:05:34

Yes, that might work, but will make confusing for other developers. Also from Documentation Note: Integers must be in the range 0 to getViewTypeCount() - 1. IGNORE_ITEM_VIEW_TYPE can also be returned. So it's just better to write bit more code and don't use hacks.

@Dragas 2017-10-10 13:42:50

I agree. Back then i had missed that particular clause.

@Deemoe 2018-01-11 18:14:41

Thats funny because the RecyclerView.Adapter:getItemViewType() docs here developer.android.com/reference/android/support/v7/widget/… suggest what Dragas had posted that you should "Consider using id resources to uniquely identify item view types." apparently oblivious to the requirement for getViewTypeCount()

@Islam Assi 2015-12-25 16:11:03

Yes, it is possible.

Write a generic view holder:

    public abstract class GenericViewHolder extends RecyclerView.ViewHolder
{
    public GenericViewHolder(View itemView) {
        super(itemView);
    }

    public abstract  void setDataOnView(int position);
}

then create your view holders and make them extend the GenericViewHolder. For example, this one:

     public class SectionViewHolder extends GenericViewHolder{
    public final View mView;
    public final TextView dividerTxtV;

    public SectionViewHolder(View itemView) {
        super(itemView);
        mView = itemView;
        dividerTxtV = (TextView) mView.findViewById(R.id.dividerTxtV);
    }

    @Override
    public void setDataOnView(int position) {
        try {
            String title= sections.get(position);
            if(title!= null)
                this.dividerTxtV.setText(title);
        }catch (Exception e){
            new CustomError("Error!"+e.getMessage(), null, false, null, e);
        }
    }
}

then the RecyclerView.Adapter class will look like this one:

public class MyClassRecyclerViewAdapter extends RecyclerView.Adapter<MyClassRecyclerViewAdapter.GenericViewHolder> {

@Override
public int getItemViewType(int position) {
     // depends on your problem
     switch (position) {
         case : return VIEW_TYPE1;
         case : return VIEW_TYPE2;
         ...
     }
}

    @Override
   public GenericViewHolder onCreateViewHolder(ViewGroup parent, int viewType)  {
    View view;
    if(viewType == VIEW_TYPE1){
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout1, parent, false);
        return new SectionViewHolder(view);
    }else if( viewType == VIEW_TYPE2){
        view = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout2, parent, false);
        return new OtherViewHolder(view);
    }
    // Cont. other view holders ...
    return null;
   }

@Override
public void onBindViewHolder(GenericViewHolder holder, int position) {
    holder.setDataOnView(position);
}

@skm 2018-08-12 04:29:19

How to use then in an activity? Should the type be passed throught the method?

@skm 2018-08-12 07:14:51

How to use this Adapter in Activity? And how does it recognize which Type is in the list

@Michal Faber 2015-04-17 11:10:13

I have a better solution which allows to create multiple view types in a declarative and type safe way. It’s written in Kotlin which btw is really nice.

Simple view holders for all required view types

class ViewHolderMedium(itemView: View) : RecyclerView.ViewHolder(itemView) {
    val icon: ImageView = itemView.findViewById(R.id.icon) as ImageView
    val label: TextView = itemView.findViewById(R.id.label) as TextView
}

There is an abstraction of adapter data item. Note that a view type is represented by a hashCode of particular view holder class (KClass in Kotlin)

trait AdapterItem {
   val viewType: Int
   fun bindViewHolder(viewHolder: RecyclerView.ViewHolder)
}

abstract class AdapterItemBase<T>(val viewHolderClass: KClass<T>) : AdapterItem {
   override val viewType: Int = viewHolderClass.hashCode()  
   abstract fun bindViewHolder(viewHolder: T)
   override fun bindViewHolder(viewHolder: RecyclerView.ViewHolder) {
       bindViewHolder(viewHolder as T)
   }
}

Only bindViewHolder needs to be overriden in concrete adapter item classes (type safe way)

class AdapterItemMedium(val icon: Drawable, val label: String, val onClick: () -> Unit) : AdapterItemBase<ViewHolderMedium>(ViewHolderMedium::class) {
    override fun bindViewHolder(viewHolder: ViewHolderMedium) {
        viewHolder.icon.setImageDrawable(icon)
        viewHolder.label.setText(label)
        viewHolder.itemView.setOnClickListener { onClick() }
    }
}

List of such AdapterItemMedium objects is a data source for the adapter which actually accepts List<AdapterItem> see below.

Important part of this solution is a view holder factory which will provide fresh instances of a specific ViewHolder

class ViewHolderProvider {
    private val viewHolderFactories = hashMapOf<Int, Pair<Int, Any>>()

    fun provideViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val (layoutId: Int, f: Any) = viewHolderFactories.get(viewType)
        val viewHolderFactory = f as (View) -> RecyclerView.ViewHolder
        val view = LayoutInflater.from(viewGroup.getContext()).inflate(layoutId, viewGroup, false)
        return viewHolderFactory(view)
    }

    fun registerViewHolderFactory<T>(key: KClass<T>, layoutId: Int, viewHolderFactory: (View) -> T) {
        viewHolderFactories.put(key.hashCode(), Pair(layoutId, viewHolderFactory))
    }
}

And the simple adapter class looks like this

public class MultitypeAdapter(val items: List<AdapterItem>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

   val viewHolderProvider = ViewHolderProvider() // inject ex Dagger2

   init {
        viewHolderProvider!!.registerViewHolderFactory(ViewHolderMedium::class, R.layout.item_medium, { itemView ->
            ViewHolderMedium(itemView)
        })
   }

   override fun getItemViewType(position: Int): Int {
        return items[position].viewType
    }

    override fun getItemCount(): Int {
        return items.size()
    }

    override fun onCreateViewHolder(viewGroup: ViewGroup, viewType: Int): RecyclerView.ViewHolder? {
        return viewHolderProvider!!.provideViewHolder(viewGroup, viewType)
    }

    override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
        items[position].bindViewHolder(viewHolder)     
    }
}

Only 3 steps to create a new view type:

  1. create a view holder class
  2. create an adapter item class (extending from AdapterItemBase)
  3. register view holder class in ViewHolderProvider

Here is an example of this concept : android-drawer-template It goes even further - view type which act as a spinner component, selectable adapter items.

@Simon 2015-04-11 15:40:06

The below is not pseudocode and I have tested it and it has worked for me.

I wanted to create a headerview in my recyclerview and then display a list of pictures below the header which the user can click on.

I used a few switches in my code, don't know if that is the most efficient way to do this so feel free to give your comments:

   public class ViewHolder extends RecyclerView.ViewHolder{

        //These are the general elements in the RecyclerView
        public TextView place;
        public ImageView pics;

        //This is the Header on the Recycler (viewType = 0)
        public TextView name, description;

        //This constructor would switch what to findViewBy according to the type of viewType
        public ViewHolder(View v, int viewType) {
            super(v);
            if (viewType == 0) {
                name = (TextView) v.findViewById(R.id.name);
                decsription = (TextView) v.findViewById(R.id.description);
            } else if (viewType == 1) {
                place = (TextView) v.findViewById(R.id.place);
                pics = (ImageView) v.findViewById(R.id.pics);
            }
        }
    }


    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent,
                                         int viewType)
    {
        View v;
        ViewHolder vh;
        // create a new view
        switch (viewType) {
            case 0: //This would be the header view in my Recycler
                v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.recyclerview_welcome, parent, false);
                vh = new ViewHolder(v,viewType);
                return  vh;
            default: //This would be the normal list with the pictures of the places in the world
                v = LayoutInflater.from(parent.getContext())
                        .inflate(R.layout.recyclerview_picture, parent, false);
                vh = new ViewHolder(v, viewType);
                v.setOnClickListener(new View.OnClickListener(){

                    @Override
                    public void onClick(View v) {
                        Intent intent = new Intent(mContext, nextActivity.class);
                        intent.putExtra("ListNo",mRecyclerView.getChildPosition(v));
                        mContext.startActivity(intent);
                    }
                });
                return vh;
        }
    }

    //Overriden so that I can display custom rows in the recyclerview
    @Override
    public int getItemViewType(int position) {
        int viewType = 1; //Default is 1
        if (position == 0) viewType = 0; //if zero, it will be a header view
        return viewType;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        //position == 0 means its the info header view on the Recycler
        if (position == 0) {
            holder.name.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,"name clicked", Toast.LENGTH_SHORT).show();
                }
            });
            holder.description.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View v) {
                    Toast.makeText(mContext,"description clicked", Toast.LENGTH_SHORT).show();
                }
            });
            //this means it is beyond the headerview now as it is no longer 0. For testing purposes, I'm alternating between two pics for now
        } else if (position > 0) {
           holder.place.setText(mDataset[position]);
            if (position % 2 == 0) {
               holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic1));
            }
            if (position % 2 == 1) {
                holder.pics.setImageDrawable(mContext.getResources().getDrawable(R.drawable.pic2));
            }

        }
    }

@Bassinator 2017-08-04 18:32:15

This is a nice, what if I wanted multiple headers at dynamic positions? Say, a list of items with headers defining categories. You solution would seem to require the special headers to be at predetermined int positions.

@yqritc 2015-04-01 14:57:44

If the layouts for view types are only a few and binding logics are simple, follow Anton's solution.
But the code will be messy if you need to manage the complex layouts and binding logics.

I believe the following solution will be useful for someone who need to handle complex view types.

Base DataBinder class

abstract public class DataBinder<T extends RecyclerView.ViewHolder> {

    private DataBindAdapter mDataBindAdapter;

    public DataBinder(DataBindAdapter dataBindAdapter) {
        mDataBindAdapter = dataBindAdapter;
    }

    abstract public T newViewHolder(ViewGroup parent);

    abstract public void bindViewHolder(T holder, int position);

    abstract public int getItemCount();

......

}

The functions needed to define in this class are pretty much same as the adapter class when creating the single view type.
For each view type, create the class by extending this DataBinder.

Sample DataBinder class

public class Sample1Binder extends DataBinder<Sample1Binder.ViewHolder> {

    private List<String> mDataSet = new ArrayList();

    public Sample1Binder(DataBindAdapter dataBindAdapter) {
        super(dataBindAdapter);
    }

    @Override
    public ViewHolder newViewHolder(ViewGroup parent) {
        View view = LayoutInflater.from(parent.getContext()).inflate(
            R.layout.layout_sample1, parent, false);
        return new ViewHolder(view);
    }

    @Override
    public void bindViewHolder(ViewHolder holder, int position) {
        String title = mDataSet.get(position);
        holder.mTitleText.setText(title);
    }

    @Override
    public int getItemCount() {
        return mDataSet.size();
    }

    public void setDataSet(List<String> dataSet) {
        mDataSet.addAll(dataSet);
    }

    static class ViewHolder extends RecyclerView.ViewHolder {

        TextView mTitleText;

        public ViewHolder(View view) {
            super(view);
            mTitleText = (TextView) view.findViewById(R.id.title_type1);
        }
    }
}

In order to manage DataBinder classes, create adapter class.

Base DataBindAdapter class

abstract public class DataBindAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        return getDataBinder(viewType).newViewHolder(parent);
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder viewHolder, int position) {
        int binderPosition = getBinderPosition(position);
        getDataBinder(viewHolder.getItemViewType()).bindViewHolder(viewHolder, binderPosition);
    }

    @Override
    public abstract int getItemCount();

    @Override
    public abstract int getItemViewType(int position);

    public abstract <T extends DataBinder> T getDataBinder(int viewType);

    public abstract int getPosition(DataBinder binder, int binderPosition);

    public abstract int getBinderPosition(int position);

......

}

Create the class by extending this base class, and then instantiate DataBinder classes and override abstract methods

  1. getItemCount
    Return the total item count of DataBinders

  2. getItemViewType
    Define the mapping logic between the adapter position and view type.

  3. getDataBinder
    Return the DataBinder instance based on the view type

  4. getPosition
    Define convert logic to the adapter position from the position in the specified DataBinder

  5. getBinderPosition
    Define convert logic to the position in the DataBinder from the adapter position

Hope this solution will be helpful.
I left more detail solution and samples in GitHub, so please refer the following link if you need.
https://github.com/yqritc/RecyclerView-MultipleViewTypesAdapter

@Simon 2015-04-25 18:19:34

I'm slightly confused by your code, maybe you can help me, I would not like my views to be defined by the positions in the list but by their viewtypes. It appears as if the views in your code are determined based on their positions ie. so if im on position 1, view 1 is displayed, position 3, view 3 is displayed and everything else displays position 2's view. I don't want to base my views on positions but on viewtypes - so if i specify that the view type is images, it should display the image. How can i do that?

@yqritc 2015-04-27 03:48:20

Sorry, I could not fully understand your question..., but you need to write the logic somewhere in order to bind the position and viewtype.

@Sniper 2015-04-27 05:02:34

this code is not confused , this is a RecyclerView Adapter pattern, and this should be excepted like correct answer of the question. Follow the link of @yqritc, spend a little time to discovered and you will have perfect pattern for RecyclerView with different type of layouts.

@cesards 2015-05-29 11:09:26

POLYMORPHISM FTW :D

@rgv 2015-07-16 19:09:12

newbe here, public class DataBinder<T extends RecyclerView.ViewHolder> can someone tell me what <T someClass> is called, so I can google if I get the term . Also when I say abstract public class DataBinder<T extends RecyclerView.ViewHolder> does that mean that this class is of type ViewHolder, so in result every class that extends this class will be of typeviewHolder is that the idea?

@Android Dev 2015-11-24 13:28:12

@yqritc I have used the library you have mentioned here , I am getting problem with multiple DataBinders , I have posted a question on Stackoverflow .Here is the link stackoverflow.com/questions/33894351/… . Help me plz.

@Erdi İzgi 2016-12-01 14:58:27

Very clean solution. It works smooth and elegantly. @Simon I understood your question. You should create some kind of logic in your adapter class. For instance; you can you position %3 == 0 instead of position == 0. But this might not be enough depending of the size of your dataset for that type.

@Paul Okeke 2018-09-06 20:31:16

@cesards you made me to go refresh my knowledge again on polymorphism lol.... Java isn't bad

Related Questions

Sponsored Content

37 Answered Questions

[SOLVED] How to add dividers and spaces between items in RecyclerView?

33 Answered Questions

[SOLVED] Why doesn't RecyclerView have onItemClickListener()?

3 Answered Questions

2 Answered Questions

[SOLVED] Recycler View position comparison

0 Answered Questions

Recyclerview with multiple View Types: inner workings?

1 Answered Questions

[SOLVED] Scrolling lagged after applying the typeface in the Recycler view items

  • 2016-02-26 11:56:13
  • leo
  • 251 View
  • 2 Score
  • 1 Answer
  • Tags:   java android

2 Answered Questions

1 Answered Questions

Sponsored Content