Skip to content

Instantly share code, notes, and snippets.

@saber-solooki
Created July 7, 2018 18:40
Show Gist options
  • Star 57 You must be signed in to star a gist
  • Fork 24 You must be signed in to fork a gist
  • Save saber-solooki/edeb57be63d2a60ef551676067c66c71 to your computer and use it in GitHub Desktop.
Save saber-solooki/edeb57be63d2a60ef551676067c66c71 to your computer and use it in GitHub Desktop.
Sticky Header RecyclerView
package com.saber.customstickyheader;
import android.graphics.Color;
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;
import java.util.ArrayList;
import java.util.List;
public class SimpleRecyclerView extends RecyclerView.Adapter<RecyclerView.ViewHolder> implements StickHeaderItemDecoration.StickyHeaderInterface {
private List<Data> mData;
public SimpleRecyclerView() {
mData = new ArrayList<>();
mData.add(new Data(1));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));`
mData.add(new Data(0));
mData.add(new Data(2));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(1));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(2));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
mData.add(new Data(0));
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
switch (viewType) {
case HeaderDataImpl.HEADER_TYPE_1:
return new SimpleRecyclerView.HeaderViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.header1_item_recycler, parent, false));
case HeaderDataImpl.HEADER_TYPE_2:
return new SimpleRecyclerView.Header2ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.header2_item_recycler, parent, false));
default:
return new SimpleRecyclerView.ViewHolder(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_recycler, parent, false));
}
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder holder, int position) {
if (holder instanceof ViewHolder) {
((ViewHolder) holder).bindData(position);
} else if (holder instanceof HeaderViewHolder){
((HeaderViewHolder) holder).bindData(position);
} else if (holder instanceof Header2ViewHolder){
((Header2ViewHolder) holder).bindData(position);
}
}
@Override
public int getItemViewType(int position) {
return mData.get(position).getViewType();
}
@Override
public int getItemCount() {
return mData.size();
}
@Override
public int getHeaderPositionForItem(int itemPosition) {
int headerPosition = 0;
do {
if (this.isHeader(itemPosition)) {
headerPosition = itemPosition;
break;
}
itemPosition -= 1;
} while (itemPosition >= 0);
return headerPosition;
}
@Override
public int getHeaderLayout(int headerPosition) {
if (mData.get(headerPosition).getViewType() == 1)
return R.layout.header1_item_recycler;
else {
return R.layout.header2_item_recycler;
}
}
@Override
public void bindHeaderData(View header, int headerPosition) {
}
@Override
public boolean isHeader(int itemPosition) {
if (mData.get(itemPosition).getViewType() == 1 || mData.get(itemPosition).getViewType() == 2)
return true;
else
return false;
}
class HeaderViewHolder extends RecyclerView.ViewHolder {
TextView tvHeader;
HeaderViewHolder(View itemView) {
super(itemView);
tvHeader = itemView.findViewById(R.id.tvHeader);
}
void bindData(int position) {
tvHeader.setText(String.valueOf(position / 5));
}
}
class Header2ViewHolder extends RecyclerView.ViewHolder {
TextView tvHeader;
Header2ViewHolder(View itemView) {
super(itemView);
tvHeader = itemView.findViewById(R.id.tvHeader);
}
void bindData(int position) {
tvHeader.setText(String.valueOf(position / 5));
}
}
class ViewHolder extends RecyclerView.ViewHolder {
TextView tvRows;
ViewHolder(View itemView) {
super(itemView);
tvRows = itemView.findViewById(R.id.tvRows);
}
void bindData(int position) {
tvRows.setText("saber" + position);
((ViewGroup) tvRows.getParent()).setBackgroundColor(Color.parseColor("#ffffff"));
}
}
class Data {
int viewType;
public Data(int viewType) {
this.viewType = viewType;
}
public int getViewType() {
return viewType;
}
public void setViewType(int viewType) {
this.viewType = viewType;
}
}
}
package com.saber.customstickyheader;
import android.graphics.Canvas;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
public class StickHeaderItemDecoration extends RecyclerView.ItemDecoration {
private StickyHeaderInterface mListener;
private int mStickyHeaderHeight;
public StickHeaderItemDecoration(@NonNull StickyHeaderInterface listener) {
mListener = listener;
}
@Override
public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
super.onDrawOver(c, parent, state);
View topChild = parent.getChildAt(0);
if (topChild == null) {
return;
}
int topChildPosition = parent.getChildAdapterPosition(topChild);
if (topChildPosition == RecyclerView.NO_POSITION) {
return;
}
int headerPos = mListener.getHeaderPositionForItem(topChildPosition);
View currentHeader = getHeaderViewForItem(headerPos, parent);
fixLayoutSize(parent, currentHeader);
int contactPoint = currentHeader.getBottom();
View childInContact = getChildInContact(parent, contactPoint, headerPos);
if (childInContact != null && mListener.isHeader(parent.getChildAdapterPosition(childInContact))) {
moveHeader(c, currentHeader, childInContact);
return;
}
drawHeader(c, currentHeader);
}
private View getHeaderViewForItem(int headerPosition, RecyclerView parent) {
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, int currentHeaderPos) {
View childInContact = null;
for (int i = 0; i < parent.getChildCount(); i++) {
int heightTolerance = 0;
View child = parent.getChildAt(i);
//measure height tolerance with child if child is another header
if (currentHeaderPos != i) {
boolean isChildHeader = mListener.isHeader(parent.getChildAdapterPosition(child));
if (isChildHeader) {
heightTolerance = mStickyHeaderHeight - child.getHeight();
}
}
//add heightTolerance if child top be in display area
int childBottomPosition;
if (child.getTop() > 0) {
childBottomPosition = child.getBottom() + heightTolerance;
} else {
childBottomPosition = child.getBottom();
}
if (childBottomPosition > 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 StickHeaderItemDecoration} 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 StickHeaderItemDecoration} 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 StickHeaderItemDecoration} 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 StickHeaderItemDecoration} 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);
}
}
RecyclerView recyclerView = findViewById(R.id.recyclerView);
SimpleRecyclerView adapter = new SimpleRecyclerView();
LinearLayoutManager layoutManager = new LinearLayoutManager(this);
recyclerView.setAdapter(adapter);
recyclerView.setLayoutManager(layoutManager);
recyclerView.addItemDecoration(new StickHeaderItemDecoration(adapter));
@krupinder955
Copy link

I am not able to get click event on the header. is it possible ?
As i have a image view in the header view i need to get the click event.

@Shusshu
Copy link

Shusshu commented Mar 20, 2019

You can't interact with ItemDecorators

@idrisbohra
Copy link

How to handle the pagination?

@palplaykimo
Copy link

this stick header very helpful me, thank you .

@ArunYogeshwaran
Copy link

Thanks for this

@Jaosrikate
Copy link

This is helpful, Thank you
It's working!

@aruns2896
Copy link

@smuyyh what is paint???

@smuyyh
Copy link

smuyyh commented Nov 18, 2019

https://github.com/smuyyh/StickyHeaderRecyclerView

This allows sticky headers to be implemented through viewholder

@chichi289
Copy link

chichi289 commented Nov 7, 2020

If you scroll immediately after setting response then it is crashing

java.lang.IndexOutOfBoundsException: Index: 7, Size: 7
at java.util.ArrayList.get(ArrayList.java:437)
at com.test.ui.alerts.adapters.HeaderChildAdapter.isHeader(HeaderChildAdapter.kt:56)

@AsadLeo1995
Copy link

have you handle this crash?

@preetham1316
Copy link

preetham1316 commented Aug 4, 2023

I am not able to get click event on the header. is it possible ? As i have a image view in the header view i need to get the click event.

You can use this approach if it suits your requirements it can support clicks aswell for sticky headers, https://gist.github.com/preetham1316/d21be9e6987be601a7d5507081c127a8

@huang840801
Copy link

The header will duplicate when the view overlay. How to solve it?

https://stackoverflow.com/questions/76903052/android-recyclerview-sticky-header-alpha-view

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment