package com.marshalchen.common.uimodule.recyclerviewstickyheaders; import android.graphics.Canvas; import android.graphics.Rect; import android.support.v4.view.ViewCompat; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.RecyclerViewHelper; import android.view.View; import android.view.ViewGroup; import java.util.HashMap; import java.util.Iterator; /** * Created by aurel on 22/09/14. */ public class StickyHeadersItemDecoration extends RecyclerView.ItemDecoration { private final static int NO_HEIGHT = -1; private final StickyHeadersAdapter adapter; private final RecyclerView parent; private final RecyclerView.ViewHolder headerViewHolder; private final HashMap<Long, Boolean> headers; private final AdapterDataObserver adapterDataObserver; private boolean overlay; private int headerHeight; public StickyHeadersItemDecoration(StickyHeadersAdapter adapter, RecyclerView parent) { this(adapter, parent, false); } public StickyHeadersItemDecoration(StickyHeadersAdapter adapter, RecyclerView parent, boolean overlay) { this.adapter = adapter; this.parent = parent; this.headerViewHolder = adapter.onCreateViewHolder(parent); this.overlay = overlay; this.headers = new HashMap<Long, Boolean>(); this.adapterDataObserver = new AdapterDataObserver(); this.headerHeight = NO_HEIGHT; } @Override public void onDrawOver(Canvas c, RecyclerView parent, RecyclerView.State state) { final int childCount = parent.getChildCount(); final RecyclerView.LayoutManager lm = parent.getLayoutManager(); View header = headerViewHolder.itemView; Float lastY = null; for (int i = childCount - 1; i >= 0; i--) { final View child = parent.getChildAt(i); final RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)child.getLayoutParams(); final int position = parent.getChildPosition(child); RecyclerView.ViewHolder holder = parent.getChildViewHolder(child); if (!lp.isItemRemoved()) { float translationY = ViewCompat.getTranslationY(child); if (i == 0 || isHeader(holder)) { float y = getHeaderY(child, lm) + translationY; if (lastY != null && lastY < y + headerHeight) { y = lastY - headerHeight; } adapter.onBindViewHolder(headerViewHolder, position); c.save(); c.translate(0, y); header.draw(c); c.restore(); lastY = y; } } } } @Override public void getItemOffsets(Rect outRect, View view, RecyclerView parent, RecyclerView.State state) { ensureHeaderLaidOut(); RecyclerView.LayoutParams lp = (RecyclerView.LayoutParams)view.getLayoutParams(); RecyclerView.ViewHolder holder = parent.getChildViewHolder(view); if (overlay || !isHeader(holder)) { outRect.set(0, 0, 0, 0); } else { //TODO: Handle layout direction outRect.set(0, headerHeight, 0, 0); } if (lp.isItemRemoved()) { headers.remove(holder.getItemId()); } } public void registerAdapterDataObserver(RecyclerView.Adapter adapter) { adapter.registerAdapterDataObserver(adapterDataObserver); } private float getHeaderY(View item, RecyclerView.LayoutManager lm) { return lm.getDecoratedTop(item) < 0 ? 0 : lm.getDecoratedTop(item); } private Boolean isHeader(RecyclerView.ViewHolder holder) { if (!headers.containsKey(holder.getItemId()) || headers.get(holder.getItemId()) == null) { int itemPosition = RecyclerViewHelper.convertPreLayoutPositionToPostLayout(parent, holder.getPosition()); if (itemPosition == 0) { headers.put(holder.getItemId(), true); } else { headers.put(holder.getItemId(), adapter.getHeaderId(itemPosition) != adapter.getHeaderId(itemPosition -1)); } } return headers.get(holder.getItemId()); } private void ensureHeaderLaidOut() { if (headerHeight == NO_HEIGHT) { int widthSpec = View.MeasureSpec.makeMeasureSpec(parent.getWidth(), View.MeasureSpec.EXACTLY); int heightSpec = View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED); headerViewHolder.itemView.measure(widthSpec, heightSpec); headerViewHolder.itemView.layout(0, 0, headerViewHolder.itemView.getMeasuredWidth(), headerViewHolder.itemView.getMeasuredHeight()); headerHeight = headerViewHolder.itemView.getMeasuredHeight(); } } private class AdapterDataObserver extends RecyclerView.AdapterDataObserver { public AdapterDataObserver() { } @Override public void onItemRangeRemoved(int positionStart, int itemCount) { RecyclerView.ViewHolder holder = parent.findViewHolderForPosition(positionStart + 1); if (holder != null) { headers.put(holder.getItemId(), null); } else { cleanOffScreenItemsIds(); } } @Override public void onItemRangeInserted(int positionStart, int itemCount) { RecyclerView.ViewHolder holder = parent.findViewHolderForPosition(positionStart); if (holder != null) { headers.put(holder.getItemId(), null); } else { cleanOffScreenItemsIds(); } } private void cleanOffScreenItemsIds() { Iterator<Long> iterator = headers.keySet().iterator(); while (iterator.hasNext()) { long itemId = iterator.next(); if (parent.findViewHolderForItemId(itemId) == null) { iterator.remove(); } } } } }