RecyclerView header and footer

81,416

Solution 1

in your adapter add this class:

private class VIEW_TYPES {
        public static final int Header = 1;
        public static final int Normal = 2;
        public static final int Footer = 3;
}

then Override the following method like this:

@Override
public int getItemViewType(int position) {

    if(items.get(position).isHeader)
        return VIEW_TYPES.Header;
    else if(items.get(position).isFooter)
        return VIEW_TYPES.Footer;
    else
        return VIEW_TYPES.Normal;

}

Now in the onCreateViewHolder method inflate your layout based on the view type::

@Override
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {

    View rowView;

    switch (i) {

        case VIEW_TYPES.Normal:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.normal, viewGroup, false);
            break;
        case VIEW_TYPES.Header:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.header, viewGroup, false);
            break;
        case VIEW_TYPES.Footer:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.footer, viewGroup, false);
            break;
        default:
            rowView = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.normal, viewGroup, false);
            break;
    }
    return new ViewHolder (rowView);
}

Now in the onBindViewHolder method bind your layout based on the view holder:

@Override
    public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) {

        int viewType = getItemViewType(position);

        switch(viewType) {

            case VIEW_TYPES.Header: // handle row header
                break;
            case VIEW_TYPES.Footer: // handle row footer
                break;
            case VIEW_TYPES.Normal: // handle row item
                break;

        }

    }

Hope this can help.

Solution 2

This is very easy with ItemDecorations and without modifying any other code:

recyclerView.addItemDecoration(new HeaderDecoration(this,
                               recyclerView,  R.layout.test_header));

Reserve some space for drawing, inflate the layout you want drawn and draw it in the reserved space.

The code for the Decoration:

public class HeaderDecoration extends RecyclerView.ItemDecoration {

    private View mLayout;

    public HeaderDecoration(final Context context, RecyclerView parent, @LayoutRes int resId) {
        // inflate and measure the layout
        mLayout = LayoutInflater.from(context).inflate(resId, parent, false);
        mLayout.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
                View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
    }


    @Override
    public void onDraw(Canvas c, RecyclerView parent, RecyclerView.State state) {
        super.onDraw(c, parent, state);
        // layout basically just gets drawn on the reserved space on top of the first view
        mLayout.layout(parent.getLeft(), 0, parent.getRight(), mLayout.getMeasuredHeight());
        for (int i = 0; i < parent.getChildCount(); i++) {
            View view = parent.getChildAt(i);
            if (parent.getChildAdapterPosition(view) == 0) {
                c.save();
                final int height = mLayout.getMeasuredHeight();
                final int top = view.getTop() - height;
                c.translate(0, top);
                mLayout.draw(c);
                c.restore();
                break;
            }
        }
    }

    @Override
    public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
        if (parent.getChildAdapterPosition(view) == 0) {
            outRect.set(0, mLayout.getMeasuredHeight(), 0, 0);
        } else {
            outRect.setEmpty();
        }
    }
}

Solution 3

If all you need is a blank header and footer, here is a very simple way to achieve this (written in Kotlin):

class HeaderFooterDecoration(private val headerHeight: Int, private val footerHeight: Int) : RecyclerView.ItemDecoration() {
    override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
        val adapter = parent.adapter ?: return
        when (parent.getChildAdapterPosition(view)) {
            0 -> outRect.top = headerHeight
            adapter.itemCount - 1 -> outRect.bottom = footerHeight
            else -> outRect.set(0, 0, 0, 0)
        }
    }
}

Call it this way:

recyclerView.addItemDecoration(HeaderFooterDecoration(headerHeightPx, footerHeightPx))

Solution 4

You can use this GitHub] library to add a Header or Footer to your RecyclerView in the simplest way possible.

You need to add the HFRecyclerView library in your project or you can also grab it from Gradle:

compile 'com.mikhaellopez:hfrecyclerview:1.0.0'

This library is based on a work at @hister

This is a result in image:

Preview

Solution 5

recyclerview:1.2.0 introduces ConcatAdapter

ConcatAdapter is a new RecyclerView Adapter that can combine multiple adapters linearly.

How to use ConcatAdapter?

add following dependency into your build.gradle file

androidx.recyclerview:recyclerview:1.2.0-alpha04

Then, if you have multiple adapters, you can easily merge them using

  MyAdapter adapter1 = ...;
 AnotherAdapter adapter2 = ...;
 ConcatAdapter merged = new ConcatAdapter(adapter1, adapter2);
 recyclerView.setAdapter(merged);

For the sample above, ConcatAdapter will present items from adapter1 followed by adapter2.

Here you can find the complete documentation.

Find complete working sample here.

Read this article for more info.

Here you can find the source code.

Share:
81,416
Sandra
Author by

Sandra

Updated on July 08, 2022

Comments

  • Sandra
    Sandra almost 2 years

    Maybe this question has been asked before, but I could not seem to find a precise answer or solution. I started using the RecyclerView, and I implemented it using the LinearLayoutManager. Now I want to add custom header and footer items, that differ from the rest of the items in my RecyclerView. The header and footer should not be sticky, I want them to scroll with the rest of the items. Can somebody point out some example how to do this or just share ideas. I will appreciate it very much. Thx

  • AndroidDev
    AndroidDev about 9 years
    I could implement the header fine but when I added the footer its getting inserted after the first item of the Recycler view though I specified the Footer attribute as "layout_alignParentBottom=true". Any idea what might be the reason?
  • Bronx
    Bronx about 9 years
    Hi, header and footer are item too, so you have to add the header first (checking that it is in the first position) then add your items and finally in the last position add the footer
  • Ronak Joshi
    Ronak Joshi almost 9 years
    Thanks for this solution but can you tell me where is the methods name, "isHeader" and "isFooter" ? @Bronx
  • Bronx
    Bronx almost 9 years
    Hi @RonakJoshi isHeader and isFooter are not methods, they are variables you have in the items of your list. For example you have a class named MyItem and the constructor is public MyItem(Object myObject, boolean isHeader, boolean isFooter)
  • Ronak Joshi
    Ronak Joshi almost 9 years
    Yes,Got it. Thanks @Bronx +1 for your solution.
  • chin87
    chin87 over 8 years
    how can you add click handling in item decorator? I have to add a button as header, its can be seen with your code but I am unable to add on click as findViewById return null
  • David Medenjak
    David Medenjak over 8 years
    @chin87 ...since mLayout is a view - just add it to the view? Either add a getter or modify the constructor. (And it depends where you call findViewById...Since the view is neither attached to the recyclerView nor to the fragment)
  • Martin Erlic
    Martin Erlic over 8 years
    I keep getting 'cannot resolve symbol .isHeader'. Also 'method call expected' for 'return new ViewHolder (rowView);' The solution doesn't work for me.
  • Bronx
    Bronx over 8 years
    Hi @bluemunch, check my previous answer to RonakJoshi, isHeader is a parameter you must have in your item class. This was an example, you can use whatever logic you want to choose which item is the header or the footer.
  • android developer
    android developer over 8 years
    How do you use it to add a footer? Will it also work when LinearLayoutManager is horizontal?
  • David Medenjak
    David Medenjak over 8 years
    @androiddeveloper you would have to modify or adapt the code, this sample is more proof of concept. to use it as footer you would check for last item rather than position == 0 and draw beneath instead of above, for using it horizontally, you would also need to adapt it, modifying the drawing for left/right instead of above
  • android developer
    android developer over 8 years
    @DavidMedenjak That's too bad. Do you know perhaps of a repo that has them all? Are there any disadvantages of using your solution, other than the need of extra code for each header&footer for horizontal&vertical
  • David Medenjak
    David Medenjak over 8 years
    @androiddeveloper The only real downside is, that it is just a decoration. You won't be able to add onClick to it. I don't know of any full implementations, but modifying the code for all 4 cases is not too hard. You can find the repo that I play around with including more samples at github.com/bleeding182/recyclerviewItemDecorations
  • android developer
    android developer over 8 years
    @DavidMedenjak Why can't I add onClick to it? because it has no real view, as it only draws stuff? Is it a lot of changes to the current code? Maybe it's better to just add parameters to the decorator, instead of creating new, similar classes.
  • android developer
    android developer over 8 years
    @DavidMedenjak Also, suppose I want to remove the header at some point, will it have a nice animation like of normal items of the RecyclerView (when they have an id) ? Or will it just disappear right away?
  • David Medenjak
    David Medenjak over 8 years
    @androiddeveloper you don't get any animation for free. you can use the childs alpha and translation to animate with the child, I wrote an article on that here: bleeding182.blogspot.com/2015/11/…
  • android developer
    android developer over 8 years
    @DavidMedenjak The article is about normal items. You mean the same holds for decorators too?
  • David Medenjak
    David Medenjak over 8 years
  • Dima
    Dima about 8 years
    Very helpful? but I faced of with another problem. I've added progress bar in header but it animates only when I move list or call notifydatasetchange()
  • David Medenjak
    David Medenjak about 8 years
    @Dima Yes. This is not a classic View, it's just some drawing on top. If you change it you will have to find a way to invalidate it yourself and update the recyclerview. The main use case of the presented answer is to show some image or text as a header
  • X09
    X09 about 7 years
    @Bronx Adding the link to that your other answer will be nice though.
  • X09
    X09 about 7 years
    @Bronx or add the full code because I am still having the same issue santafebound is having.
  • Andy
    Andy over 6 years
    I've attempted your answer but I can't get it to work. How simple is your R.layout.test_header? The layout I am attempting to add is a bit more complex. LinearLayout and stuff. Is this not possible?
  • David Medenjak
    David Medenjak over 6 years
    @Andy it really depends. Try using a fixed height/width and see if that helps, maybe it can't be properly measured/layouted. But I'd try to keep it simple
  • Leonid Ustenko
    Leonid Ustenko over 6 years
    I use a TextView as a footer layout and it makes no new lines, just draws single line textview which goes out of bounds
  • lenhuy2106
    lenhuy2106 almost 6 years
    i didn't find a way to create a footer yet.
  • lenhuy2106
    lenhuy2106 almost 6 years
    If someones looking for the footer version: gist.github.com/dreiklangdev/4b092eade09feb26bdf1c090fd2e564‌​3
  • lenhuy2106
    lenhuy2106 almost 6 years
    @LeoDroidcoder did you try wrap it in a e.g. frameLayout?
  • Kathir
    Kathir over 5 years
    Okay, your approach is straightforward. But, isn't it a waste of memory to have the view as View.GONE for most of the time?
  • Raktim Bhattacharya
    Raktim Bhattacharya over 5 years
    @Kathir...it is processed only when its visible in the screen area...other time it is treated and kept in background as usual array of records to be shown..
  • Kathir
    Kathir over 5 years
    Thank you @Raktim. I found a relevant question answering that. In short, it still takes up memory comparing to what not having that view would be consuming but it's better than View.INVISIBLE. Also, read this to understand when to use View.INVISIBLE instead
  • AMAN77
    AMAN77 almost 5 years
    Not very often that I find the best solution way down here. Short Concise, readable, gets the job done. Full Marks Sean :D
  • Amar Yadav
    Amar Yadav almost 4 years
    A very good example with source code to create RecyclerView with header and footer loopwiki.com/ui-ux-design/…
  • Anurag Bhalekar
    Anurag Bhalekar over 3 years
    Your code came out really handy, but I tried modifying it for footer by changing parent.getChildAdapterPosition(view) == 0 to parent.getChildAdapterPosition(view) == parent.getAdapter().getItemCount() But it didn;t work. Well, it does work for any other position in between, but not the last one, could you suggest any changes?
  • Umberto Covino
    Umberto Covino over 3 years
    Thanks a lot @Trunks! In my case I have added a simple header containing only a TextView in this way: recyclerView.addItemDecoration(new HeaderItemDecoration(aTextView), 0).
  • meekash55
    meekash55 almost 3 years
    seems promising. But need a full code. as Such I'm using Kotlin and didn't find items.get(position).isHeader (isHeader) property here. As well I am looking to put "Load more" button at the footer. So on click event also need to handle somewhere. Please help!
  • Driss Bounouar
    Driss Bounouar over 2 years
    @AnuragBhalekar it's parent.getChildAdapterPosition(view) == (parent.getAdapter().getItemCount()-1)