Work in progress RecyclerView decoration to pin header
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
@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