How to add custom header to RecylerView

RecyclerView is the upgrade of ListView and there are a lot of advantages compared to ListView such as CardView and ViewHolder.

In this tutorial we are going to see how to customize RecyclerView by adding separate layout for header in LinearLayoutManager and GridLayoutManager.

To implement heterogeneous layouts inside the RecyclerView, most of the work is done within the RecyclerView.Adapter. In particular, there are special methods to be overridden within the adapter:

  • getItemViewType()
  • onCreateViewHolder()
  • onBindViewHolder()

LinearLayoutManager

We need two classes, which represents two objects working with header and list item. I named these classes Header and Item. For simplicity, I just made the Header and Item classes with one field. However you can modify it to meet your needs by adding more fields or change the type of field.

Let's start from ListItem interface which is base for items in RecyclerView.

public interface ListItem {
    int TYPE_ITEM = 0;
    int TYPE_HEADER = 1;

    int getItemType();
}

Declare Header

public class Header implements ListItem {
    String name;

    public Header(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int getItemType() {
        return ListItem.TYPE_HEADER;
    }
}

Declare Item

public class Item implements ListItem {
    String name;

    public Person(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    @Override
    public int getItemType() {
        return ListItem.TYPE_ITEM;
    }
}

Following is layout for header_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="0dp">

    <TextView
        android:id="@+id/tvName"
        android:textSize="16sp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:textStyle="bold"
        android:textColor="@android:color/white"
        android:background="@color/colorAccent"
        android:padding="5dp"/>

</LinearLayout>

Following is layout for item_layout.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="match_parent"
    android:layout_height="wrap_content">

    <TextView
        android:id="@+id/tvItem"
        android:layout_marginTop="15dp"
        android:layout_marginBottom="15dp"
        android:layout_gravity="center"
        android:textSize="14sp"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content" />

</LinearLayout>

We will create an adapter for our RecyclerView. This adapter must extend RecyclerView.Adapter which is followed by a ViewHolder pattern. ViewHolder will help the system avoid calling findViewById method every time it shows a row.

public class DataAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private ArrayList<ListItem> items;

    public DataAdapter(ArrayList<ListItem> items) {
        this.items = items;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if(viewType == ListItem.TYPE_HEADER) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.header_layout, parent, false);
            return  new VHHeader(v);
        } else if(viewType == ListItem.TYPE_ITEM) {
            View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_layout, parent, false);
            return new VHItem(v);
        }
        throw new RuntimeException("there is no type that matches the type " + viewType + " + make sure your using types correctly");
    }


    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if(holder instanceof VHHeader) {
            Header header = (Header) items.get(position);
            VHHeader VHheader = (VHHeader)holder;
            VHheader.tvName.setText(header.getName());
        } else if(holder instanceof VHItem) {
            Person person = (Person) items.get(position);
            VHItem VHitem = (VHItem)holder;
            VHitem.tvItem.setText(person.getName());
        }
    }

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

    @Override
    public int getItemViewType(int position) {
        return items.get(position).getItemType();
    }

    class VHHeader extends RecyclerView.ViewHolder{
        TextView tvName;
        public VHHeader(View itemView) {
            super(itemView);
            this.tvName = (TextView)itemView.findViewById(R.id.tvName);
        }
    }

    class VHItem extends RecyclerView.ViewHolder{
        TextView tvItem;
        public VHItem(View itemView) {
            super(itemView);
            this.tvItem = (TextView)itemView.findViewById(R.id.tvItem);
        }
    }
}

Following is layout for activity_main.xml

<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:app="http://schemas.android.com/apk/res-auto"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    tools:context="proft.me.sandbox.RecycleHeaderActivity">

    <android.support.v7.widget.RecyclerView
        android:id="@+id/rvItems"
        android:scrollbars="vertical"
        android:layout_width="match_parent"
        android:layout_height="match_parent"/>

</android.support.constraint.ConstraintLayout>

Following is MainActivity

public class MainActivity extends AppCompatActivity {
    private ArrayList<ListItem> items =  new ArrayList<>();
    private DataAdapter adapter;
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView)findViewById(R.id.rvItems);
        recyclerView.setHasFixedSize(true);
        RecyclerView.LayoutManager layoutManager = new LinearLayoutManager(getApplicationContext());
        recyclerView.setLayoutManager(layoutManager);

        adapter = new DataAdapter(items);
        recyclerView.setAdapter(adapter);

        items.add(new Header("Header 1"));
        items.add(new Person("Item 1", true));
        items.add(new Person("Item 2", true));
        items.add(new Person("Item 3", true));
        items.add(new Header("Header 2"));
        items.add(new Person("Item 4", false));
        items.add(new Person("Item 5", false));
        items.add(new Person("Item 6", false));
        items.add(new Person("Item 7", false));
        items.add(new Person("Item 8", false));
        adapter.notifyDataSetChanged();
    }
}

Result

android_recycleview_custom_header_linear.png

GridLayoutManager

The GridLayoutManager allows you to show a RecyclerView as a grid. Using SpanSizeLookup you can change the span size of the header so it expands to fit as many columns as you have.

Just modify MainActivity, add GridLayoutManager and redefine SpanSizeLookup.

public class MainActivity extends AppCompatActivity {
    private ArrayList<ListItem> items =  new ArrayList<>();
    private DataAdapterHeader adapter;
    private RecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        recyclerView = (RecyclerView)findViewById(R.id.rvItems);
        recyclerView.setHasFixedSize(true);

        adapter = new DataAdapterHeader(items);

        recyclerView.setAdapter(adapter);

        GridLayoutManager gd = new GridLayoutManager(this, 2);

        gd.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
            @Override
            public int getSpanSize(int position) {
                return adapter.getItemViewType(position) == ListItem.TYPE_HEADER ? 2 : 1;
            }
        });

        recyclerView.setLayoutManager(gd);

        items.add(new Header("Header 1"));
        items.add(new Person("Item 1", true));
        items.add(new Person("Item 2", true));
        items.add(new Person("Item 3", true));
        items.add(new Header("Header 2"));
        items.add(new Person("Item 4", false));
        items.add(new Person("Item 5", false));
        items.add(new Person("Item 6", false));
        items.add(new Person("Item 7", false));
        items.add(new Person("Item 8", false));
        adapter.notifyDataSetChanged();

    }
}
android_recycleview_custom_header_grid.png
comments powered by Disqus