Before I open source on making a can realize universal Adapter RecyclerView list grouping: GroupedRecyclerViewAdapter. Some friends gave me feedback when using it, hoping to achieve the effect of head suspension and top suction. I had GroupedRecyclerViewAdapter design goal, is to achieve a convenient management RecyclerView variety of item types of Adapter, can realize the two level list Adapter, in particular, are common for such requirements in development. So I didn’t think about levitation. It wasn’t until I got feedback from these users that I started thinking about adding such a feature. But want to also should add this function, because the head suspension generally appear in two levels of grouping list, while my GroupedRecyclerViewAdapter already achieved two level grouping list, add a head floating function also is very reasonable.

To give RecyclerView function of floating head in GroupedRecyclerViewAdapter framework I added a StickyHeaderLayout controls, StickyHeaderLayout implements head suspension and manages the View of the suspended top. Now I’m going to give you StickyHeaderLayout source code, I’ve got a detailed annotation of the implementation of StickyHeaderLayout in the source code, I believe you can understand it well. Because of its StickyHeaderLayout is expanding, the function of the GroupedRecyclerViewAdapter with GroupedRecyclerViewAdapter are closely related. So before reading the source, you need to first understand GroupedRecyclerViewAdapter, and StickyHeaderLayout are to be of use with GroupedRecyclerViewAdapter. To understand GroupedRecyclerViewAdapter, please see my another article: “the Android can be grouped RecyclerViewAdapter”. If you just want to use its features and don’t need to know how it works, you can also go directly to my GitHub.

StickyHeaderLayout source:

/** * Depiction: the head Depiction of the top layout. Just wrap it with StickyHeaderLayout {@linkRecyclerView}, * and use {@linkGroupedRecyclerViewAdapter}, can realize the list head top absorption function. StickyHeaderLayout can only wrap RecyclerView, and only one RecyclerView. * <p> * Author:donkingliang * Dat:2017/11/14 */
public class StickyHeaderLayout extends FrameLayout {

    private Context mContext;
    private RecyclerView mRecyclerView;

    // Ceiling container, used to carry the ceiling layout.
    private FrameLayout mStickyLayout;

    // Save the cache pool for the ceiling layout. It takes the viewType of the list group header as the key and the ViewHolder as the value to save and recycle the top layout.
    private final SparseArray<BaseViewHolder> mStickyViews = new SparseArray<>();

    // The key used to hold the viewType in the top layout.
    private final int VIEW_TAG_TYPE = -101;

    // Hold the key of the ViewHolder in the ceiling layout.
    private final int VIEW_TAG_HOLDER = -102;

    // Record the current top group.
    private int mCurrentStickyGroup = -1;

    // Whether to suck the top.
    private boolean isSticky = true;

    public StickyHeaderLayout(@NonNull Context context) {
        super(context);
        mContext = context;
    }

    public StickyHeaderLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
        mContext = context;
    }

    public StickyHeaderLayout(@NonNull Context context, @Nullable AttributeSet attrs, @AttrRes int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        mContext = context;
    }

    @Override
    public void addView(View child, int index, ViewGroup.LayoutParams params) {
        if (getChildCount() > 0| |! (childinstanceof RecyclerView)) {
            // You can only add a RecyclerView to StickyHeaderLayout, and you can only add RecyclerView.
            throw new IllegalArgumentException("StickyHeaderLayout can host only one direct child --> RecyclerView");
        }
        super.addView(child, index, params);
        mRecyclerView = (RecyclerView) child;
        addOnScrollListener();
        addStickyLayout();
    }

    /** * add scrolllistener */
    private void addOnScrollListener(a) {
        mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
            @Override
            public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
                // The layout of the ceiling needs to be constantly updated while scrolling.
                if(isSticky) { updateStickyView(); }}}); }/** * add top container */
    private void addStickyLayout(a) {
        mStickyLayout = new FrameLayout(mContext);
        LayoutParams lp = new LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT,
                FrameLayout.LayoutParams.WRAP_CONTENT);
        mStickyLayout.setLayoutParams(lp);
        super.addView(mStickyLayout, 1, lp);
    }

    /** * Updated the roof layout. * /
    private void updateStickyView(a) {
        RecyclerView.Adapter adapter = mRecyclerView.getAdapter();
        / / only RecyclerView adapter is GroupedRecyclerViewAdapter, will add the top layout.
        if (adapter instanceof GroupedRecyclerViewAdapter) {
            GroupedRecyclerViewAdapter gAdapter = (GroupedRecyclerViewAdapter) adapter;

            // Get the first item displayed in the list.
            int firstVisibleItem = getFirstVisibleItem();
            // Get the group it is in by position of the first item displayed.
            int groupPosition = gAdapter.getGroupPositionForPosition(firstVisibleItem);

            // If the current top group is not the one we want to top, update the top layout. This avoids frequent updates to the ceiling layout.
            if(mCurrentStickyGroup ! = groupPosition) { mCurrentStickyGroup = groupPosition;// Get the group header position of the current group by groupPosition. This set of heads is the layout we need for the top.
                int groupHeaderPosition = gAdapter.getPositionForGroupHeader(groupPosition);
                if(groupHeaderPosition ! = -1) {
                    // Get the top layout viewType.
                    int viewType = gAdapter.getItemViewType(groupHeaderPosition);

                    // If the current layout is of the same type as the one we need, fetch its ViewHolder directly, otherwise reclaim it.
                    BaseViewHolder holder = recycleStickyView(viewType);

                    // indicates whether the holder is removed from the current ceiling layout.
                    booleanflag = holder ! =null;

                    if (holder == null) {
                        // Get the top layout from the cache pool.
                        holder = getStickyViewByType(viewType);
                    }

                    if (holder == null) {
                        / / if not obtained from the buffer pool to the layout, suction a top by GroupedRecyclerViewAdapter created.
                        holder = gAdapter.onCreateViewHolder(mStickyLayout, viewType);
                        holder.itemView.setTag(VIEW_TAG_TYPE, viewType);
                        holder.itemView.setTag(VIEW_TAG_HOLDER, holder);
                    }

                    / / by GroupedRecyclerViewAdapter update the layout of the data.
                    // This ensures that the top layout looks the same as the group headers in the list.
                    gAdapter.onBindViewHolder(holder, groupHeaderPosition);

                    // If the holder is not extracted from the current top layout, add the top layout to the container.
                    if (!flag) {
                        mStickyLayout.addView(holder.itemView);
                    }
                } else {
                    // If the current group does not have a group header, the top layout is not displayed.
                    // Recycle the old ceiling layout.recycle(); }}// This handles the situation where the top layout is added to "StickyLayout" the first time it opens, but the height of "StickyLayout" is still 0.
            if (mStickyLayout.getChildCount() > 0 && mStickyLayout.getHeight() == 0) {
                mStickyLayout.requestLayout();
            }

            // Set the Y offset for mStickyLayout.
            mStickyLayout.setTranslationY(calculateOffset(gAdapter, firstVisibleItem, groupPosition + 1)); }}/** * Determine whether the top layout needs to be recycled first, if so, recycle the top layout and return NULL. * If not, return the ViewHolder for the suction top layout. * This avoids frequent addition and removal of roof top layouts. * *@param viewType
     * @return* /
    private BaseViewHolder recycleStickyView(int viewType) {
        if (mStickyLayout.getChildCount() > 0) {
            View view = mStickyLayout.getChildAt(0);
            int type = (int) view.getTag(VIEW_TAG_TYPE);
            if (type == viewType) {
                return (BaseViewHolder) view.getTag(VIEW_TAG_HOLDER);
            } else{ recycle(); }}return null;
    }

    /** * Recycle and remove the top layout */
    private void recycle(a) {
        if (mStickyLayout.getChildCount() > 0) {
            View view = mStickyLayout.getChildAt(0);
            mStickyViews.put((int) (view.getTag(VIEW_TAG_TYPE)), (BaseViewHolder) (view.getTag(VIEW_TAG_HOLDER))); mStickyLayout.removeAllViews(); }}/** * Get the top layout from the cache pool **@paramViewType viewType * of the top layout@return* /
    private BaseViewHolder getStickyViewByType(int viewType) {
        return mStickyViews.get(viewType);
    }

    /** * Calculates the offset of StickyLayout. Because if the next group heads to StickyLayout, * you have to put StickyLayout on top until the next group heads to the top. Otherwise, the two group heads overlap. * *@param gAdapter
     * @paramFirstVisibleItem The first item displayed in the current list. *@paramGroupPosition Indicates the group subscript of a group. *@returnReturns the offset. * /
    private float calculateOffset(GroupedRecyclerViewAdapter gAdapter, int firstVisibleItem, int groupPosition) {
        int groupHeaderPosition = gAdapter.getPositionForGroupHeader(groupPosition);
        if(groupHeaderPosition ! = -1) {
            int index = groupHeaderPosition - firstVisibleItem;
            if (mRecyclerView.getChildCount() > index) {
                // Get the itemView for the group header of the next group.
                View view = mRecyclerView.getChildAt(index);
                float off = view.getY() - mStickyLayout.getHeight();
                if (off < 0) {
                    returnoff; }}}return 0;
    }

    /** * gets the current first display item. */
    private int getFirstVisibleItem(a) {
        int firstVisibleItem = -1;
        RecyclerView.LayoutManager layout = mRecyclerView.getLayoutManager();
        if(layout ! =null) {
            if (layout instanceof LinearLayoutManager) {
                firstVisibleItem = ((LinearLayoutManager) layout).findFirstVisibleItemPosition();
            } else if (layout instanceof GridLayoutManager) {
                firstVisibleItem = ((GridLayoutManager) layout).findFirstVisibleItemPosition();
            } else if (layout instanceof StaggeredGridLayoutManager) {
                int[] firstPositions = new int[((StaggeredGridLayoutManager) layout).getSpanCount()]; ((StaggeredGridLayoutManager) layout).findFirstVisibleItemPositions(firstPositions); firstVisibleItem = getMin(firstPositions); }}return firstVisibleItem;
    }

    private int getMin(int[] arr) {
        int min = arr[0];
        for (int x = 1; x < arr.length; x++) {
            if (arr[x] < min)
                min = arr[x];
        }
        return min;
    }

    /** * whether to suck the top **@return* /
    public boolean isSticky(a) {
        return isSticky;
    }

    /** * Sets whether to suck the top. * *@param sticky
     */
    public void setSticky(boolean sticky) {
        if(isSticky ! = sticky) { isSticky = sticky;if(mStickyLayout ! =null) {
                if (isSticky) {
                    mStickyLayout.setVisibility(VISIBLE);
                    updateStickyView();
                } else {
                    recycle();
                    mStickyLayout.setVisibility(GONE);
                }
            }
        }
    }
}Copy the code

StickyHeaderLayout has the following advantages: 1. Non-itemdecoration. StickyHeaderLayout’s hover View is a real View, not a simple image, so it can hover any View. This is different from using ItemDecoration for levitation. 2, and GroupedRecyclerViewAdapter perfect union. Floating layouts are created and updated directly by The Adapter, which makes the dangling layout display and processing (event listeners, business logic, and so on) consistent with the items in the list. You can think of a floating layout as an item in a list. And GroupedRecyclerViewAdapter supports a variety of item types, so the suspension layout can also support a variety of item types. 3. StickyHeaderLayout cache reuse of floating layouts to avoid unnecessary creation, update, and removal operations. Optimize the interface to draw smoothly. 4. Easy to use. You only need to use RecyclerView StickyHeaderLayout package, and use GroupedRecyclerViewAdapter two-stage list can achieve them.

Effect:

Head suction top list.gif

Portal: github.com/donkinglian…