package com.yuyang.stickyheaders.handler; import android.content.Context; import android.os.Build; import android.support.annotation.Nullable; import android.support.annotation.Px; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.view.View; import android.view.ViewGroup; import android.view.ViewGroup.MarginLayoutParams; import android.view.ViewTreeObserver; import com.yuyang.stickyheaders.StickyLinearLayoutManager; import java.util.List; import java.util.Map; public final class StickyHeaderHandler { private static final int INVALID_POSITION = -1; public static final int NO_ELEVATION = -1; public static final int DEFAULT_ELEVATION = 5; private final RecyclerView mRecyclerView; private RecyclerView.ViewHolder currentViewHolder; private View currentHeader; private final boolean checkMargins; private List<Integer> mHeaderPositions; private int orientation; private boolean dirty; private int lastBoundPosition = INVALID_POSITION; private float headerElevation = NO_ELEVATION; private int cachedElevation = NO_ELEVATION; @Nullable private StickyLinearLayoutManager.StickyHeaderListener listener; private final ViewTreeObserver.OnGlobalLayoutListener visibilityObserver = new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { int visibility = StickyHeaderHandler.this.mRecyclerView.getVisibility(); if (currentHeader != null) { currentHeader.setVisibility(visibility); } } }; public StickyHeaderHandler(RecyclerView recyclerView) { this.mRecyclerView = recyclerView; checkMargins = recyclerViewHasPadding(); } public void setHeaderPositions(List<Integer> headerPositions) { this.mHeaderPositions = headerPositions; } /** * @param firstVisiblePosition 第一个可见item * @param visibleHeaders 当前可见的所有header位置 * @param viewFactory header视图构造器 * @param atTop 第0个item完全可见 */ public void updateHeaderState(int firstVisiblePosition, Map<Integer, View> visibleHeaders, ViewHolderFactory viewFactory, boolean atTop) { int headerPositionToShow = atTop ? INVALID_POSITION : getHeaderPositionToShow(firstVisiblePosition, visibleHeaders.get(firstVisiblePosition)); View headerToCopy = visibleHeaders.get(headerPositionToShow); if (headerPositionToShow != lastBoundPosition) { if (headerPositionToShow == INVALID_POSITION || (checkMargins && headerAwayFromEdge(headerToCopy))) { // 如果header刚好贴边,就无需加入 dirty = true; safeDetachHeader(); lastBoundPosition = INVALID_POSITION; } else { // 否则就创建一个header视图 lastBoundPosition = headerPositionToShow; RecyclerView.ViewHolder viewHolder = viewFactory.getViewHolderForPosition(headerPositionToShow); attachHeader(viewHolder, headerPositionToShow); } } else if (checkMargins && headerAwayFromEdge(headerToCopy)) { detachHeader(lastBoundPosition); lastBoundPosition = INVALID_POSITION; } checkHeaderPositions(visibleHeaders); mRecyclerView.post(new Runnable() { @Override public void run() { checkElevation(); } }); } private void checkHeaderPositions(final Map<Integer, View> visibleHeaders) { if (currentHeader == null) return; if (currentHeader.getHeight() == 0) { waitForLayoutAndRetry(visibleHeaders); return; } boolean reset = true; for (Map.Entry<Integer, View> entry : visibleHeaders.entrySet()) { if (entry.getKey() <= lastBoundPosition) { continue; } View nextHeader = entry.getValue(); reset = offsetHeader(nextHeader) == -1; break; } if (reset) { resetTranslation(); } currentHeader.setVisibility(View.VISIBLE); } public void setElevateHeaders(int dpElevation) { if (dpElevation != NO_ELEVATION) { cachedElevation = dpElevation; } else { headerElevation = NO_ELEVATION; cachedElevation = NO_ELEVATION; } } public void reset(int orientation) { this.orientation = orientation; lastBoundPosition = INVALID_POSITION; dirty = true; safeDetachHeader(); } public void clearHeader() { detachHeader(lastBoundPosition); } public void clearVisibilityObserver() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { mRecyclerView.getViewTreeObserver().removeOnGlobalLayoutListener(visibilityObserver); } else { mRecyclerView.getViewTreeObserver().removeGlobalOnLayoutListener(visibilityObserver); } } public void setListener(@Nullable StickyLinearLayoutManager.StickyHeaderListener listener) { this.listener = listener; } private float offsetHeader(View nextHeader) { boolean shouldOffsetHeader = shouldOffsetHeader(nextHeader); float offset = -1; if (shouldOffsetHeader) { if (orientation == LinearLayoutManager.VERTICAL) { offset = -(currentHeader.getHeight() - nextHeader.getY()); currentHeader.setTranslationY(offset); } else { offset = -(currentHeader.getWidth() - nextHeader.getX()); currentHeader.setTranslationX(offset); } } return offset; } private boolean shouldOffsetHeader(View nextHeader) { if (orientation == LinearLayoutManager.VERTICAL) { return nextHeader.getY() < currentHeader.getHeight(); } else { return nextHeader.getX() < currentHeader.getWidth(); } } private void resetTranslation() { if (orientation == LinearLayoutManager.VERTICAL) { currentHeader.setTranslationY(0); } else { currentHeader.setTranslationX(0); } } private int getHeaderPositionToShow(int firstVisiblePosition, @Nullable View headerForPosition) { int headerPositionToShow = INVALID_POSITION; if (headerIsOffset(headerForPosition)) { int offsetHeaderIndex = mHeaderPositions.indexOf(firstVisiblePosition); if (offsetHeaderIndex > 0) { return mHeaderPositions.get(offsetHeaderIndex - 1); } } for (Integer headerPosition : mHeaderPositions) { if (headerPosition <= firstVisiblePosition) { // 寻找第一个可见的 item 所关联的 header 的位置 headerPositionToShow = headerPosition; } else { break; } } return headerPositionToShow; } private boolean headerIsOffset(View headerForPosition) { return headerForPosition != null && (orientation == LinearLayoutManager.VERTICAL ? headerForPosition.getY() > 0 : headerForPosition.getX() > 0); } private void attachHeader(RecyclerView.ViewHolder viewHolder, int headerPosition) { if (currentViewHolder == viewHolder) { callDetach(lastBoundPosition); mRecyclerView.getAdapter().onBindViewHolder(currentViewHolder, headerPosition); currentViewHolder.itemView.requestLayout(); checkTranslation(); callAttach(headerPosition); dirty = false; return; } detachHeader(lastBoundPosition); this.currentViewHolder = viewHolder; mRecyclerView.getAdapter().onBindViewHolder(currentViewHolder, headerPosition); this.currentHeader = currentViewHolder.itemView; callAttach(headerPosition); resolveElevationSettings(currentHeader.getContext()); currentHeader.setVisibility(View.INVISIBLE); mRecyclerView.getViewTreeObserver().addOnGlobalLayoutListener(visibilityObserver); getRecyclerParent().addView(currentHeader); if (checkMargins) { updateLayoutParams(currentHeader); } dirty = false; } private int currentDimension() { if (currentHeader == null) { return 0; } if (orientation == LinearLayoutManager.VERTICAL) { return currentHeader.getHeight(); } else { return currentHeader.getWidth(); } } private boolean headerHasTranslation() { if (currentHeader == null) { return false; } if (orientation == LinearLayoutManager.VERTICAL) { return currentHeader.getTranslationY() < 0; } else { return currentHeader.getTranslationX() < 0; } } private void updateTranslation(int diff) { if (currentHeader == null) { return; } if (orientation == LinearLayoutManager.VERTICAL) { currentHeader.setTranslationY(currentHeader.getTranslationY() + diff); } else { currentHeader.setTranslationX(currentHeader.getTranslationX() + diff); } } private void checkTranslation() { final View view = currentHeader; if (view == null) return; view.getViewTreeObserver().addOnGlobalLayoutListener(new ViewTreeObserver.OnGlobalLayoutListener() { int previous = currentDimension(); @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); } if (currentHeader == null) return; int newDimen = currentDimension(); if (headerHasTranslation() && previous != newDimen) { updateTranslation(previous - newDimen); } } }); } private void checkElevation() { if (headerElevation != NO_ELEVATION && currentHeader != null) { if (orientation == LinearLayoutManager.VERTICAL && currentHeader.getTranslationY() == 0 || orientation == LinearLayoutManager.HORIZONTAL && currentHeader.getTranslationX() == 0) { elevateHeader(); } else { settleHeader(); } } } private void elevateHeader() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (currentHeader.getTag() != null) { return; } currentHeader.setTag(true); currentHeader.animate().z(headerElevation); } } private void settleHeader() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { if (currentHeader.getTag() != null) { currentHeader.setTag(null); currentHeader.animate().z(0); } } } private void detachHeader(int position) { if (currentHeader != null) { getRecyclerParent().removeView(currentHeader); callDetach(position); clearVisibilityObserver(); currentHeader = null; currentViewHolder = null; } } private void callAttach(int position) { if (listener != null) { listener.headerAttached(currentHeader, position); } } private void callDetach(int position) { if (listener != null) { listener.headerDetached(currentHeader, position); } } private void updateLayoutParams(View currentHeader) { MarginLayoutParams params = (MarginLayoutParams) currentHeader.getLayoutParams(); matchMarginsToPadding(params); } private void matchMarginsToPadding(MarginLayoutParams layoutParams) { @Px int leftMargin = orientation == LinearLayoutManager.VERTICAL ? mRecyclerView.getPaddingLeft() : 0; @Px int topMargin = orientation == LinearLayoutManager.VERTICAL ? 0 : mRecyclerView.getPaddingTop(); @Px int rightMargin = orientation == LinearLayoutManager.VERTICAL ? mRecyclerView.getPaddingRight() : 0; layoutParams.setMargins(leftMargin, topMargin, rightMargin, 0); } private boolean headerAwayFromEdge(View headerToCopy) { return headerToCopy != null && (orientation == LinearLayoutManager.VERTICAL ? headerToCopy.getY() > 0 : headerToCopy.getX() > 0); } private boolean recyclerViewHasPadding() { return mRecyclerView.getPaddingLeft() > 0 || mRecyclerView.getPaddingRight() > 0 || mRecyclerView.getPaddingTop() > 0; } private ViewGroup getRecyclerParent() { return (ViewGroup) mRecyclerView.getParent(); } private void waitForLayoutAndRetry(final Map<Integer, View> visibleHeaders) { final View view = currentHeader; if (view == null) return; view.getViewTreeObserver().addOnGlobalLayoutListener( new ViewTreeObserver.OnGlobalLayoutListener() { @Override public void onGlobalLayout() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { view.getViewTreeObserver().removeOnGlobalLayoutListener(this); } else { view.getViewTreeObserver().removeGlobalOnLayoutListener(this); } if (currentHeader == null) return; getRecyclerParent().requestLayout(); checkHeaderPositions(visibleHeaders); } }); } private void safeDetachHeader() { final int cachedPosition = lastBoundPosition; getRecyclerParent().post(new Runnable() { @Override public void run() { if (dirty) { detachHeader(cachedPosition); } } }); } private void resolveElevationSettings(Context context) { if (cachedElevation != NO_ELEVATION && headerElevation == NO_ELEVATION) { headerElevation = dp2px(context, cachedElevation); } } private float dp2px(Context context, int dp) { float scale = context.getResources().getDisplayMetrics().density; return dp * scale; } }