bookmark_border

Multi-Type RecyclerView items

Mohammad Shaddad,

Cover image courtesy of Facebook Engineering.

Most modern mobile applications render complex list items, of different types. Think of the Facebook or Twitter timelines, where you have a list of stories that are: simple text, images, videos, web links and others. As common as this requirement is, I get asked frequently on how to implement this, and after I was asked about it today, I have decided to write this short implementation guide.

In this tutorial, I'll walk you step by step into how I approach this problem in my Android applications using the RecyclerView.

So What is a RecyclerView again?

If you have written Android code in the last 12 months, then you have already used the RecyclerView. If not, then here's a quick overview of RecyclerView.

RecyclerView is a ViewGroup widget that is used to create complex lists of items. It is a successor to the ListView offering more advanced and flexible implementation. The RecyclerView is superior to the classic ListView when displaying large data sets efficiently.

The RecyclerView relies on two key components to manage its list and display: RecyclerView.Adapter and RecyclerView.ViewHolder.

This tutorial assumes that you have a basic understanding of how to use the RecyclerView. If not, then you can find a getting started guide at the Android developers guide.

Now let's get to the problem at hand.

Step 1 - Define the Adapter

The RecyclerView works hand in hand with the RecyclerView.Adapter which is responsible for retrieving the list of items, preparing the views and binding the data to the list items.

We'll create a TimelineViewAdapter which will inherit the RecyclerView.Adapter , in the constructor, we will initialize a static list of items that will be displayed in the timeline as follows:

  1. public class TimelineViewAdapter extends RecyclerView.Adapter {
  2.  
  3. private List<Object> items;
  4.  
  5. public TimelineViewAdapter() {
  6. items = new ArrayList<>();
  7. items.add(new TextStoryline(
  8. "Story line 1",
  9. Date.valueOf("2016-12-06")
  10. ));
  11. items.add(new TextStoryline(
  12. "Story line 2",
  13. Date.valueOf("2016-12-05")
  14. ));
  15. items.add(new ImageStoryline(
  16. "http://images.com/image1.jpg", // dummy invalid url
  17. "Image 1",
  18. Date.valueOf("2016-12-05")
  19. ));
  20. items.add(new TextStoryline(
  21. "Story line 3",
  22. Date.valueOf("2016-12-05")
  23. ));
  24. // Add as many items as needed
  25. }
  26.  
  27. @Override
  28. public int getItemCount() {
  29. return items.size();
  30. }
  31. }



Step 2 - Define the View Types

Now that we have defined the adapter skeleton, we need to declare the view types that the adapter deals with. For simplicity, we will use two types only: simple text, and image items. I do this by defining constant variables in the adapter implementation as follows:

  1. public class TimelineViewAdapter extends RecyclerView.Adapter {
  2.  
  3. private List<Object> items;
  4.  
  5. private final static int VIEW_TYPE_SIMPLE_TEXT_STORY = 0;
  6. private final static int VIEW_TYPE_IMAGE_STORY = 1;
  7.  
  8. // implementation goes here
  9. }



Now that we know what view types our adapter handles, we need to override the base implementation of the getItemViewType(int position) method. This method is used to let the adapter know the type of view/item at a specified location:

  1. public class TimelineViewAdapter extends RecyclerView.Adapter {
  2.  
  3. private List<Object> items;
  4.  
  5. private final static int VIEW_TYPE_SIMPLE_TEXT_STORY = 0;
  6. private final static int VIEW_TYPE_IMAGE_STORY = 1;
  7.  
  8. public TimelineViewAdapter() {
  9. items = new ArrayList<>();
  10. // Rest of method body
  11. }
  12.  
  13. @Override
  14. public int getItemViewType(int position) {
  15. Object item = items.get(position);
  16. if(item instanceof TextStoryline){
  17. return VIEW_TYPE_SIMPLE_TEXT_STORY;
  18. }
  19. else if(item instanceof ImageStoryline){
  20. return VIEW_TYPE_IMAGE_STORY;
  21. }
  22. else {
  23. throw new UnsupportedOperationException();
  24. }
  25. }
  26. }



Step 3 - Implement the Views

Now that you are ready to process the items in the adapter, let's create the ViewHolder widgets that are used for binding the data of each item in the list.

Each widget must inherit the ViewHolder class and implement a constructor accepting a View as an input parameter. For this tutorial, we will create two widgets: TextStorylineViewHolder, and ImageStorylineViewHolder. Following is an the implementation of the TextStorylineViewHolder class and its corresponding layout definition:

  1. public class TextStorylineViewHolder extends RecyclerView.ViewHolder {
  2.  
  3. private TextView storyView;
  4.  
  5. public TextStorylineViewHolder(View itemView) {
  6. super(itemView);
  7. storyView = (TextView) itemView.findViewById(R.id.text_story_line);
  8. }
  9.  
  10. public void bindStoryLine(TextStoryline storyline) {
  11. storyView.setText(
  12. storyline.getStory()
  13. );
  14. }
  15. }



  1. <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
  2. android:layout_width="match_parent"
  3. android:layout_height="wrap_content"
  4. xmlns:card_view="http://schemas.android.com/apk/res-auto" >
  5.  
  6. <android.support.v7.widget.CardView
  7. xmlns:card_view="http://schemas.android.com/apk/res-auto"
  8. android:id="@+id/text_story_line_view"
  9. android:layout_gravity="center"
  10. android:layout_width="match_parent"
  11. android:layout_height="wrap_content"
  12. card_view:cardCornerRadius="4dp"
  13. android:layout_marginBottom="8dp">
  14.  
  15. <TextView
  16. android:id="@+id/text_story_line"
  17. android:layout_width="match_parent"
  18. android:layout_height="match_parent"
  19. android:padding="@dimen/activity_horizontal_margin" />
  20.  
  21. </android.support.v7.widget.CardView>
  22.  
  23. </LinearLayout>

As you can see, I have added a bindStoryline(TextStoryline storyline) method to the implementation, we'll be using this method when processing the items in the adapter.

Step 4 - Pass the Views to the Adapter

In this step, you need to initialize and pass the ViewHolder that the adapter needs to use for a specific type. The adapter calls the onCreateViewHolder(ViewGroup parent, int viewType) method which we will override in our TimelineViewAdapter and will pass it the viewType that was acquired calling the getItemViewType method:

  1. @Override
  2. public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  3. switch (viewType) {
  4. case VIEW_TYPE_SIMPLE_TEXT_STORY: {
  5. View view = LayoutInflater
  6. .from(
  7. parent.getContext()
  8. )
  9. .inflate(
  10. R.layout.text_story_line_view_holder,
  11. parent,
  12. false
  13. );
  14. return new TextStorylineViewHolder(view);
  15. }
  16. case VIEW_TYPE_IMAGE_STORY: {
  17. View view = LayoutInflater
  18. .from(
  19. parent.getContext()
  20. )
  21. .inflate(
  22. R.layout.image_story_line_view_holder,
  23. parent,
  24. false
  25. );
  26. return new ImageStorylineViewHolder(view);
  27. }
  28. default: {
  29. throw new UnsupportedOperationException();
  30. }
  31. }
  32. }

Step 5 - Bind the List Items to the Views

The last step in creating our adapter is to bind each list entry to its corresponding view. This is accomplished by overriding the onBindViewHolder(ViewHolder holder, int position) method in the TimelineViewAdapter. This method is called by the RecyclerView to display the data at the specified position. The RecycleView will pass the ViewHolder it acquired when it called the onCreateViewHolder method as a parameter. In this method, we will bind the attributes of the item at the specified position to the view elements of the view holder.

  1. @Override
  2. public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
  3. switch (holder.getItemViewType()) {
  4. case VIEW_TYPE_SIMPLE_TEXT_STORY: {
  5. ((TextStorylineViewHolder) holder).bindStoryLine(
  6. (TextStoryline) items.get(position)
  7. );
  8. return;
  9. }
  10. case VIEW_TYPE_IMAGE_STORY: {
  11. ((ImageStorylineViewHolder) holder).bindStoryLine(
  12. (ImageStoryline) items.get(position)
  13. );
  14. return;
  15. }
  16. default: {
  17. throw new UnsupportedOperationException();
  18. }
  19. }
  20. }

Step 6 - Consume the RecyclerView.Adapter

Finally, all that is left to do is to consume the RecyclerView.Adapter that we have created and bind it to a RecyclerView in our activity. This is done by calling the setAdapter(RecyclerView.Adapter adapter) method on a RecyclerView object as follows:

  1. public class MainActivity extends AppCompatActivity {
  2.  
  3. private RecyclerView recyclerView;
  4. private RecyclerView.Adapter adapter;
  5. private RecyclerView.LayoutManager layoutManager;
  6.  
  7. @Override
  8. protected void onCreate(Bundle savedInstanceState) {
  9. super.onCreate(savedInstanceState);
  10. setContentView(R.layout.activity_main);
  11.  
  12. recyclerView = (RecyclerView) findViewById(R.id.my_recycler_view);
  13. layoutManager = new LinearLayoutManager(this);
  14. recyclerView.setLayoutManager(layoutManager);
  15.  
  16. adapter = new TimelineViewAdapter();
  17. recyclerView.setAdapter(adapter);
  18. }
  19. }

Running our simple application, we will get the following output:


Application output

figure 1 - Application output

As you can see, this is a simple to achieve task once you get an understanding of how the RecyclerView works.

Just in case you get stuck or face issues following this guide, you can find the sample reference code on our GitHub samples repository. Feel free to fork, update, as required.

Thanks for reading this guide, and if you feel there are parts that were not clear or require further clarifications, please leave a note in the discussion section below.

androidandroid-recyclerview

What level is this content for?

We've all had our humble beginnings, it's our hard learned experiences that got us to master our code. We will all learn something sharing our knowledge, leave a legacy and write a post now.
create Write post

About the author


Mohammad Shaddad
Mohammad Shaddad

Mohammad is a technology consultant and entrepreneur with big passion for technology and living systems; aka software communities. He enjoys mentoring other programmers and entrepreneurs, as well as creating new things. Mohammad is the founder of @barmijly and @nafaqati. He currently spends his time between Amman.JO and Dubai.AE.