163

I want to fix my header views in the top of the screen like in the image below and without using external libraries.

enter image description here

In my case, I don't want to do it alphabetically. I have two different types of views (Header and normal). I only want to fix to the top, the last header.

Tonechas
  • 13,398
  • 16
  • 46
  • 80
Jaume Colom
  • 1,701
  • 3
  • 11
  • 6

11 Answers11

390

Here I will explain how to do it without an external library. It will be a very long post, so brace yourself.

First of all, let me acknowledge @tim.paetz whose post inspired me to set off to a journey of implementing my own sticky headers using ItemDecorations. I borrowed some parts of his code in my implementation.

As you might have already experienced, if you attempted to do it yourself, it is very hard to find a good explanation of HOW to actually do it with the ItemDecoration technique. I mean, what are the steps? What is the logic behind it? How do I make the header stick on top of the list? Not knowing answers to these questions is what makes others to use external libraries, while doing it yourself with the use of ItemDecoration is pretty easy.

Initial conditions

  1. You dataset should be a list of items of different type (not in a "Java types" sense, but in a "header/item" types sense).
  2. Your list should be already sorted.
  3. Every item in the list should be of certain type - there should be a header item related to it.
  4. Very first item in the list must be a header item.

Here I provide full code for my RecyclerView.ItemDecoration called HeaderItemDecoration. Then I explain the steps taken in detail.

public class HeaderItemDecoration extends RecyclerView.ItemDecoration {

 private StickyHeaderInterface mListener;
 private int mStickyHeaderHeight;

 public HeaderItemDecoration(RecyclerView recyclerView, @NonNull StickyHeaderInterface listener) {
  mListener = listener;

  // On Sticky Header Click
  recyclerView.addOnItemTouchListener(new RecyclerView.OnItemTouchListener() {
   public boolean onInterceptTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {
    if (motionEvent.getY() <= mStickyHeaderHeight) {
     // Handle the clicks on the header here ...
     return true;
    }
    return false;
   }

   public void onTouchEvent(RecyclerView recyclerView, MotionEvent motionEvent) {

   }

   public void onRequestDisallowInterceptTouchEvent(boolean disallowIntercept) {

   }
  });
 }

 @Override
 public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
  super.onDrawOver(c, parent, state);

  View topChild = parent.getChildAt(0);
  if (Util.isNull(topChild)) {
   return;
  }

  int topChildPosition = parent.getChildAdapterPosition(topChild);
  if (topChildPosition == RecyclerView.NO_POSITION) {
   return;
  }

  View currentHeader = getHeaderViewForItem(topChildPosition, parent);
  fixLayoutSize(parent, currentHeader);
  int contactPoint = currentHeader.getBottom();
  View childInContact = getChildInContact(parent, contactPoint);
  if (Util.isNull(childInContact)) {
   return;
  }

  if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) {
   moveHeader(c, currentHeader, childInContact);
   return;
  }

  drawHeader(c, currentHeader);
 }

 private View getHeaderViewForItem(int itemPosition, RecyclerView parent) {
  int headerPosition = mListener.getHeaderPositionForItem(itemPosition);
  int layoutResId = mListener.getHeaderLayout(headerPosition);
  View header = LayoutInflater.from(parent.getContext()).inflate(layoutResId, parent, false);
  mListener.bindHeaderData(header, headerPosition);
  return header;
 }

 private void drawHeader(Canvas c, View header) {
  c.save();
  c.translate(0, 0);
  header.draw(c);
  c.restore();
 }

 private void moveHeader(Canvas c, View currentHeader, View nextHeader) {
  c.save();
  c.translate(0, nextHeader.getTop() - currentHeader.getHeight());
  currentHeader.draw(c);
  c.restore();
 }

 private View getChildInContact(RecyclerView parent, int contactPoint) {
  View childInContact = null;
  for (int i = 0; i < parent.getChildCount(); i++) {
   View child = parent.getChildAt(i);
   if (child.getBottom() > contactPoint) {
    if (child.getTop() <= contactPoint) {
     // This child overlaps the contactPoint
     childInContact = child;
     break;
    }
   }
  }
  return childInContact;
 }

 /**
  * Properly measures and layouts the top sticky header.
  * @param parent ViewGroup: RecyclerView in this case.
  */
 private void fixLayoutSize(ViewGroup parent, View view) {

  // Specs for parent (RecyclerView)
  int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY);
  int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(), View.MeasureSpec.UNSPECIFIED);

  // Specs for children (headers)
  int childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.getPaddingLeft() + parent.getPaddingRight(), view.getLayoutParams().width);
  int childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.getPaddingTop() + parent.getPaddingBottom(), view.getLayoutParams().height);

  view.measure(childWidthSpec, childHeightSpec);

  view.layout(0, 0, view.getMeasuredWidth(), mStickyHeaderHeight = view.getMeasuredHeight());
 }

 public interface StickyHeaderInterface {

  /**
   * This method gets called by {@link HeaderItemDecoration} to fetch the position of the header item in the adapter
   * that is used for (represents) item at specified position.
   * @param itemPosition int. Adapter's position of the item for which to do the search of the position of the header item.
   * @return int. Position of the header item in the adapter.
   */
  int getHeaderPositionForItem(int itemPosition);

  /**
   * This method gets called by {@link HeaderItemDecoration} to get layout resource id for the header item at specified adapter's position.
   * @param headerPosition int. Position of the header item in the adapter.
   * @return int. Layout resource id.
   */
  int getHeaderLayout(int headerPosition);

  /**
   * This method gets called by {@link HeaderItemDecoration} to setup the header View.
   * @param header View. Header to set the data on.
   * @param headerPosition int. Position of the header item in the adapter.
   */
  void bindHeaderData(View header, int headerPosition);

  /**
   * This method gets called by {@link HeaderItemDecoration} to verify whether the item represents a header.
   * @param itemPosition int.
   * @return true, if item at the specified adapter's position represents a header.
   */
  boolean isHeader(int itemPosition);
 }
}

Business logic

So, how do I make it stick?

You don't. You can't make a RecyclerView's item of your choice just stop and stick on top, unless you are a guru of custom layouts and you know 12,000+ lines of code for a RecyclerView by heart. So, as it always goes with the UI design, if you can't make something, fake it. You just draw the header on top of everything using Canvas. You also should know which items the user can see at the moment. It just happens, that ItemDecoration can provide you with both the Canvas and information about visible items. With this, here are basic steps:

  1. In onDrawOver method of RecyclerView.ItemDecoration get the very first (top) item that is visible to the user.

        View topChild = parent.getChildAt(0);
    
  2. Determine which header represents it.

            int topChildPosition = parent.getChildAdapterPosition(topChild);
        View currentHeader = getHeaderViewForItem(topChildPosition, parent);
    
  3. Draw the appropriate header on top of the RecyclerView by using drawHeader() method.

I also want to implement the behavior when the new upcoming header meets the top one: it should seem as the upcoming header gently pushes the top current header out of the view and takes his place eventually.

Same technique of "drawing on top of everything" applies here.

  1. Determine when the top "stuck" header meets the new upcoming one.

            View childInContact = getChildInContact(parent, contactPoint);
    
  2. Get this contact point (that is the bottom of the sticky header your drew and the top of the upcoming header).

            int contactPoint = currentHeader.getBottom();
    
  3. If the item in the list is trespassing this "contact point", redraw your sticky header so its bottom will be at the top of the trespassing item. You achieve this with translate() method of the Canvas. As the result, the starting point of the top header will be out of visible area, and it will seem as "being pushed out by the upcoming header". When it is completely gone, draw the new header on top.

            if (childInContact != null) {
            if (mListener.isHeader(parent.getChildAdapterPosition(childInContact))) {
                moveHeader(c, currentHeader, childInContact);
            } else {
                drawHeader(c, currentHeader);
            }
        }
    

The rest is explained by comments and thorough annotations in piece of code I provided.

The usage is straight forward:

mRecyclerView.addItemDecoration(new HeaderItemDecoration((HeaderItemDecoration.StickyHeaderInterface) mAdapter));

Your mAdapter must implement StickyHeaderInterface for it to work. The implementation depends on the data you have.

Finally, here I provide a gif with a half-transparent headers, so you can grasp the idea and actually see what is going on under the hood.

Here is the illustration of "just draw on top of everything" concept. You can see that there are two items "header 1" - one that we draw and stays on top in a stuck position, and the other one that comes from the dataset and moves with all the rest items. The user won't see the inner-workings of it, because you'll won't have half-transparent headers.

"just draw on top of everything" concept

And here what happens in the "pushing out" phase:

"pushing out" phase

Hope it helped.

Edit

Here is my actual implementation of getHeaderPositionForItem() method in the RecyclerView's adapter:

@Override
public int getHeaderPositionForItem(int itemPosition) {
    int headerPosition = 0;
    do {
        if (this.isHeader(itemPosition)) {
            headerPosition = itemPosition;
            break;
        }
        itemPosition -= 1;
    } while (itemPosition >= 0);
    return headerPosition;
}

Slightly different implementation in Kotlin

svkaka
  • 3,942
  • 2
  • 31
  • 55
Sevastyan Savanyuk
  • 5,797
  • 4
  • 22
  • 34
  • @Sevastyan Thanks for the answer. It works well!!! But I wanted to know what's your logic for finding HeaderPositionForItem inside the adapter. – Hitesh Jun 21 '17 at 21:37
  • @Hitesh It may seem a little redundant, but I decided to keep it in order to guarantee that the value passed to methods `getHeaderLayout` and `bindHeaderData` is the position of the header we are interested in. This makes implementation straight forward. e.g. your `bindHeaderData` implementation may look something like this: `YourDataItemType dataItem = mProvider.getItem(headerPosition); header.bind(dataItem);`. As you see, we do not need to check if the `dataItem` is actually representing data for a header - the method signature already guarantees that. – Sevastyan Savanyuk Jun 22 '17 at 06:00
  • @Sevastyan I see that the bindHeaderData has the position of the header but to calculate that you use HeaderPositionForItem method, and to find the header position for the current item, do you loop through all elements to find it? – Hitesh Jun 22 '17 at 15:18
  • 8
    @Sevastyan Just brilliant! I really liked the way you solved this challenge. Nothing to say, except maybe one question : Is there a way to set an OnClickListener on the "sticky header", or at least to consume the click preventing the user from clicking through it? – Greelings Jun 30 '17 at 16:23
  • @Denis I think that's doable. I'll get to it, when I have some spare time. Or you can offer an edit. :-) – Sevastyan Savanyuk Jun 30 '17 at 16:27
  • Thank you for this solution! But I found a small bug. I also use another ItemDivider between items, when one of these is top, the header disappears. Do you have any solution for this already? Otherwise I need to develop one myself in a later stadium – Geert Berkers Aug 30 '17 at 14:08
  • Thanks for your bug report. Unfortunately, I do not have a solution. Will look into this, but not in the nearest future. – Sevastyan Savanyuk Aug 30 '17 at 17:56
  • @Sevastyan - do you have a working sample of this? I'm not sure what do I need to provide in my adapter? – Desolator Aug 30 '17 at 20:20
  • 23
    Would be great if you put adapter example of this implementation – Desolator Aug 30 '17 at 20:23
  • I used a tricky solution because I wanted to finish it fast, it is working now, but I will check into this later again. I removed the other DividerItemDecoration and added a small view (width= match parent, height = 1dp, background = "?android:attr/dividerVertical"). This is not a proper solution, but for now it works. I'll let you know when I find something else – Geert Berkers Aug 31 '17 at 07:13
  • @GeertBerkers Thanks for sharing. Since my answer got popularity, I will try to revisit it soon and refactor it with regards to the proposed fixes. – Sevastyan Savanyuk Aug 31 '17 at 07:21
  • 1
    I finally made it to work with few tweaks here and there. although if you add any padding to your items it'll keep on flickering whenever you scroll to the padded area. the solution in your item's layout create a parent layout with 0 padding and a child layout with whatever padding you want. – Desolator Aug 31 '17 at 22:36
  • 1
    I found a bug that's affecting onDrawOver. when you add onClickListener to the items and these items contain animation (or show hide effects) it just changes the value of the sticky header to the item you just clicked and put the original back (it takes a second till it fixes itself). I've already applied a fix. use layoutManager.findFirstVisibleItemPosition() instead of parent.getChildAdapterPosition(topChild) – Desolator Sep 22 '17 at 02:08
  • 1
    @Sevastyan: nice work... but please put adapter example of this implementation :) – Rgv Oct 16 '17 at 09:21
  • @GeertBerkers I also stumbled upon the same issue when there was a DividerItemDecoration already added to the view. The childInContact is returned as null when the divider comes into contact with the currentHeader. So I fixed it inside the corresponding condition block in onDrawOver(). Please see below code – Ashok Oct 29 '17 at 12:56
  • 2
    `if (childInContact == null) { // Check if there is another child some px after the divider if(getChildInContact(parent, contactPoint + 15) != null) { drawHeader(c, currentHeader); return; } return; }` – Ashok Oct 29 '17 at 12:59
  • 1
    @Sevastyan Hats off for your crystal clear explanation of the implementation steps. Thanks :) – Ashok Oct 29 '17 at 13:02
  • @Sevastyan great solution! but what if my header has a bunch of buttons and I need to intercept the click of each one? – Silvia H Oct 30 '17 at 01:37
  • 11
    Thanks. Interesting solution, but a bit expensive to inflate the header view on every scroll event. I just changed the logic and use ViewHolder and keep them in a HashMap of WeakReferences to reuse already inflated views. – Michael Nov 01 '17 at 17:11
  • I fail to understand why this answer was created at all. The author, Sevastyan, freely admits they were "inspired" by @tim.paetz answer. Why not just improve the existing answer by tim.paetz to add these implementation steps? Now there are two answers that essentially say the same thing and none of which are accepted. This is very messy and benefits the community little and the individual making new answers greatly. – gtzilla Nov 14 '17 at 16:42
  • Really nice post. I just wanted to ask that, do you have any clue of how to use it with RecyclerViewCursorAdapter ? – Tabish Hussain Feb 05 '18 at 17:51
  • Thanks. I assume you are talking about `RecyclerViewCursorAdapter` from some library. Unfortunately, as a relative Android dev beginner, I'm trying to avoid any libraries as much as I can, hence, not familiar with it. I think reading the source code for `RecyclerViewCursoAdapter` is a best place to start. – Sevastyan Savanyuk Feb 05 '18 at 20:21
  • Would it be too much to ask for a horizontal layout example as well? – SudoPlz Apr 05 '18 at 16:23
  • Are the section headers different view types in the adapter or does it get drawn as part of the ItemDecorator, I mean header 2, header 3? – sat Apr 13 '18 at 22:25
  • 1
    @sat ALL the section headers are different view types. See initial condition #1. We draw only on the very top of the list and it is not even an `ItemDecorator`. I use `ItemDecorator` to get to `Canvas` and info about visible items. – Sevastyan Savanyuk Apr 14 '18 at 07:01
  • I think the method fixLayoutSize() is redundant in this case. In the original examples, they only used this method because they needed to inflate the header layout in this ItemDecoration class. But, in your example, header is instantiated in RecyclerView already, we don't need to call measure again when drawing. – katie Apr 23 '18 at 14:46
  • @katie Hmm....interesting.... Knowing how insanely complicated layout and view inflation is, and considering my exiguous knowledge of it, maybe you are right.... – Sevastyan Savanyuk Apr 24 '18 at 06:37
  • @SAndroidD Will not do, cause I don't see the point of sharing full code. My post answers the original question (and to a good extent, I should note). Besides, this is not "full-code-sharing" platform. Also the project I've implemented it in is live and I'm no longer working on it. And of course, I'm lazy too :-) – Sevastyan Savanyuk May 11 '18 at 09:06
  • @ShahabSaalami It's the class where I keep all my utility methods, like a static method to check if object is null: `Util.isNull(...)`. – Sevastyan Savanyuk May 20 '18 at 16:37
  • @katie no the fixLayoutSize() is still required as the layout is still being inflated in the ItemDecoration in Sevastyan code too. – Maks Jun 06 '18 at 00:16
  • @Sevastyan how is ```getHeaderLayout``` implementation? i did like this, but, i get Resources$NotFoundException. ```@Override public int getHeaderLayout(int headerPosition) { return R.id.list_item_section_text; }``` – nAkhmedov Jun 19 '18 at 11:49
  • @nAkhmedov You should use `.layout.` instead of `.id.`: `return R.layout.list_item_section_text;`. – Sevastyan Savanyuk Jun 19 '18 at 13:41
  • @Sevastyan all header is not visible in recyclerview, if i scroll to top then onDraw method is calling and other header is getting visible. – nAkhmedov Jun 22 '18 at 09:36
  • @nAkhmedov Well, that's the way it is supposed to work. Do you have a header on the very top in your list (do very first group of items have a header)? If not, this solution should be tweaked to suit your case. – Sevastyan Savanyuk Jun 22 '18 at 12:56
  • 1
    @Maks You're right. My bad. Though I think its good to mention to other people that, if this header is a view that's already an item in your RecyclerView, then you can modify the getHeaderViewForItem() method to retrieve that view directly instead of inflating it again. – katie Jul 11 '18 at 20:52
  • 5
    @Sevastyan, great work. I have a suggestion. To avoid creating new headers everytime. Just save the header and change it only when it changes. `private View getHeaderViewForItem(int itemPosition, RecyclerView parent) { int headerPosition = mListener.getHeaderPositionForItem(itemPosition); if(headerPosition != mCurrentHeaderIndex) { mCurrentHeader = mListener.createHeaderView(headerPosition, parent); mCurrentHeaderIndex = headerPosition; } return mCurrentHeader; }` – Vera Rivotti Jul 12 '18 at 12:13
  • 1
    For everyone having troubles that the header click if fired twice: The code should include a check to only trigger if the action was ACTION_UP like ```if (motionEvent.y <= mStickyHeaderHeight && motionEvent.action == MotionEvent.ACTION_UP)``` – Makibo Aug 23 '18 at 04:19
  • Kotlin version of getHeaderPositionForItem ```override fun getHeaderPositionForItem(itemPosition: Int): Int { return (itemPosition downTo 0).firstOrNull { isHeader(it) } ?: 0 }``` – Makibo Aug 23 '18 at 04:20
  • How can we use this to make sticky footer? – Maddy Nov 01 '18 at 06:36
  • Nice, solution, but besides of the performance issues, there is another bug which we should be aware of: If you want to use multiple item decorators, for example dividers, then the getChildInContact may return null even if it is not expected, since cases may appear when the contactPoint is not intersecting with any child (because it might intersect with the decorator item instead). To fix this issue, an offset should be added for finding the `childInContact`. – mathew11 Nov 26 '18 at 10:35
  • 2
    I've updated solution with few improvements and simplify interface as almost always header will be obtained same way. - check views with offsets when looking for adjacent views - added cache - switch to Kotlin - based on ViewHolders instead of Views https://gist.github.com/filipkowicz/1a769001fae407b8813ab4387c42fcbd – Filipkowicz Mar 07 '19 at 12:20
  • @Dennis Unfortunately, not. If I could, it would have already be there. I'm planning to write a demo app in Kotlin though. But there is no time estimate. – Sevastyan Savanyuk Mar 08 '19 at 08:25
  • Oh, Okay.. no problem.. please do write a comment if you'll add one, so I'll get notified. – Daksh Gargas Mar 08 '19 at 08:39
  • @Sevastyan thanks! consider adding note that there are few changes to your original approach - people might be surprised that it's not simple "to kotlin" translation – Filipkowicz Mar 10 '19 at 21:20
  • It's very good! bu I want to use PagedListAdapter with Room to load data from database, how to do it? – Stony May 20 '19 at 02:51
  • I have created a gist for the complete solution: https://gist.github.com/kasunn25/256e2c6b8dc8d0953d6fc9f7d9861246 – Kasun Wanniarachchi May 20 '19 at 07:25
  • @SevastyanSavanyuk Very nice explanation. Do you have a blog or tutorials page? I'd love to read more like this. – user2424380 Jun 22 '19 at 23:26
  • 1
    @user2424380 I do have a personal page with similar stuff. Unfortunately, I write on a very rare occasion. https://droid.hawkrig.com/ – Sevastyan Savanyuk Jun 24 '19 at 06:12
  • 1
    You should actually use `parent.getLayoutManager().getChildAt(0)` to get the first child. Using the `RecyclerView`(rv) to get the `topChild` will actually get you the wrong child during animation if using a `GridLayoutManager`(gm). In a rv using a gm, during animation (if moving from e.g. a 3x grid view to a 1x grid view), the first view in the rv will be the first item from the second row of items(2x1). This is because the gm will be trying to animate view from 2x1 to e.g 4x1, and creating the views that are supposed to be at 1-3x1. During this time, the rv says 4x1 is the top child. – nivekmai Apr 11 '20 at 15:53
  • Good solution. But, I have issue with consequent headers with different heights. That is why, the current header does not scroll entirely, only partially then sticks until new header pops up. Can you help me this this? – Madina Saidova Apr 16 '20 at 17:13
  • Anyone who could help with the @Denis.' question!. – Syed Arsalan Kazmi Feb 22 '22 at 20:08
  • Those who are new to passing functions in kotlin, please refer https://stackoverflow.com/a/33402863/14784590 for passing in isHeader – Reejesh Nov 08 '22 at 13:55
  • That is a nice and simple solution, but what if each row of my recycler view has an Image on the left side, in the middle some description and on the right side some checkbox, so on my header I want to have also some align text that describes each component of my recycler view. – Pavlos Mavris May 15 '23 at 10:16
  • How to make header invisible when overlay if I really need half-transparent header. – GHH Aug 15 '23 at 06:34
31

Easiest way is to just create an Item Decoration for your RecyclerView.

import android.graphics.Canvas;
import android.graphics.Rect;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;

public class RecyclerSectionItemDecoration extends RecyclerView.ItemDecoration {

private final int             headerOffset;
private final boolean         sticky;
private final SectionCallback sectionCallback;

private View     headerView;
private TextView header;

public RecyclerSectionItemDecoration(int headerHeight, boolean sticky, @NonNull SectionCallback sectionCallback) {
    headerOffset = headerHeight;
    this.sticky = sticky;
    this.sectionCallback = sectionCallback;
}

@Override
public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) {
    super.getItemOffsets(outRect, view, parent, state);

    int pos = parent.getChildAdapterPosition(view);
    if (sectionCallback.isSection(pos)) {
        outRect.top = headerOffset;
    }
}

@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
    super.onDrawOver(c,
                     parent,
                     state);

    if (headerView == null) {
        headerView = inflateHeaderView(parent);
        header = (TextView) headerView.findViewById(R.id.list_item_section_text);
        fixLayoutSize(headerView,
                      parent);
    }

    CharSequence previousHeader = "";
    for (int i = 0; i < parent.getChildCount(); i++) {
        View child = parent.getChildAt(i);
        final int position = parent.getChildAdapterPosition(child);

        CharSequence title = sectionCallback.getSectionHeader(position);
        header.setText(title);
        if (!previousHeader.equals(title) || sectionCallback.isSection(position)) {
            drawHeader(c,
                       child,
                       headerView);
            previousHeader = title;
        }
    }
}

private void drawHeader(Canvas c, View child, View headerView) {
    c.save();
    if (sticky) {
        c.translate(0,
                    Math.max(0,
                             child.getTop() - headerView.getHeight()));
    } else {
        c.translate(0,
                    child.getTop() - headerView.getHeight());
    }
    headerView.draw(c);
    c.restore();
}

private View inflateHeaderView(RecyclerView parent) {
    return LayoutInflater.from(parent.getContext())
                         .inflate(R.layout.recycler_section_header,
                                  parent,
                                  false);
}

/**
 * Measures the header view to make sure its size is greater than 0 and will be drawn
 * https://yoda.entelect.co.za/view/9627/how-to-android-recyclerview-item-decorations
 */
private void fixLayoutSize(View view, ViewGroup parent) {
    int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(),
                                                     View.MeasureSpec.EXACTLY);
    int heightSpec = View.MeasureSpec.makeMeasureSpec(parent.getHeight(),
                                                      View.MeasureSpec.UNSPECIFIED);

    int childWidth = ViewGroup.getChildMeasureSpec(widthSpec,
                                                   parent.getPaddingLeft() + parent.getPaddingRight(),
                                                   view.getLayoutParams().width);
    int childHeight = ViewGroup.getChildMeasureSpec(heightSpec,
                                                    parent.getPaddingTop() + parent.getPaddingBottom(),
                                                    view.getLayoutParams().height);

    view.measure(childWidth,
                 childHeight);

    view.layout(0,
                0,
                view.getMeasuredWidth(),
                view.getMeasuredHeight());
}

public interface SectionCallback {

    boolean isSection(int position);

    CharSequence getSectionHeader(int position);
}
}

XML for your header in recycler_section_header.xml:

<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/list_item_section_text"
    android:layout_width="match_parent"
    android:layout_height="@dimen/recycler_section_header_height"
    android:background="@android:color/black"
    android:paddingLeft="10dp"
    android:paddingRight="10dp"
    android:textColor="@android:color/white"
    android:textSize="14sp"
/>

And finally to add the Item Decoration to your RecyclerView:

RecyclerSectionItemDecoration sectionItemDecoration =
        new RecyclerSectionItemDecoration(getResources().getDimensionPixelSize(R.dimen.recycler_section_header_height),
                                          true, // true for sticky, false for not
                                          new RecyclerSectionItemDecoration.SectionCallback() {
                                              @Override
                                              public boolean isSection(int position) {
                                                  return position == 0
                                                      || people.get(position)
                                                               .getLastName()
                                                               .charAt(0) != people.get(position - 1)
                                                                                   .getLastName()
                                                                                   .charAt(0);
                                              }

                                              @Override
                                              public CharSequence getSectionHeader(int position) {
                                                  return people.get(position)
                                                               .getLastName()
                                                               .subSequence(0,
                                                                            1);
                                              }
                                          });
    recyclerView.addItemDecoration(sectionItemDecoration);

With this Item Decoration you can either make the header pinned/sticky or not with just a boolean when creating the Item Decoration.

You can find a complete working example on github: https://github.com/paetztm/recycler_view_headers

Ichigo Kurosaki
  • 3,765
  • 8
  • 41
  • 56
tim.paetz
  • 2,635
  • 20
  • 23
  • Thank you. this worked for me, however this header overlaps the recyclerview. can you help? –  May 09 '17 at 10:48
  • I'm not sure what you mean by overlaps the RecyclerView. For the "sticky" boolean, if you set that to false it will put the item decoration in between the rows and won't stay at the top of the RecyclerView. – tim.paetz May 10 '17 at 11:39
  • 1
    setting it to "sticky" to false puts header between the rows, but that does not stay stuck(which I don't want) to the top. while setting it to true, it stays stuck on the top but it overlaps the first row in the recyclerview –  May 10 '17 at 11:54
  • I can see that as potentially two problems, one is the section callback, you aren't setting the first item (0 position) for isSection to true. The other is that you are passing in the wrong height. The xml's height for the text view must be the same height as the height you pass into the constructor of the section item decoration. – tim.paetz May 10 '17 at 14:01
  • 3
    One thing I would add, is that if your header layout has the title text view dynamically sized (e.g. `wrap_content`), You'd want to run `fixLayoutSize` after setting the title text as well. – copolii Apr 24 '18 at 22:54
  • It is not working correctly with pagination??? Any help regarding this issue? Here it looks like https://drive.google.com/file/d/1kMYEA6T6xeT79eQlFJvPO_EObjUrJmDq/view?usp=sharing – Rimsha Butt Jan 19 '21 at 13:49
8

I've made my own variation of Sevastyan's solution above

class HeaderItemDecoration(recyclerView: RecyclerView, private val listener: StickyHeaderInterface) : RecyclerView.ItemDecoration() {

private val headerContainer = FrameLayout(recyclerView.context)
private var stickyHeaderHeight: Int = 0
private var currentHeader: View? = null
private var currentHeaderPosition = 0

init {
    val layout = RelativeLayout(recyclerView.context)
    val params = recyclerView.layoutParams
    val parent = recyclerView.parent as ViewGroup
    val index = parent.indexOfChild(recyclerView)
    parent.addView(layout, index, params)
    parent.removeView(recyclerView)
    layout.addView(recyclerView, LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT)
    layout.addView(headerContainer, LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
}

override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    super.onDrawOver(c, parent, state)

    val topChild = parent.getChildAt(0) ?: return

    val topChildPosition = parent.getChildAdapterPosition(topChild)
    if (topChildPosition == RecyclerView.NO_POSITION) {
        return
    }

    val currentHeader = getHeaderViewForItem(topChildPosition, parent)
    fixLayoutSize(parent, currentHeader)
    val contactPoint = currentHeader.bottom
    val childInContact = getChildInContact(parent, contactPoint) ?: return

    val nextPosition = parent.getChildAdapterPosition(childInContact)
    if (listener.isHeader(nextPosition)) {
        moveHeader(currentHeader, childInContact, topChildPosition, nextPosition)
        return
    }

    drawHeader(currentHeader, topChildPosition)
}

private fun getHeaderViewForItem(itemPosition: Int, parent: RecyclerView): View {
    val headerPosition = listener.getHeaderPositionForItem(itemPosition)
    val layoutResId = listener.getHeaderLayout(headerPosition)
    val header = LayoutInflater.from(parent.context).inflate(layoutResId, parent, false)
    listener.bindHeaderData(header, headerPosition)
    return header
}

private fun drawHeader(header: View, position: Int) {
    headerContainer.layoutParams.height = stickyHeaderHeight
    setCurrentHeader(header, position)
}

private fun moveHeader(currentHead: View, nextHead: View, currentPos: Int, nextPos: Int) {
    val marginTop = nextHead.top - currentHead.height
    if (currentHeaderPosition == nextPos && currentPos != nextPos) setCurrentHeader(currentHead, currentPos)

    val params = currentHeader?.layoutParams as? MarginLayoutParams ?: return
    params.setMargins(0, marginTop, 0, 0)
    currentHeader?.layoutParams = params

    headerContainer.layoutParams.height = stickyHeaderHeight + marginTop
}

private fun setCurrentHeader(header: View, position: Int) {
    currentHeader = header
    currentHeaderPosition = position
    headerContainer.removeAllViews()
    headerContainer.addView(currentHeader)
}

private fun getChildInContact(parent: RecyclerView, contactPoint: Int): View? =
        (0 until parent.childCount)
            .map { parent.getChildAt(it) }
            .firstOrNull { it.bottom > contactPoint && it.top <= contactPoint }

private fun fixLayoutSize(parent: ViewGroup, view: View) {

    val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
    val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)

    val childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec,
            parent.paddingLeft + parent.paddingRight,
            view.layoutParams.width)
    val childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec,
            parent.paddingTop + parent.paddingBottom,
            view.layoutParams.height)

    view.measure(childWidthSpec, childHeightSpec)

    stickyHeaderHeight = view.measuredHeight
    view.layout(0, 0, view.measuredWidth, stickyHeaderHeight)
}

interface StickyHeaderInterface {

    fun getHeaderPositionForItem(itemPosition: Int): Int

    fun getHeaderLayout(headerPosition: Int): Int

    fun bindHeaderData(header: View, headerPosition: Int)

    fun isHeader(itemPosition: Int): Boolean
}
}

... and here is implementation of StickyHeaderInterface (I did it directly in recycler adapter):

override fun getHeaderPositionForItem(itemPosition: Int): Int =
    (itemPosition downTo 0)
        .map { Pair(isHeader(it), it) }
        .firstOrNull { it.first }?.second ?: RecyclerView.NO_POSITION

override fun getHeaderLayout(headerPosition: Int): Int {
    /* ... 
      return something like R.layout.view_header
      or add conditions if you have different headers on different positions
    ... */
}

override fun bindHeaderData(header: View, headerPosition: Int) {
    if (headerPosition == RecyclerView.NO_POSITION) header.layoutParams.height = 0
    else /* ...
      here you get your header and can change some data on it
    ... */
}

override fun isHeader(itemPosition: Int): Boolean {
    /* ...
      here have to be condition for checking - is item on this position header
    ... */
}

So, in this case header is not just drawing on canvas, but view with selector or ripple, clicklistener, etc.

Andrii Turkovskyi
  • 27,554
  • 16
  • 95
  • 105
  • Thanks for sharing! Why did you end up wrapping the RecyclerView in a new RelativeLayout? – tmm1 Feb 14 '18 at 19:03
  • Because my version of sticky header is View, which I put into this RelativeLayout above RecyclerView (in headerContainer field) – Andrii Turkovskyi Feb 15 '18 at 20:36
  • Can you show your implementation in class file? How you passed the object of listener which is implemented in adapter. – Dipali Shah Dec 06 '18 at 05:34
  • `recyclerView.addItemDecoration(HeaderItemDecoration(recyclerView, adapter))`. Sorry, can't find example of implementation, which I used. I've edited answer - added some text to comments – Andrii Turkovskyi Dec 06 '18 at 07:45
  • Brilliant idea, but one issue occurs: since the sticky header is drawn on top of `RecyclerView` we _lose_ its top overscroll animation. – ivan8m8 Jan 29 '23 at 12:49
6

to anyone looking for solution to the flickering/blinking issue when you already have DividerItemDecoration. i seem to have solved it like this:

override fun onDrawOver(...)
    {
        //code from before

       //do NOT return on null
        val childInContact = getChildInContact(recyclerView, currentHeader.bottom)
        //add null check
        if (childInContact != null && mHeaderListener.isHeader(recyclerView.getChildAdapterPosition(childInContact)))
        {
            moveHeader(...)
            return
        }
    drawHeader(...)
}

this seems to be working but can anyone confirm i did not break anything else?

or_dvir
  • 479
  • 1
  • 7
  • 22
5

You can check and take the implementation of the class StickyHeaderHelper in my FlexibleAdapter project, and adapt it to your use case.

But, I suggest to use the library since it simplifies and reorganizes the way you usually implement the Adapters for RecyclerView: Don't reinvent the wheel.

I would also say, don't use Decorators or deprecated libraries, as well as don't use libraries that do only 1 or 3 things, you will have to merge implementations of others libraries yourself.

Davideas
  • 3,226
  • 2
  • 33
  • 51
  • I spent 2 days to read the wiki and sample, but still don't know how to create collapsable list by using your lib. The sample is quite complex for newbie – Nguyen Minh Binh Feb 27 '17 at 08:14
  • 2
    Why are you against using `Decorator`s? – Sevastyan Savanyuk May 27 '17 at 09:22
  • 2
    @Sevastyan, because we will arrive at the point we need click listener on it and on child views as well. We Decorator you simply can't by definition. – Davideas May 29 '17 at 11:50
  • @Davidea, do you mean you want to set click listeners on the headers in the future? If so, it makes sense. But still, if you supply your headers as dataset items, there will be no problems. Even Yigit Boyar recommends using Decorators. – Sevastyan Savanyuk May 29 '17 at 12:23
  • @Sevastyan, yes in my library the header is an item as others in the list, so users can manipulate it. In a far future a custom layout manager will replace the current helper. – Davideas May 29 '17 at 12:43
  • @Davidea, you can check out my answer and maybe get some ideas for your development. – Sevastyan Savanyuk Jun 02 '17 at 11:10
  • Hey @davideas I was going through this setup page. https://github.com/davideas/FlexibleAdapter/wiki/5.x-%7C-Setting-Up I am stumped at this text. "⚠️ Warning: If using a version of FlexibleAdapter older than 5.1.0, replace both instances of androidx.cardview.widget.CardView with android.support.v7.widget.CardView." Why should we downgrade? – M. Azyoksul Apr 11 '21 at 18:23
5

Yo,

This is how you do it if you want just one type of holder stick when it starts getting out of the screen (we are not caring about any sections). There is only one way without breaking the internal RecyclerView logic of recycling items and that is to inflate additional view on top of the recyclerView's header item and pass data into it. I'll let the code speak.

import android.graphics.Canvas
import android.graphics.Rect
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.annotation.LayoutRes
import androidx.recyclerview.widget.RecyclerView

class StickyHeaderItemDecoration(@LayoutRes private val headerId: Int, private val HEADER_TYPE: Int) : RecyclerView.ItemDecoration() {

private lateinit var stickyHeaderView: View
private lateinit var headerView: View

private var sticked = false

// executes on each bind and sets the stickyHeaderView
override fun getItemOffsets(outRect: Rect, view: View, parent: RecyclerView, state: RecyclerView.State) {
    super.getItemOffsets(outRect, view, parent, state)

    val position = parent.getChildAdapterPosition(view)

    val adapter = parent.adapter ?: return
    val viewType = adapter.getItemViewType(position)

    if (viewType == HEADER_TYPE) {
        headerView = view
    }
}

override fun onDrawOver(c: Canvas, parent: RecyclerView, state: RecyclerView.State) {
    super.onDrawOver(c, parent, state)
    if (::headerView.isInitialized) {

        if (headerView.y <= 0 && !sticked) {
            stickyHeaderView = createHeaderView(parent)
            fixLayoutSize(parent, stickyHeaderView)
            sticked = true
        }

        if (headerView.y > 0 && sticked) {
            sticked = false
        }

        if (sticked) {
            drawStickedHeader(c)
        }
    }
}

private fun createHeaderView(parent: RecyclerView) = LayoutInflater.from(parent.context).inflate(headerId, parent, false)

private fun drawStickedHeader(c: Canvas) {
    c.save()
    c.translate(0f, Math.max(0f, stickyHeaderView.top.toFloat() - stickyHeaderView.height.toFloat()))
    headerView.draw(c)
    c.restore()
}

private fun fixLayoutSize(parent: ViewGroup, view: View) {

    // Specs for parent (RecyclerView)
    val widthSpec = View.MeasureSpec.makeMeasureSpec(parent.width, View.MeasureSpec.EXACTLY)
    val heightSpec = View.MeasureSpec.makeMeasureSpec(parent.height, View.MeasureSpec.UNSPECIFIED)

    // Specs for children (headers)
    val childWidthSpec = ViewGroup.getChildMeasureSpec(widthSpec, parent.paddingLeft + parent.paddingRight, view.getLayoutParams().width)
    val childHeightSpec = ViewGroup.getChildMeasureSpec(heightSpec, parent.paddingTop + parent.paddingBottom, view.getLayoutParams().height)

    view.measure(childWidthSpec, childHeightSpec)

    view.layout(0, 0, view.measuredWidth, view.measuredHeight)
}

}

And then you just do this in your adapter:

override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
    super.onAttachedToRecyclerView(recyclerView)
    recyclerView.addItemDecoration(StickyHeaderItemDecoration(R.layout.item_time_filter, YOUR_STICKY_VIEW_HOLDER_TYPE))
}

Where YOUR_STICKY_VIEW_HOLDER_TYPE is viewType of your what is supposed to be sticky holder.

Stanislav Kinzl
  • 370
  • 4
  • 6
3

Another solution, based on scroll listener. Initial conditions are the same as in Sevastyan answer

RecyclerView recyclerView;
TextView tvTitle; //sticky header view

//... onCreate, initialize, etc...

public void bindList(List<Item> items) { //All data in adapter. Item - just interface for different item types
    adapter = new YourAdapter(items);
    recyclerView.setAdapter(adapter);
    StickyHeaderViewManager<HeaderItem> stickyHeaderViewManager = new StickyHeaderViewManager<>(
            tvTitle,
            recyclerView,
            HeaderItem.class, //HeaderItem - subclass of Item, used to detect headers in list
            data -> { // bind function for sticky header view
                tvTitle.setText(data.getTitle());
            });
    stickyHeaderViewManager.attach(items);
}

Layout for ViewHolder and sticky header.

item_header.xml

<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_title"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"/>

Layout for RecyclerView

<FrameLayout
    android:layout_width="match_parent"
    android:layout_height="match_parent">

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

    <!--it can be any view, but order important, draw over recyclerView-->
    <include
        layout="@layout/item_header"/>

</FrameLayout>

Class for HeaderItem.

public class HeaderItem implements Item {

    private String title;

    public HeaderItem(String title) {
        this.title = title;
    }

    public String getTitle() {
        return title;
    }

}

It's all use. The implementation of the adapter, ViewHolder and other things, is not interesting for us.

public class StickyHeaderViewManager<T> {

    @Nonnull
    private View headerView;

    @Nonnull
    private RecyclerView recyclerView;

    @Nonnull
    private StickyHeaderViewWrapper<T> viewWrapper;

    @Nonnull
    private Class<T> headerDataClass;

    private List<?> items;

    public StickyHeaderViewManager(@Nonnull View headerView,
                                   @Nonnull RecyclerView recyclerView,
                                   @Nonnull Class<T> headerDataClass,
                                   @Nonnull StickyHeaderViewWrapper<T> viewWrapper) {
        this.headerView = headerView;
        this.viewWrapper = viewWrapper;
        this.recyclerView = recyclerView;
        this.headerDataClass = headerDataClass;
    }

    public void attach(@Nonnull List<?> items) {
        this.items = items;
        if (ViewCompat.isLaidOut(headerView)) {
            bindHeader(recyclerView);
        } else {
            headerView.post(() -> bindHeader(recyclerView));
        }

        recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {

            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                super.onScrolled(recyclerView, dx, dy);
                bindHeader(recyclerView);
            }
        });
    }

    private void bindHeader(RecyclerView recyclerView) {
        if (items.isEmpty()) {
            headerView.setVisibility(View.GONE);
            return;
        } else {
            headerView.setVisibility(View.VISIBLE);
        }

        View topView = recyclerView.getChildAt(0);
        if (topView == null) {
            return;
        }
        int topPosition = recyclerView.getChildAdapterPosition(topView);
        if (!isValidPosition(topPosition)) {
            return;
        }
        if (topPosition == 0 && topView.getTop() == recyclerView.getTop()) {
            headerView.setVisibility(View.GONE);
            return;
        } else {
            headerView.setVisibility(View.VISIBLE);
        }

        T stickyItem;
        Object firstItem = items.get(topPosition);
        if (headerDataClass.isInstance(firstItem)) {
            stickyItem = headerDataClass.cast(firstItem);
            headerView.setTranslationY(0);
        } else {
            stickyItem = findNearestHeader(topPosition);
            int secondPosition = topPosition + 1;
            if (isValidPosition(secondPosition)) {
                Object secondItem = items.get(secondPosition);
                if (headerDataClass.isInstance(secondItem)) {
                    View secondView = recyclerView.getChildAt(1);
                    if (secondView != null) {
                        moveViewFor(secondView);
                    }
                } else {
                    headerView.setTranslationY(0);
                }
            }
        }

        if (stickyItem != null) {
            viewWrapper.bindView(stickyItem);
        }
    }

    private void moveViewFor(View secondView) {
        if (secondView.getTop() <= headerView.getBottom()) {
            headerView.setTranslationY(secondView.getTop() - headerView.getHeight());
        } else {
            headerView.setTranslationY(0);
        }
    }

    private T findNearestHeader(int position) {
        for (int i = position; position >= 0; i--) {
            Object item = items.get(i);
            if (headerDataClass.isInstance(item)) {
                return headerDataClass.cast(item);
            }
        }
        return null;
    }

    private boolean isValidPosition(int position) {
        return !(position == RecyclerView.NO_POSITION || position >= items.size());
    }
}

Interface for bind header view.

public interface StickyHeaderViewWrapper<T> {

    void bindView(T data);
}
Anrimian
  • 4,257
  • 4
  • 22
  • 30
  • I like this solution. Small typo in findNearestHeader: `for (int i = position; position >= 0; i--){ //should be i >= 0` – Konstantin Jul 12 '18 at 18:33
2

For those who may concern. Based on Sevastyan's answer, should you want to make it horizontal scroll. Simply change all getBottom() to getRight() and getTop() to getLeft()

Guster
  • 1,733
  • 1
  • 16
  • 18
2

you can get sticky header functionality by copying these 2 files into your project. i had no issues with this implementation:

  • can interact with the sticy header (tap/long press/swipe)
  • the sticky header hides and reveals itself properly...even if each view holder has a different height (some other answers here don't handle that properly, causing the wrong headers to show, or the headers to jump up and down)

see an example of the 2 files being used in this small github project i whipped up

Eric
  • 16,397
  • 8
  • 68
  • 76
1

In case you want the header to be beside your recyclerview item like this enter image description here then use the same code here and add this two lines inside onDrawOver

//hide the image and the name, and draw only the alphabet
        val headerView = getHeaderViewForItem(topChildPosition, parent) ?: return
        headerView.findViewById<ShapeableImageView>(R.id.contactImageView).isVisible = false
        headerView.findViewById<TextView>(R.id.nameTextView).isVisible = false

here you are basically redrawing again the recyclerview item but hiding all elements which is on the right.
if you are wondering how to create such recyclerview item, then here is how:
your recyclerview item then you will create list of your data like this:

class ContactRecyclerDataItem(val contact: SimpleContact, val alphabet: String? = null)

so that when you recieve the list of your data you can build list of ContactRecyclerDataItem

this way

list?.let {
            val adapterDataList = mutableListOf<ContactRecyclerDataItem>()
            if (it.isNotEmpty()) {
                var prevChar = (it[0].name[0].code + 1).toChar()
                it.forEach { contact ->
                    if (contact.name[0] != prevChar) {
                        prevChar = contact.name[0]
                        adapterDataList.add(ContactRecyclerDataItem(contact, prevChar.toString()))
                    } else {
                        adapterDataList.add(ContactRecyclerDataItem(contact))
                    }
                }
            }
            contactsAdapter.data = adapterDataList
        }

then inside your recycler adapter inside viewHolder you make check if the alphabet is empty or not,

        if (itemRecycler.alphabet != null) {
            alphabetTextView.text = itemRecycler.alphabet
        } else {
            alphabetTextView.text = ""
        }

at the end you build this recyclerview with alphabets on the left, but to make them sticky you inflate and move the first element which is the header all the way down until the next header, the trick as mentioned above is to hide all the other elements in your recyclerview item except the alphabet.
to make the first element clickable you should return false inside the itemDecorat inside init block in parent.addOnItemTouchListene{} when returning false, you are passing the click listener to the bellow view which is in this case your visible recyclerview item.

Amr
  • 1,068
  • 12
  • 21
-3

The answer has already been here. If you don't want to use any library, you can follow these steps:

  1. Sort list with data by name
  2. Iterate via list with data, and in place when current's item first letter != first letter of next item, insert "special" kind of object.
  3. Inside your Adapter place special view when item is "special".

Explanation:

In onCreateViewHolder method we can check viewType and depending on the value (our "special" kind) inflate a special layout.

For example:

public static final int TITLE = 0;
public static final int ITEM = 1;

@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
    if (context == null) {
        context = parent.getContext();
    }
    if (viewType == TITLE) {
        view = LayoutInflater.from(context).inflate(R.layout.recycler_adapter_title, parent,false);
        return new TitleElement(view);
    } else if (viewType == ITEM) {
        view = LayoutInflater.from(context).inflate(R.layout.recycler_adapter_item, parent,false);
        return new ItemElement(view);
    }
    return null;
}

where class ItemElement and class TitleElement can look like ordinary ViewHolder :

public class ItemElement extends RecyclerView.ViewHolder {
//TextView text;

public ItemElement(View view) {
    super(view);
   //text = (TextView) view.findViewById(R.id.text);

}

So the idea of all of that is interesting. But i am interested if it's effectively, cause we need to sort the data list. And i think this will take the speed down. If any thoughts about it, please write me :)

And also the opened question : is how to hold the "special" layout on the top, while the items are recycling. Maybe combine all of that with CoordinatorLayout.

Valeria
  • 696
  • 6
  • 12