Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
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));
@Shusshu
Copy link

Shusshu commented Mar 20, 2019

You can't interact with ItemDecorators

@idrisbohra
Copy link

idrisbohra commented Jul 22, 2019

How to handle the pagination?

@palplaykimo
Copy link

palplaykimo commented Jul 24, 2019

this stick header very helpful me, thank you .

@ArunYogeshwaran
Copy link

ArunYogeshwaran commented Aug 7, 2019

Thanks for this

@Jaosrikate
Copy link

Jaosrikate commented Oct 31, 2019

This is helpful, Thank you
It's working!

@aruns2896
Copy link

aruns2896 commented Nov 18, 2019

@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

AsadLeo1995 commented May 19, 2021

have you handle this crash?

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