[UNMAINTAINED] Sticky Headers decorator for Android's RecyclerView

Last update: Aug 18, 2022

This project is no longer being maintained

sticky-headers-recyclerview

This decorator allows you to easily create section headers for RecyclerViews using a LinearLayoutManager in either vertical or horizontal orientation.

Credit to Emil Sjölander for creating StickyListHeaders, a library that many of us relied on for sticky headers in our listviews.

Here is a quick video of it in action (click to see the full video):

animated gif demo

animated gif demo

Download

Current version: Maven Central

compile 'com.timehop.stickyheadersrecyclerview:library:[latest.version.number]@aar'

Usage

There are three main classes, StickyRecyclerHeadersAdapter, StickyRecyclerHeadersDecoration, and StickyRecyclerHeadersTouchListener.

StickyRecyclerHeadersAdapter has a very similar interface to the RecyclerView.Adapter, and it is recommended that you make your RecyclerView.Adapter implement StickyRecyclerHeadersAdapter.

There interface looks like this:

public interface StickyRecyclerHeadersAdapter<VH extends RecyclerView.ViewHolder> {
  public long getHeaderId(int position);

  public VH onCreateHeaderViewHolder(ViewGroup parent);

  public void onBindHeaderViewHolder(VH holder, int position);

  public int getItemCount();
}

The second class, StickyRecyclerHeadersDecoration, is where most of the magic happens, and does not require any configuration on your end. Here's an example from onCreate() in an activity:

mRecyclerView = (RecyclerView) findViewById(R.id.recyclerview);
mAdapter = new MyStickyRecyclerHeadersAdapter();
mRecyclerView.setAdapter(mAdapter);
mRecyclerView.setLayoutManager(new LinearLayoutManager(context));
mRecyclerView.addItemDecoration(new StickyRecyclerHeadersDecoration(mAdapter));

StickyRecyclerHeadersTouchListener allows you to listen for clicks on header views. Simply create an instance of StickyRecyclerHeadersTouchListener, set the OnHeaderClickListener, and add the StickyRecyclerHeadersTouchListener as a touch listener to your RecyclerView.

StickyRecyclerHeadersTouchListener touchListener =
    new StickyRecyclerHeadersTouchListener(recyclerView, headersDecor);
touchListener.setOnHeaderClickListener(
    new StickyRecyclerHeadersTouchListener.OnHeaderClickListener() {
      @Override
      public void onHeaderClick(View header, int position, long headerId) {
        Toast.makeText(MainActivity.this, "Header position: " + position + ", id: " + headerId,
            Toast.LENGTH_SHORT).show();
      }
    });
mRecyclerView.addOnItemTouchListener(touchListener);

The StickyHeaders aren't aware of your adapter so if you must notify them when your data set changes.

    mAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
      @Override public void onChanged() {
        headersDecor.invalidateHeaders();
      }
    });

If the Recyclerview's layout manager implements getExtraLayoutSpace (to preload more content then is visible for performance reasons), you must implement ItemVisibilityAdapter and pass an instance as a second argument to StickyRecyclerHeadersDecoration's constructor.

    @Override
    public boolean isPositionVisible(final int position) {
        return layoutManager.findFirstVisibleItemPosition() <= position
            && layoutManager.findLastVisibleItemPosition() >= position;
    }

Item animators don't play nicely with RecyclerView decorations, so your mileage with that may vary.

Compatibility

API 11+

Known Issues

  • The header views aren't recycled at this time. Contributions are most welcome.

  • I haven't tested this with ItemAnimators yet.

  • The header views are drawn to a canvas, and are not actually a part of the view hierarchy. As such, they can't have touch states, and you may run into issues if you try to load images into them asynchronously.

Version History

0.4.3 (12/24/2015) - Change minSDK to 11, fix issue with header bounds caching

0.4.2 (8/21/2015) - Add support for reverse ReverseLayout in LinearLayoutManager by AntonPukhonin

0.4.1 (6/24/2015) - Fix "dancing headers" by DarkJaguar91

0.4.0 (4/16/2015) - Code reorganization by danoz73, fixes for different sized headers, performance improvements

0.3.6 (1/30/2015) - Prevent header clicks from passing on the touch event

0.3.5 (12/12/2014) - Add StickyRecyclerHeadersDecoration.invalidateHeaders() method

0.3.4 (12/3/2014) - Fix issues with rendering of header views with header ID = 0

0.3.3 (11/13/2014) - Fixes for padding, support views without headers

0.3.2 (11/1/2014) - Bug fixes for list items with margins and deleting items

0.2 (10/3/2014) - Add StickyRecyclerHeadersTouchListener

0.1 (10/2/2014) - Initial Release

GitHub

https://github.com/timehop/sticky-headers-recyclerview
Comments
  • 1. Structural Changes + Fix for padding support on RecyclerView

    I moved around some of the logic to do various things (rendering, position calculation, view creation/caching, etc). After the restructuring, I included a fix for supporting padding on the RecyclerView itself. You should just be able to run the demo and see the padding/margin.

    If you don't want to structure this code in this manner, I can just leave this in my own fork no problem.

    :gift_heart:

    Reviewed by danoz73 at 2015-02-23 07:57
  • 2. Bug when delete item

    I use your lib to try recycler view with sticky header, anything is work good, but when i delete item, sometimes it became mess and when i want multiple delete from the bottom of item yes that bug appear again.

    Reviewed by fjr619 at 2014-10-30 07:19
  • 3. Headers not updating when calling notifyDataSetChanged on adapter

    I use a cursorloader that updates the adapter data set by calling notifyDataSetChanged. All the cells that aren't sticky get updated, but not the sticky headers. In fact, onBindHeaderViewHolder doesn't get called at all.

    Reviewed by meilers at 2015-03-06 04:35
  • 4. Error loading in Fragment

    I have used this code successfully from my Main Activity in which I am loading data from my server onto the cards used in recycerview. Everything is working fine but now when I tried to shift the exact same code onto a fragment, it keeps giving me an error. I have changed all the getActivity to view.getContext() and made other appropriate changes in the import. Could someone help me out?

     Process: varun.brandlabs.com.zeko, PID: 13859
        android.view.InflateException: Binary XML file line #8: Error inflating class <unknown>
                at android.view.LayoutInflater.createView(LayoutInflater.java:633)
                at android.view.LayoutInflater.createViewFromTag(LayoutInflater.java:743)
                at android.view.LayoutInflater.rInflate(LayoutInflater.java:806)
                at android.view.LayoutInflater.inflate(LayoutInflater.java:504)
                at android.view.LayoutInflater.inflate(LayoutInflater.java:414)
                at varun.brandlabs.com.zeko.NewsFeed.MyStickyAdapter.onCreateHeaderViewHolder(MyStickyAdapter.java:53)
                at varun.brandlabs.com.zeko.NewsFeed.MyStickyAdapter.onCreateHeaderViewHolder(MyStickyAdapter.java:22)
                at com.timehop.stickyheadersrecyclerview.caching.HeaderViewCache.getHeader(HeaderViewCache.java:34)
                at com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersDecoration.getHeaderView(StickyRecyclerHeadersDecoration.java:138)
                at com.timehop.stickyheadersrecyclerview.StickyRecyclerHeadersDecoration.getItemOffsets(StickyRecyclerHeadersDecoration.java:64)
                at android.support.v7.widget.RecyclerView.getItemDecorInsetsForChild(RecyclerView.java:3583)
                at android.support.v7.widget.RecyclerView$LayoutManager.measureChildWithMargins(RecyclerView.java:6623)
                at android.support.v7.widget.LinearLayoutManager.layoutChunk(LinearLayoutManager.java:1385)
                at android.support.v7.widget.LinearLayoutManager.fill(LinearLayoutManager.java:1322)
                at android.support.v7.widget.LinearLayoutManager.onLayoutChildren(LinearLayoutManager.java:556)
                at android.support.v7.widget.RecyclerView.dispatchLayout(RecyclerView.java:2673)
                at android.support.v7.widget.RecyclerView.onLayout(RecyclerView.java:2971)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.support.v4.widget.SwipeRefreshLayout.onLayout(SwipeRefreshLayout.java:562)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.RelativeLayout.onLayout(RelativeLayout.java:1076)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.support.v4.view.ViewPager.onLayout(ViewPager.java:1626)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1705)
                at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1559)
                at android.widget.LinearLayout.onLayout(LinearLayout.java:1468)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
                at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1705)
                at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1559)
                at android.widget.LinearLayout.onLayout(LinearLayout.java:1468)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
                at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.LinearLayout.setChildFrame(LinearLayout.java:1705)
                at android.widget.LinearLayout.layoutVertical(LinearLayout.java:1559)
                at android.widget.LinearLayout.onLayout(LinearLayout.java:1468)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.widget.FrameLayout.layoutChildren(FrameLayout.java:573)
                at android.widget.FrameLayout.onLayout(FrameLayout.java:508)
                at android.view.View.layout(View.java:15631)
                at android.view.ViewGroup.layout(ViewGroup.java:4966)
                at android.view.ViewRootImpl.performLayout(ViewRootImpl.java:2101)
                at android.view.ViewRootImpl.performTraversals(View
    
    
    public class MyStickyAdapter extends RecyclerView.Adapter<MyStickyAdapter.ViewHolder>
            implements StickyRecyclerHeadersAdapter<RecyclerView.ViewHolder> {
        Context mContext;
    
        MyStickyAdapter(Context mContext){this.mContext=mContext;}
    
        public static class ViewHolder extends RecyclerView.ViewHolder{
            public TextView HeadView, Row;
            public CardView cardView; ImageView image;
    
            ViewHolder(final View view){
                super(view);
    
            }
        }
    
        @Override
        public long getHeaderId(int position) {
                return position;
        }
    
        @Override
        public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
            View v = LayoutInflater.from(viewGroup.getContext())
                    .inflate(R.layout.sample_feed_card, viewGroup, false);
            ViewHolder viewHolder = new ViewHolder(v);
            return viewHolder;
        }
    
        @Override
        public ViewHolder onCreateHeaderViewHolder(ViewGroup parent) {
            View v = LayoutInflater.from(parent.getContext())
                    .inflate(R.layout.sample_feed_header, parent, false);
            ViewHolder viewHolder=new ViewHolder(v);
            return viewHolder;
        }
    
        @Override
        public void onBindHeaderViewHolder(RecyclerView.ViewHolder holder, int position) {
    
        }
    
        @Override
        public void onBindViewHolder (ViewHolder holder, int i) {
    
        }
    
        @Override
        public int getItemCount() {
           return 4;
        }
        @Override
        public void onAttachedToRecyclerView(RecyclerView recyclerView) {
            super.onAttachedToRecyclerView(recyclerView);
        }
    
    }
    

    As of now the code above is using default values iv placed so the childs all look the same. 2) I keep loading data asynchronously, so i need to mAdapter.add(newData) and then mAdapter.notifyItemRangeChanged. Does that work efficiently with this?

    Reviewed by varunagarwal315 at 2015-08-16 14:42
  • 5. RecyclerView jumps when adding items on the top

    I have an autoloading (from top) list of items. When user scroll to top of the list - new items are added to the top.

    I need to preserve the scroll position. The desired behaviour is to keep all the items as they are and add more items to top.

    NOW when adding items to top - the list jumps a little up - thus placing the first item to the very top (not under header as it was).

    Could you please fix this asap.

    P.S. Thank you for the great library, works great.

    Reviewed by ealymbaev at 2015-10-26 11:08
  • 6. Get Position of HeaderView

    Hello Everyone,

    I want to open popup menu for Header Item. I am able to get HeaderView Touch event but not it's actual position on screen. I debug the code but findHeaderPositionUnder(int x, int y) has not updated Rect() of HeaderView. Is it possible to get?

    Reviewed by shivanic1986 at 2015-11-02 07:15
  • 7. Does hasStableIds=true in RecyclerArrayAdapter require a better getItemId() implementation?

    RecyclerArrayAdapter.java you set setHasStableIds(true) but then declare

    public long getItemId(int position) {
        return position;
      }
    

    Isn't that a contradiction?

    The method getItemId() is documented as follows

    Return the stable ID for the item at position. If #hasStableIds() would return false this method should return #NO_ID. The default implementation of this method returns #NO_ID @param position Adapter position to query @return the stable ID of the item at position

    and in hasStableIds() we have

    Returns true if this adapter publishes a unique long value that can act as a key for the item at a given position in the data set. If that item is relocated in the data set, the ID returned for that item should be the same. @return true if this adapter's items have stable IDs

    I think that as long we don't know a any structure of the elements, it is better to fall back to the default implementation returning NO_ID.

    Reviewed by webmaster128 at 2015-09-15 20:15
  • 8. Introduce a non sticky header switch

    This introduces the ability to change the header to a non sticky mode without breaking the api.

    To be used like this:

        final StickyRecyclerHeadersDecoration headersDecor = new StickyRecyclerHeadersDecoration(mAdapter);
        headersDecor.setHeadersSticky(false);
        recyclerView.addItemDecoration(headersDecor);
    

    I would kinda like to introduce a builder pattern and make a creator for StickyRecyclerHeadersDecoration.java. This would however break the api.

    recyclerView.addItemDecoration(StickyRecyclerHeadersDecoration.with(mAdapter)
                                                                  .setHeadersSticky(false);
    
    Reviewed by markini at 2015-09-02 12:02
  • 9. Dancing headers

    If you have items that update in the recycler view, the header item for the set that updates tends to dance around. Basically the header items moves to just above the item that has been updated and then moves back up to the correct position.

    Reviewed by DarkJaguar91 at 2015-06-04 07:32
  • 10. Extra spacing added every time the adapter is updated

    Every time the adapter is refreshed in this code, the padding between headers and sections is increased. This is not the intended result

    Here is the code I use to update the dataset. (ultimately notify data set is changed is called, the headerDecors are updated and the listview is reattached to the headers and the new adapter)

                                             if(mPerf == null)
                                                    mPerf = new PerformanceAdapter(list, context);
                                                else{
                                                    mPerf.updateList(list);
                                                }
                                                recList.removeItemDecoration(headersDecor);
    
                                                if(headersDecor!=null){
                                                    headersDecor.invalidateHeaders();
                                                }
                                                headersDecor = new StickyRecyclerHeadersDecoration(mPerf);
                                                recList.addItemDecoration(headersDecor);
                                                recList.setAdapter(mPerf);
    
    Reviewed by ericlw at 2015-06-18 01:49
  • 11. Don't propagate touch to items behind a header (update StickyRecyclerHeadersTouchListener)

    When a sticky header is touched, a regular item behind it may receive the ACTION_DOWN event, and display "pressed" state. This commit fixes such undesired behavior by returning true for onInterceptTouchEvent() if it was DOWN inside the borders of a sticky header.

    Reviewed by alexcohn at 2015-09-01 16:38
  • 12. How to adjust position of headers in horizontal layout? Currently it's covering up content!

    Hi. Currently my headers are covering the content of the layout I'm using. How can i reposition them to a bit higher? What part of code do i need to edit? image

    Reviewed by FahadSaleem at 2021-07-15 08:53
  • 13. Only last header is visible

    The issue reproduces periodically without any rules. onCreateHeaderViewHolder and onBindHeaderViewHolder gets called, but headers are not visible My code looks like

    override fun getHeaderId(position: Int): Long {
        return clients[position].companyName.toUpperCase().get(0).toLong()
    }
    
    override fun onCreateHeaderViewHolder(parent: ViewGroup): HeaderHolder {
        val view = LayoutInflater.from(parent.context).inflate(R.layout.item_client_list_header, parent, false)
         return HeaderHolder(view)
    }
    
    override fun onBindHeaderViewHolder(holder: HeaderHolder, position: Int) {
        val letter = clients[position].companyName.get(0).toUpperCase().toString()
        holder.bind(letter)
    }
    

    and xml:

    <?xml version="1.0" encoding="utf-8"?>
    <TextView xmlns:tools="http://schemas.android.com/tools"
        xmlns:android="http://schemas.android.com/apk/res/android"
        style="@style/SansMediumBig"
        tools:text="A"
        android:layout_width="match_parent"
        android:padding="@dimen/padding_small"
        android:background="@color/transparent_grey_3"/>
    

    But no matter what font or any other style (even without any style) I'm using - result is the same. Note that I'm using this recycle view https://github.com/myinnos/AlphabetIndex-Fast-Scroll-RecyclerView Help me please :(

    Reviewed by graisVictory at 2019-02-04 09:24
  • 14. Showing duplicates data item inside recyclerview

    • OnBindViewHolder is getting called only for initial 5 items while I have 50 items in the list.
    • Because of this, those 5 items are getting repeated to accomplish 50 counts and showing duplicate data.
    Reviewed by mtariquekhan at 2018-12-28 06:20
  • 15. Problem of header click event when the data set changed

    Hello, project owner, I recently used your library in my project. It runs perfectly when there is no click event.

    But when I added the click event to header view, after the data set changed, the item may perform unexpected action -- the same as clicking the header.

    I guess this may occurred due to the cached views, so is there any method to solve this problem ? If this is one known issue, I'd be glad to make contribution to this project.

    Reviewed by Shouheng88 at 2018-09-11 16:15
ItemDecorator - Custom item decorator for adding divider for only the first item of a RecyclerView

ItemDecorator Custom item decorator for adding divider for only the first item o

Apr 1, 2022
A couple of sticky header decorations for android's recycler view.
A couple of sticky header decorations for android's recycler view.

DEPRECATION NOTICE This library has not been touched in a very long time and many things have changed in the android ecosystem since then. Updating an

Aug 8, 2022
An adapter to create Android RecyclerViews with sections, providing headers and footers.
An adapter to create Android RecyclerViews with sections, providing headers and footers.

⚠ This library is no longer maintained ⚠️ SectionedRecyclerView An adapter to create Android RecyclerViews with sections, providing headers and footer

Aug 17, 2022
A couple of sticky header decorations for android's recycler view.
A couple of sticky header decorations for android's recycler view.

DEPRECATION NOTICE This library has not been touched in a very long time and many things have changed in the android ecosystem since then. Updating an

Aug 8, 2022
A RecyclerView that implements pullrefresh and loadingmore featrues.you can use it like a standard RecyclerView
A RecyclerView that implements pullrefresh and loadingmore featrues.you can use it like a standard RecyclerView

XRecyclerView a RecyclerView that implements pullrefresh , loadingmore and header featrues.you can use it like a standard RecyclerView. you don't need

Aug 9, 2022
ANDROID. ChipsLayoutManager (SpanLayoutManager, FlowLayoutManager). A custom layout manager for RecyclerView which mimicric TextView span behaviour, flow layouts behaviour with support of amazing recyclerView features
ANDROID. ChipsLayoutManager (SpanLayoutManager, FlowLayoutManager). A custom layout manager for RecyclerView which mimicric TextView span behaviour, flow layouts behaviour with support of amazing recyclerView features

ChipsLayoutManager This is ChipsLayoutManager - custom Recycler View's LayoutManager which moves item to the next line when no space left on the curre

Aug 9, 2022
A RecyclerView that implements pullrefresh and loadingmore featrues.you can use it like a standard RecyclerView
A RecyclerView that implements pullrefresh and loadingmore featrues.you can use it like a standard RecyclerView

XRecyclerView a RecyclerView that implements pullrefresh , loadingmore and header featrues.you can use it like a standard RecyclerView. you don't need

Aug 12, 2022
Carousel Recyclerview let's you create carousel layout with the power of recyclerview by creating custom layout manager.
Carousel Recyclerview let's you create carousel layout with the power of recyclerview by creating custom layout manager.

Carousel Recyclerview let's you create carousel layout with the power of recyclerview by creating custom layout manager.

Aug 9, 2022
RecyclerView : SleepQualityTracker with RecyclerView app
RecyclerView : SleepQualityTracker with RecyclerView app

RecyclerView - SleepQualityTracker with RecyclerView app SleepQualityTracker with RecyclerView This app builds on the SleepQualityTracker developed pr

May 14, 2022
TikTok-RecyclerView - This is a demo app built using 'Koin' a new dependency injection framework for Android along with RecyclerView and ExoPlayer2.
TikTok-RecyclerView - This is a demo app built using 'Koin' a new dependency injection framework for Android along with RecyclerView and ExoPlayer2.

TikTok-RecyclerView Demo About This is a demo app built using 'Koin' a new dependency injection framework for Android along with RecyclerView and ExoP

Jun 16, 2022
Pagination-RecyclerView - Simple and easy way to Paginating a RecyclerView
Pagination-RecyclerView - Simple and easy way to Paginating a RecyclerView

Pagination-RecyclerView Simple and easy way to Paginating a RecyclerView Android

Jan 3, 2022
An Android Animation library which easily add itemanimator to RecyclerView items.
An Android Animation library which easily add itemanimator to RecyclerView items.

RecyclerView Animators RecyclerView Animators is an Android library that allows developers to easily create RecyclerView with animations. Please feel

Aug 9, 2022
A RecyclerView(advanced and flexible version of ListView in Android) with refreshing,loading more,animation and many other features.
A RecyclerView(advanced and flexible version of ListView in Android) with refreshing,loading more,animation and many other features.

UltimateRecyclerView Master branch: Dev branch: Project website:https://github.com/cymcsg/UltimateRecyclerView Description UltimateRecyclerView is a R

Aug 4, 2022
[] RecyclerView made simple
[] RecyclerView made simple

TwoWayView RecyclerView made simple. Features A LayoutManager base class that greatly simplifies the development of custom layouts for RecyclerView A

Aug 18, 2022
RecyclerView extension library which provides advanced features. (ex. Google's Inbox app like swiping, Play Music app like drag and drop sorting)
RecyclerView extension library which provides advanced features. (ex. Google's Inbox app like swiping, Play Music app like drag and drop sorting)

Advanced RecyclerView This RecyclerView extension library provides Google's Inbox app like swiping, Play Music app like drag-and-drop sorting and expa

Aug 11, 2022
Pumped up RecyclerView

##Description This is an attempt to make RecyclerView easier to use. Features built in: ProgressBar while adapter hasn't been set EmptyView if adapter

Aug 7, 2022
Android library providing simple way to control divider items (ItemDecoration) of RecyclerView
Android library providing simple way to control divider items (ItemDecoration) of RecyclerView

RecyclerView-FlexibleDivider Android library providing simple way to control divider items of RecyclerView Release Note [Release Note] (https://github

Aug 11, 2022
[] Super fast and easy way to create header for Android RecyclerView

DEPRECATED I created this library back in the day when I thought RecyclerView was all new and difficult. Writing an adapter that could inflate multipl

Aug 13, 2022