Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
Work in progress RecyclerView decoration to pin header
@Beta
private class HeaderDecor extends RecyclerView.ItemDecoration {
private RecyclerView.AdapterDataObserver adapterDataObserver;
private RecyclerView.ViewHolder headerViewHolder;
private int headerViewType;
private final Rect tmpBounds = new Rect();
@Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) {
if (headerViewHolder == null) {
headerViewHolder = obtainViewHolder(parent);
}
c.save();
final View view = headerViewHolder.itemView;
view.getDrawingRect(tmpBounds);
if (tmpBounds.isEmpty()) {
layoutHeaderIfMissedLayoutPass(parent);
}
c.clipRect(tmpBounds);
view.draw(c);
c.restore();
}
void bindHeaderHolder() {
if (headerViewHolder == null) {
return;
}
final ViewGroup parentView = (ViewGroup) headerViewHolder.itemView;
if (parentView != null && parentView instanceof RecyclerView) {
RecyclerView parent = (RecyclerView) parentView;
//noinspection unchecked
parent.getAdapter().onBindViewHolder(headerViewHolder, 0);
parent.invalidateItemDecorations();
}
}
private RecyclerView.ViewHolder obtainViewHolder(RecyclerView parent) {
final RecyclerView.Adapter adapter = parent.getAdapter();
if (adapterDataObserver == null) {
parent.setViewCacheExtension(new HeaderViewCaching());
adapter.registerAdapterDataObserver(adapterDataObserver = new HeaderObserver());
}
RecyclerView.ViewHolder headerViewHolder = parent.findViewHolderForAdapterPosition(0);
// Means that it was recycled by framework
if (headerViewHolder == null) {
final int viewType = headerViewType = adapter.getItemViewType(0);
// Try to obtain it from Recycled pool
headerViewHolder = parent.getRecycledViewPool().getRecycledView(viewType);
if (headerViewHolder == null) {
headerViewHolder = adapter.onCreateViewHolder(parent, viewType);
Timber.i("New header was created");
} else {
Timber.i("Header is taken from RecycledViewPool");
}
} else {
Timber.i("Header is attached to RecyclerView");
}
// noinspection unchecked
adapter.onBindViewHolder(headerViewHolder, 0);
return headerViewHolder;
}
void layoutHeaderIfMissedLayoutPass(RecyclerView parent) {
if (headerViewHolder == null) {
return;
}
//reset from previous settings
tmpBounds.setEmpty();
final View itemView = headerViewHolder.itemView;
// mark View invisible for RecyclerView to skip overdrawing
itemView.setVisibility(View.INVISIBLE);
// If View is still not measured
if (!itemView.getGlobalVisibleRect(tmpBounds)) {
Timber.d("View is not laid out, measuring it");
// Take care of view offsets
final ViewGroup.LayoutParams lp = itemView.getLayoutParams();
if (lp instanceof ViewGroup.MarginLayoutParams) {
final ViewGroup.MarginLayoutParams mlp = (ViewGroup.MarginLayoutParams) lp;
tmpBounds.left = mlp.leftMargin;
tmpBounds.top = mlp.topMargin;
tmpBounds.right = mlp.rightMargin;
tmpBounds.bottom = mlp.bottomMargin;
}
// and measure it all
itemView.measure(makeSpec(parent.getWidth()), makeSpec(parent.getHeight()));
itemView.layout(tmpBounds.left, tmpBounds.top, tmpBounds.right, tmpBounds.bottom);
}
}
private static int makeSpec(int size) {
return View.MeasureSpec.makeMeasureSpec(size, View.MeasureSpec.UNSPECIFIED);
}
final class HeaderObserver extends RecyclerView.AdapterDataObserver {
@Override public void onItemRangeChanged(int positionStart, int itemCount) {
if (positionStart == 0) {
bindHeaderHolder();
}
}
}
final class HeaderViewCaching extends RecyclerView.ViewCacheExtension {
@Override public View getViewForPositionAndType(RecyclerView.Recycler recycler, int position,
int viewType) {
if (headerViewHolder != null && viewType == headerViewType) {
return headerViewHolder.itemView;
}
return null;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment