-
-
Save nhCoder/14ba329241f8e00303f2692e3f5c5d5e to your computer and use it in GitHub Desktop.
Sticky Header RecyclerView
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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; | |
} | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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); | |
} | |
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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)); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment