Before I open source on making a can realize universal Adapter RecyclerView list grouping: GroupedRecyclerViewAdapter. I also wrote a blog post about its implementation and use: Android grouped RecyclerViewAdapter. After reading my blog posts and using my open source library, some friends would give me feedback and suggestions. It makes me feel like I’m not only improving myself, but also helping others (though I rarely blog or open source). A few days ago, a friend told me that he not only needed a list of groups, but also wanted to add headers and tails to the list. Actually GroupedRecyclerViewAdapter each grouping is can set the head and tail. If just for a simple list to add the head and tail, only need to use GroupedRecyclerViewAdapter list can be the only one grouping. But he hopes to achieve multiple groups at the same time, to the big list also set up the head and tail, such demand is I never thought of before, but I’m still gives his own advice: the list of the first group, as long as the head not the tail and items, use it as a whole list of big head, the realization of the tail, too. This can also fulfill his needs, but the processing logic is a little more complicated. I actually GroupedRecyclerViewAdapter in the design, in order to make it have better expansibility and can realize more complex layout, so give it a head, tail and child support multiple types of ViewTtype, welcome interested friends to have a look.

ListView adds a header and a tail implementation principle

When we used ListView in the past, ListView gave us methods to add headers (addHeaderView()) and tails (addFooterView()), so we could easily add headers and tails to lists. And the header and tail have nothing to do with our own ListView Adapter at all. So how does ListView do that? Let’s open the ListView source and see how it adds a header (addHeaderView()) to the list. (The principle of adding a tail is the same, so I won’t mention it separately here.)

    public void addHeaderView(View v) {
        addHeaderView(v, null.true);
    }

    public void addHeaderView(View v, Object data, boolean isSelectable) {
        final FixedViewInfo info = new FixedViewInfo();
        info.view = v;
        info.data = data;
        info.isSelectable = isSelectable;
        mHeaderViewInfos.add(info);
        mAreAllItemsSelectable &= isSelectable;

        // Wrap the ListView Adapter as HeaderViewListAdapter.
        if(mAdapter ! =null) {
            if(! (mAdapterinstanceof HeaderViewListAdapter)) {
                mAdapter = new HeaderViewListAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter);
            }

            if(mDataSetObserver ! =null) { mDataSetObserver.onChanged(); }}}Copy the code

Above is the source of the ListView header method, which determines whether the current ListView Adapter is a HeaderViewListAdapter. If not, this wraps the current ListView Adapter as a HeaderViewListAdapter. So HeaderViewListAdapter is the key to implementing a ListView to add a header, so let’s take a look at what a HeaderViewListAdapter is.

    public class HeaderViewListAdapter implements WrapperListAdapter.Filterable{}Copy the code

HeaderViewListAdapter implements the WrapperListAdapter interface, which is a subinterface of ListAdapter, So HeaderViewListAdapter is an implementation class of ListAdapter, not much different from a normal ListView Adapter. HeaderViewListAdapter receives a normal ListView Adapter and ListView header list and tail list, and manages them uniformly. Let’s take a look at its core code.

    Constructor: receives header list information and tail list information passed in from the outside, as well as a plain wrapped ListAdapter.
    public HeaderViewListAdapter(ArrayList
       
         headerViewInfos, ArrayList
        
          footerViewInfos, ListAdapter adapter)
        
        {}// Return the number of items in the list.
    public int getCount(a) {
        if(mAdapter ! =null) {
            return getFootersCount() + getHeadersCount() + mAdapter.getCount();
        } else {
            returngetFootersCount() + getHeadersCount(); }} / Returns the ViewType of the current list item.public int getItemViewType(int position) {
        int numHeaders = getHeadersCount();
        if(mAdapter ! =null && position >= numHeaders) {
            int adjPosition = position - numHeaders;
            int adapterCount = mAdapter.getCount();
            // If the current list item is a normal list item, it is passed to the mAdapter.
            // Position passed to the mAdapter needs to be handled in addition to the tail of the u-turn.
            if (adjPosition < adapterCount) {
                returnmAdapter.getItemViewType(adjPosition); }}// Returns the head or tail of the current list item
        return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        // If the current list item is a header, the corresponding header layout is returned.
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).view;
        }

        // If the current list item is a normal list item, it is passed to the mAdapter.
        // Position passed to the mAdapter needs to be handled in addition to the tail of the u-turn.
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if(mAdapter ! =null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.getView(adjPosition, convertView, parent); }}// If the current list item is a tail, the corresponding tail layout is returned.
        return mFooterViewInfos.get(adjPosition - adapterCount).view;
    }Copy the code

This is the core code of the HeaderViewListAdapter. As you can see, the code is very simple and does some processing in getCount(), getItemViewType(), and getView(). GetCount () adds the number of heads and tails to the number it returns. In getItemViewType() and getView(), it determines if the current list item is a header or a tail, otherwise it passes to the wrapped normal Adapter. So the main purpose of the HeaderViewListAdapter is to manage the head and tail of the ListView.

The HeaderViewListAdapter design is very clever, just need to set the Adapter we set to the ListView wrapper, we can make our ListView has the function of adding a header and tail, and does not affect our original Adapter. Even if we don’t know that the ListView has wrapped our original Adapter as a HeaderViewListAdapter when we add the header to the ListView, we don’t need to care about its implementation logic.

According to the design idea of HeaderViewListAdapter, we can also give our recyclerView. Adapter to implement a wrapper class, as long as our own Adapter wrapper, we can make our list with the function of adding the head and tail? With this in mind, I wrote a wrapper class for RecyclerView adapters: HeaderViewAdapter.

HeaderViewAdapter code implementation

HeaderViewAdapter design ideas and implementation effect with HeaderViewListAdapter is exactly the same, in the implementation of the code will be different, after all, ListView Adapter and RecyclerView Adapter is completely different two things.

public class HeaderViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {

    // The wrapped Adapter.
    private RecyclerView.Adapter mAdapter;

    // Store HeaderView
    private final List<FixedViewInfo> mHeaderViewInfos = new ArrayList<>();

    // Store the FooterView
    private final List<FixedViewInfo> mFooterViewInfos = new ArrayList<>();

    // A listener that listens for changes in the data of the wrapped Adapter. It maps data changes of the wrapped Adapter to changes of the HeaderViewAdapter.
    private RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
                // Here is the concrete code implementation, because of space, I will not show it here.
    };

    public HeaderViewAdapter(RecyclerView.Adapter adapter) {
        this.mAdapter = adapter;
        if(mAdapter ! =null) {
            // Register the mAdapter data change listenermAdapter.registerAdapterDataObserver(mObserver); }}@Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        // Find the corresponding HeaderView or FooterView according to viewType. If not found, the viewType is a normal list item.
        View view = findViewForInfos(viewType);
        if(view ! =null) {
            return new ViewHolder(view);
        } else {
            // The mAdapter will handle it.
            returnmAdapter.onCreateViewHolder(parent, viewType); }}@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        // If it is HeaderView or FooterView, no data is bound.
        // Since HeaderView and FooterView are passed in from outside, they are not updated by the list.
        if (isHeader(position) || isFooter(position)) {
            return;
        }

        // Adjust the actual position of the list to the position corresponding to the mAdapter.
        // The mAdapter will handle it.
        int adjPosition = position - getHeadersCount();
        mAdapter.onBindViewHolder(holder, adjPosition);
    }

    @Override
    public int getItemCount(a) {
        return mHeaderViewInfos.size() + mFooterViewInfos.size()
                + (mAdapter == null ? 0 : mAdapter.getItemCount());
    }

    @Override
    public int getItemViewType(int position) {
        // If the current item is HeaderView, return the corresponding itemViewType of HeaderView.
        if (isHeader(position)) {
            return mHeaderViewInfos.get(position).itemViewType;
        }

        // If the current item is HeaderView, return the corresponding itemViewType of HeaderView.
        if (isFooter(position)) {
            return mFooterViewInfos.get(position - mHeaderViewInfos.size() - mAdapter.getItemCount()).itemViewType;
        }

        // Adjust the actual position of the list to the position corresponding to the mAdapter.
        // The mAdapter will handle it.
        int adjPosition = position - getHeadersCount();
        return mAdapter.getItemViewType(adjPosition);
    }

    /** * Check whether the current position is a header View. * *@paramPosition The position here is the position for the entire list (including HeaderView and FooterView). *@return* /
    public boolean isHeader(int position) {
        return position < getHeadersCount();
    }

    /** * Check whether the current position is a tail View. * *@paramPosition The position here is the position for the entire list (including HeaderView and FooterView). *@return* /
    public boolean isFooter(int position) {
        return getItemCount() - position <= getFootersCount();
    }

    /** * Get the number of headerViews **@return* /
    public int getHeadersCount(a) {
        return mHeaderViewInfos.size();
    }

    /** * Get the number of footerViews **@return* /
    public int getFootersCount(a) {
        return mFooterViewInfos.size();
    }

    /** * Add HeaderView **@param view
     */
    public void addHeaderView(View view) {
        addHeaderView(view, generateUniqueViewType());
    }

    private void addHeaderView(View view, int viewType) {
        // Wrap HeaderView data and add it to the list
        FixedViewInfo info = new FixedViewInfo();
        info.view = view;
        info.itemViewType = viewType;
        mHeaderViewInfos.add(info);
        notifyDataSetChanged();
    }

    /** * Add FooterView **@param view
     */
    public void addFooterView(View view) {
        addFooterView(view, generateUniqueViewType());
    }

    private void addFooterView(View view, int viewType) {
        // Wrap FooterView data and add it to the list
        FixedViewInfo info = new FixedViewInfo();
        info.view = view;
        info.itemViewType = viewType;
        mFooterViewInfos.add(info);
        notifyDataSetChanged();
    }

    /** * Generates a unique number that identifies the type of HeaderView or FooterView and ensures that the type does not duplicate. * *@return* /
    private int generateUniqueViewType(a) {
        int count = getItemCount();
        while (true) {
            // Generate a random number.
            int viewType = (int) (Math.random() * Integer.MAX_VALUE) + 1;

            // Check whether the viewType is already in use.
            boolean isExist = false;
            for (int i = 0; i < count; i++) {
                if (viewType == getItemViewType(i)) {
                    isExist = true;
                    break; }}// If the viewType is not already used, return the viewType. Otherwise, the next loop will regenerate the random number.
            if(! isExist) {returnviewType; }}}/** * Find the corresponding HeaderView or FooterView based on viewType. If not found, null is returned. * *@paramViewType Search viewType *@return* /
    private View findViewForInfos(int viewType) {
        for (FixedViewInfo info : mHeaderViewInfos) {
            if (info.itemViewType == viewType) {
                returninfo.view; }}for (FixedViewInfo info : mFooterViewInfos) {
            if (info.itemViewType == viewType) {
                returninfo.view; }}return null;
    }

    /** * The data class that wraps HeaderView and FooterView */
    private class FixedViewInfo {
        // Save HeaderView or FooterView
        View view;

        // Save the viewType corresponding to HeaderView or FooterView.
        int itemViewType;
    }

    private static class ViewHolder extends RecyclerView.ViewHolder {
        ViewHolder(View itemView) {
            super(itemView); }}}Copy the code

In order to make it easier for you to see the code, I have deleted some of the code is not very important, and the implementation of the code to explain the details of each method on the comments, I believe that you can easily understand. If you want to see the full code, please go to my GitHub.

The use of HeaderViewAdapter

I’ve packed the HeaderViewAdapter and related classes into a reference library and put it on GitHub. Welcome to use and Star.

In ListView design, all operations on the HeaderViewListAdapter are performed by the ListView itself. But we can not put the operation of HeaderViewAdapter by RecyclerView to process, so we need to operate the HeaderViewAdapter (packaging, add head and tail, etc.), in fact, this is very simple, a few words of code. And it can be applied to any RecyclerView, without any restrictions on use.

    // The adapter needs to be wrapped
    LinearAdapter adapter = new LinearAdapter(this);

    // Wrap the Adapter.
    HeaderViewAdapter headerViewAdapter = new HeaderViewAdapter(adapter);

    // Add HeaderView and FooterView
    headerViewAdapter.addHeaderView(headerView);
    headerViewAdapter.addFooterView(footerView);

    / / set the Adapter
    recyclerView.setAdapter(headerViewAdapter);Copy the code

No matter what LayoutManager our RecyclerView uses, the HeaderViewAdapter needs to make sure that the head and tail of the list fill a row, otherwise the layout will be ugly. When using LinearLayoutManager don’t need to do special processing, HeaderViewAdapter also has help us deal with the StaggeredGridLayoutManager. As for GridLayoutManager situation, my garage in HeaderViewAdapter provides a HeaderViewGridLayoutManager subclass. So everybody when using GridLayoutManager HeaderViewGridLayoutManager should be used.

    recyclerView.setLayoutManager(new HeaderViewGridLayoutManager(this.2, headerViewAdapter));Copy the code

In order to make our RecyclerView add head and tail time, closer to the ListView experience. So I provided a RecyclerView subclass in the library: HeaderRecyclerView. HeaderRecyclerView encapsulates all operations on HeaderViewAdapter, which makes us only need to operate HeaderRecyclerView, without directly dealing with HeaderViewAdapter, This allows us to use HeaderRecyclerView just like we used to use ListView.

    HeaderRecyclerView rvList = (HeaderRecyclerView) findViewById(R.id.rv_list);
    // This is a normal adapter
    GridAdapter adapter = new GridAdapter(this);
    rvList.setLayoutManager(new GridLayoutManager(this.2));
    // Set up the normal adapter directly, without wrapping it directly.
    rvList.setAdapter(adapter);

    // Add HeaderView and FooterView. Operate HeaderRecyclerView directly.
    rvList.addHeaderView(headerView);
    rvList.addFooterView(footerView);Copy the code

Effect:

LinearList.gif


GridList.gif

Portal: github.com/donkinglian…