Adding Header and Footer layouts for RecyclerView in Android

    As you can see at RecyclerView class source code, it's a subclass of ViewGroup and implements ScrollingView and NestedScrollingChild interfaces. It mean that the RecyclerView structure is not like ListView or GridView (which is sub-classes of AdapterView), it is a list view without the first and last points.
    Therefore, adding header/footer view to RecyclerView is not simple like the ListView (by calling addHeaderView(View v) or addFooterView(View v) methods). To accomplish this, we must use a custom way.
    Through this post, I'll provide a solution to add the header/footer layout by determine the property of header, footer and list item in the adapter class, Accordingly, we will inflate suitable layout for each view. For another ways, please keep search on Internet!
    DEMO VIDEO:

Declaring layouts for item/header/footer views

    The first work is you must have 3 separated layouts for 3 view types of the RecyclerView:
layout_item.xml
<?xml version="1.0" encoding="utf-8"?>
<android.support.v7.widget.CardView xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginBottom="6dp">

    <TextView
        android:id="@+id/recycler_item_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:gravity="center"
        android:padding="@dimen/activity_horizontal_margin"
        android:text="Recycler View Item"
        android:textStyle="bold" />

</android.support.v7.widget.CardView>
layout_footer.xml
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:layout_marginTop="5dp">

    <TextView
        android:id="@+id/footer_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_orange_dark"
        android:gravity="center"
        android:padding="@dimen/activity_horizontal_margin"
        android:text="Footer View"
        android:textAllCaps="true"
        android:textColor="@android:color/white"
        android:textStyle="bold" />

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

    <TextView
        android:id="@+id/header_text"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:background="@android:color/holo_blue_dark"
        android:gravity="center"
        android:padding="@dimen/activity_horizontal_margin"
        android:text="Header View"
        android:textAllCaps="true"
        android:textColor="@android:color/white"
        android:textStyle="bold" />

</LinearLayout>

Configuration in adapter class

    Like other cases, we'll make a subclass of RecyclerView.Adapter to custom an our own RecyclerView adapter. Firstly, provide some static constants:
private static final int TYPE_HEADER = 0;
private static final int TYPE_FOOTER = 1;
private static final int TYPE_ITEM = 2;
    In onCreateViewHolder() method, create a ViewHolder pattern and assign the layout to be shown based on the view type changes:
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_ITEM) {
            //Inflating recycle view item layout
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item, parent, false);
            return new ItemViewHolder(itemView);
        } else if (viewType == TYPE_HEADER) {
            //Inflating header view
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_header, parent, false);
            return new HeaderViewHolder(itemView);
        } else if (viewType == TYPE_FOOTER) {
            //Inflating footer view
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_footer, parent, false);
            return new FooterViewHolder(itemView);
        } else return null;
    }
    Most important, you must override getItemViewType() to fix the header at zeroth position and footer at last position of the RecyclerView:
    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return TYPE_HEADER;
        } else if (position == stringArrayList.size() + 1) {
            return TYPE_FOOTER;
        }
        return TYPE_ITEM;
    }
    Eventually add the total array count by 2 to represent view that the RecyclerView has to be inflated with header and footer layouts, you must rewrite getItemCount():
    @Override
    public int getItemCount() {
        return stringArrayList.size() + 2;
    }
    Finally, the complete code of the adapter class would be like:
RecyclerViewAdapter.java
package info.devexchanges.recyclerviewheaderfooter;

import android.app.Activity;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;

import java.util.ArrayList;

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

    private static final int TYPE_HEADER = 0;
    private static final int TYPE_FOOTER = 1;
    private static final int TYPE_ITEM = 2;

    private ArrayList<String> stringArrayList;
    private Activity activity;

    public RecyclerViewAdapter(Activity activity, ArrayList<String> strings) {
        this.activity = activity;
        this.stringArrayList = strings;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == TYPE_ITEM) {
            //Inflating recycle view item layout
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_item, parent, false);
            return new ItemViewHolder(itemView);
        } else if (viewType == TYPE_HEADER) {
            //Inflating header view
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_header, parent, false);
            return new HeaderViewHolder(itemView);
        } else if (viewType == TYPE_FOOTER) {
            //Inflating footer view
            View itemView = LayoutInflater.from(parent.getContext()).inflate(R.layout.layout_footer, parent, false);
            return new FooterViewHolder(itemView);
        } else return null;
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, final int position) {
        if (holder instanceof HeaderViewHolder) {
            HeaderViewHolder headerHolder = (HeaderViewHolder) holder;
            headerHolder.headerTitle.setText("Header View");
            headerHolder.headerTitle.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(activity, "You clicked at Header View!", Toast.LENGTH_SHORT).show();
                }
            });
        } else if (holder instanceof FooterViewHolder) {
            FooterViewHolder footerHolder = (FooterViewHolder) holder;
            footerHolder.footerText.setText("Footer View");
            footerHolder.footerText.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(activity, "You clicked at Footer View", Toast.LENGTH_SHORT).show();
                }
            });
        } else if (holder instanceof ItemViewHolder) {
            ItemViewHolder itemViewHolder = (ItemViewHolder) holder;
            itemViewHolder.itemText.setText("Recycler Item " + position);
            itemViewHolder.itemText.setOnClickListener(new View.OnClickListener() {
                @Override
                public void onClick(View view) {
                    Toast.makeText(activity, "You clicked at item " + position, Toast.LENGTH_SHORT).show();
                }
            });
        }
    }

    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return TYPE_HEADER;
        } else if (position == stringArrayList.size() + 1) {
            return TYPE_FOOTER;
        }
        return TYPE_ITEM;
    }

    @Override
    public int getItemCount() {
        return stringArrayList.size() + 2;
    }

    private class HeaderViewHolder extends RecyclerView.ViewHolder {
        TextView headerTitle;

        public HeaderViewHolder(View view) {
            super(view);
            headerTitle = (TextView) view.findViewById(R.id.header_text);
        }
    }

    private class FooterViewHolder extends RecyclerView.ViewHolder {
        TextView footerText;

        public FooterViewHolder(View view) {
            super(view);
            footerText = (TextView) view.findViewById(R.id.footer_text);
        }
    }

    private class ItemViewHolder extends RecyclerView.ViewHolder {
        TextView itemText;

        public ItemViewHolder(View itemView) {
            super(itemView);
            itemText = (TextView) itemView.findViewById(R.id.recycler_item_text);
        }
    }
}
NOTE: You must add RecyclerView and CarView dependencies to app-level build.gradle to use these 2 widgets:
compile 'com.android.support:recyclerview-v7:25.0.0'
compile 'com.android.support:cardview-v7:25.0.0'
compile 'com.android.support:appcompat-v7:25.0.0'

Running application

    The most important work is done, running this app, you'll have this result:

Conclusions

    By determine layout for each view type, we can add header and footer views to RecyclerView easily. I hope this post is helpful with beginners which starting using RecyclerView instead of ListView. For more posts about this flexible widget, please visit this tag link. Finally, you can get full code by click at the button below.

Share


Previous post
« Prev Post
Next post
Next Post »