preface

RecyclerView is favored by everyone through its high customizability, and there are also a lot of users began to package or transform it, so as to meet more and more needs, widely used in all kinds of App development. So if you’re familiar with RecyclerView, you’ve probably run into a situation where I’m going to add a headerView or a footerView to RecyclerView, and when you type addHeaderView, you’ll see that there’s no API for adding a headerView or a bottom View. But we can use the addHeaderView() and addFooterView() methods in ListView to create a RecyclerView.

ListView add HeaderView and FooterView source analysis

ListView about HeaderView and FooterView source

  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 adapter if it wasn't already wrapped.
        if(mAdapter ! =null) {
            if(! (mAdapterinstanceof HeaderViewListAdapter)) {
                wrapHeaderListAdapterInternal();
            }

            // In the case of re-adding a header view, or adding one later on,
            // we need to notify the observer.
            if(mDataSetObserver ! =null) { mDataSetObserver.onChanged(); }}}public void addFooterView(View v, Object data, boolean isSelectable) {
        final FixedViewInfo info = new FixedViewInfo();
        info.view = v;
        info.data = data;
        info.isSelectable = isSelectable;
        mFooterViewInfos.add(info);
        mAreAllItemsSelectable &= isSelectable;

        // Wrap the adapter if it wasn't already wrapped.
        if(mAdapter ! =null) {
            if(! (mAdapterinstanceof HeaderViewListAdapter)) {
                wrapHeaderListAdapterInternal();
            }

            // In the case of re-adding a footer view, or adding one later on,
            // we need to notify the observer.
            if(mDataSetObserver ! =null) { mDataSetObserver.onChanged(); }}}public void addFooterView(View v) {
        addFooterView(v, null.true);
    }


   protected HeaderViewListAdapter wrapHeaderListAdapterInternal( ArrayList
       
         headerViewInfos, ArrayList
        
          footerViewInfos, ListAdapter adapter)
        
        {
        return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
    }

    / * *@hide* /
    protected void wrapHeaderListAdapterInternal(a) {
        mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
    }
Copy the code
HeaderViewListAdapter for source code analysis

public class HeaderViewListAdapter implements WrapperListAdapter.Filterable {

    private final ListAdapter mAdapter;

    // These two ArrayList are assumed to NOT be null.
    // They are indeed created when declared in ListView and then shared.
    ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
    ArrayList<ListView.FixedViewInfo> mFooterViewInfos;

    // Used as a placeholder in case the provided info views are indeed null.
    // Currently only used by some CTS tests, which may be removed.
    static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
        new ArrayList<ListView.FixedViewInfo>();

    boolean mAreAllFixedViewsSelectable;

    private final boolean mIsFilterable;

    public HeaderViewListAdapter(ArrayList
       
         headerViewInfos, ArrayList
        
          footerViewInfos, ListAdapter adapter)
        
        {
        mAdapter = adapter;
        mIsFilterable = adapter instanceof Filterable;

        if (headerViewInfos == null) {
            mHeaderViewInfos = EMPTY_INFO_LIST;
        } else {
            mHeaderViewInfos = headerViewInfos;
        }

        if (footerViewInfos == null) {
            mFooterViewInfos = EMPTY_INFO_LIST;
        } else {
            mFooterViewInfos = footerViewInfos;
        }

        mAreAllFixedViewsSelectable =
                areAllListInfosSelectable(mHeaderViewInfos)
                && areAllListInfosSelectable(mFooterViewInfos);
    }

    public int getHeadersCount(a) {
        return mHeaderViewInfos.size();
    }

    public int getFootersCount(a) {
        return mFooterViewInfos.size();
    }

    public boolean isEmpty(a) {
        return mAdapter == null || mAdapter.isEmpty();
    }

    private boolean areAllListInfosSelectable(ArrayList<ListView.FixedViewInfo> infos) {
        if(infos ! =null) {
            for (ListView.FixedViewInfo info : infos) {
                if(! info.isSelectable) {return false; }}}return true;
    }

    public boolean removeHeader(View v) {
        for (int i = 0; i < mHeaderViewInfos.size(); i++) {
            ListView.FixedViewInfo info = mHeaderViewInfos.get(i);
            if (info.view == v) {
                mHeaderViewInfos.remove(i);

                mAreAllFixedViewsSelectable =
                        areAllListInfosSelectable(mHeaderViewInfos)
                        && areAllListInfosSelectable(mFooterViewInfos);

                return true; }}return false;
    }

    public boolean removeFooter(View v) {
        for (int i = 0; i < mFooterViewInfos.size(); i++) {
            ListView.FixedViewInfo info = mFooterViewInfos.get(i);
            if (info.view == v) {
                mFooterViewInfos.remove(i);

                mAreAllFixedViewsSelectable =
                        areAllListInfosSelectable(mHeaderViewInfos)
                        && areAllListInfosSelectable(mFooterViewInfos);

                return true; }}return false;
    }

    public int getCount(a) {
        if(mAdapter ! =null) {
            return getFootersCount() + getHeadersCount() + mAdapter.getCount();
        } else {
            returngetFootersCount() + getHeadersCount(); }}public boolean areAllItemsEnabled(a) {
        if(mAdapter ! =null) {
            return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
        } else {
            return true; }}public boolean isEnabled(int position) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).isSelectable;
        }

        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if(mAdapter ! =null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.isEnabled(adjPosition); }}// Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
    }

    public Object getItem(int position) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).data;
        }

        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if(mAdapter ! =null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.getItem(adjPosition); }}// Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).data;
    }

    public long getItemId(int position) {
        int numHeaders = getHeadersCount();
        if(mAdapter ! =null && position >= numHeaders) {
            int adjPosition = position - numHeaders;
            int adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.getItemId(adjPosition); }}return -1;
    }

    public boolean hasStableIds(a) {
        if(mAdapter ! =null) {
            return mAdapter.hasStableIds();
        }
        return false;
    }

    public View getView(int position, View convertView, ViewGroup parent) {
        // Header (negative positions will throw an IndexOutOfBoundsException)
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {
            return mHeaderViewInfos.get(position).view;
        }

        // Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if(mAdapter ! =null) {
            adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.getView(adjPosition, convertView, parent); }}// Footer (off-limits positions will throw an IndexOutOfBoundsException)
        return mFooterViewInfos.get(adjPosition - adapterCount).view;
    }

    public int getItemViewType(int position) {
        int numHeaders = getHeadersCount();
        if(mAdapter ! =null && position >= numHeaders) {
            int adjPosition = position - numHeaders;
            int adapterCount = mAdapter.getCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.getItemViewType(adjPosition); }}return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
    }

    public int getViewTypeCount(a) {
        if(mAdapter ! =null) {
            return mAdapter.getViewTypeCount();
        }
        return 1;
    }

    public void registerDataSetObserver(DataSetObserver observer) {
        if(mAdapter ! =null) { mAdapter.registerDataSetObserver(observer); }}public void unregisterDataSetObserver(DataSetObserver observer) {
        if(mAdapter ! =null) { mAdapter.unregisterDataSetObserver(observer); }}public Filter getFilter(a) {
        if (mIsFilterable) {
            return ((Filterable) mAdapter).getFilter();
        }
        return null;
    }
    
    public ListAdapter getWrappedAdapter(a) {
        returnmAdapter; }}Copy the code
Source code analysis summary:
  1. callHeaderViewandFooterViewIs invokedwrapHeaderListAdapterInternalmakemAdapterintoHeaderViewListAdapterType.
  2. HeaderViewListAdapterIn practice, yesListAdapterDoes the proxy operation return orListAdapterType.

2. Add head and footer for RecyclerView according to ListView design idea

2.1. Encapsulation RecyclerView

public class WrapRecyclerView extends RecyclerView {
    private ArrayList<View> mHeaderViewInfos = new ArrayList<>();
    private ArrayList<View> mFooterViewInfos = new ArrayList<>();
    private RecyclerView.Adapter mAdapter;


    public WrapRecyclerView(Context context, @Nullable AttributeSet attrs) {
        super(context, attrs);
    }

    public void addHeaderView(View v) {
        mHeaderViewInfos.add(v);
        if(mAdapter ! =null) {
            if(! (mAdapterinstanceof HeaderViewRecyclerAdapter)) {
                mAdapter = newHeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter); }}}public void addFooterView(View v) {
        mFooterViewInfos.add(v);
        if(mAdapter ! =null) {
            if(! (mAdapterinstanceof HeaderViewRecyclerAdapter)) {
                mAdapter = newHeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, mAdapter); }}}@Override
    public void setAdapter(RecyclerView.Adapter adapter) {
        this.mAdapter = adapter;
        if (mHeaderViewInfos.size() > 0 || mFooterViewInfos.size() > 0) {
            Log.d("http:"."9999");
            mAdapter = new HeaderViewRecyclerAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
        } else {
            mAdapter = adapter;
        }
        super.setAdapter(mAdapter); }}Copy the code
2.2. MyAdapter

public class MyAdapter extends RecyclerView.Adapter<MyAdapter.ViewHolder> {
    private List<String> list;

    public MyAdapter(List<String> list) {
        this.list = list;
    }

    @Override
    public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
        View view = layoutInflater.inflate(R.layout.listitem, parent, false);
        ViewHolder holder = new ViewHolder(view);
        return holder;
    }

    @Override
    public void onBindViewHolder(ViewHolder holder, int position) {
        holder.tv.setText(list.get(position));
    }

    @Override
    public int getItemCount(a) {
        return list.size();
    }

    public class ViewHolder extends RecyclerView.ViewHolder {

        public TextView tv;

        public ViewHolder(View itemView) {
            super(itemView); tv = (TextView) itemView.findViewById(R.id.tv); }}}Copy the code
2.3. MyAdapter HeaderViewRecyclerAdapter agent

public class HeaderViewRecyclerAdapter extends RecyclerView.Adapter {
    private RecyclerView.Adapter mAdapter;
    private ArrayList<View> mHeaderViewInfos;
    private ArrayList<View> mFooterViewInfos;


    public HeaderViewRecyclerAdapter(ArrayList<View> headerViewInfos, ArrayList<View> footerViewInfos, RecyclerView.Adapter adapter) {
        mAdapter = adapter;
        if (headerViewInfos == null) {
            mHeaderViewInfos = new ArrayList<>();
        } else {
            mHeaderViewInfos = headerViewInfos;
        }
        if (footerViewInfos == null) {
            mFooterViewInfos = new ArrayList<>();
        } else{ mFooterViewInfos = footerViewInfos; }}@Override
    public int getItemCount(a) {
        if(mAdapter ! =null) {
            Log.i("http:Headers"."" + getHeadersCount() + getFootersCount() + mAdapter.getItemCount());
            return getHeadersCount() + getFootersCount() + mAdapter.getItemCount();
        } else {
            Log.i("http:"."" + getFootersCount() + getHeadersCount());
            returngetFootersCount() + getHeadersCount(); }}@Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        // Also divide areas
        int numHeaders = getHeadersCount();
        if (position < numHeaders) { / / to the head
            return;
        }
        //adapter body
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if(mAdapter ! =null) {
            adapterCount = mAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                mAdapter.onBindViewHolder(holder, adjPosition);
                return; }}//footer
    }


    @Override
    public int getItemViewType(int position) {
        // Determine what type the current entry is - decide what view to render for what data
        int numHeaders = getHeadersCount();
        if (position < numHeaders) {/ / to the head
            return RecyclerView.INVALID_TYPE;
        }
        // The normal entry section
        //Adapter
        final int adjPosition = position - numHeaders;
        int adapterCount = 0;
        if(mAdapter ! =null) {
            adapterCount = mAdapter.getItemCount();
            if (adjPosition < adapterCount) {
                returnmAdapter.getItemViewType(adjPosition); }}/ / footer section
        return RecyclerView.INVALID_TYPE - 1;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        //header
        if (viewType == RecyclerView.INVALID_TYPE) {
            return new HeaderViewHolder(mHeaderViewInfos.get(0));

        } else if (viewType == RecyclerView.INVALID_TYPE - 1) {
            return new HeaderViewHolder(mFooterViewInfos.get(0));
        }
        return mAdapter.onCreateViewHolder(parent, viewType);
    }


    private int getHeadersCount(a) {
        return mHeaderViewInfos.size();
    }

    private int getFootersCount(a) {
        return mFooterViewInfos.size();
    }

    private static class HeaderViewHolder extends RecyclerView.ViewHolder {

        public HeaderViewHolder(View itemView) {
            super(itemView); }}}Copy the code
2.4. MainActivity

public class MainActivity extends AppCompatActivity {
    WrapRecyclerView recyclerView;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        recyclerView = (WrapRecyclerView) findViewById(R.id.recyclerView);

        TextView headerView = new TextView(this);
        ViewGroup.LayoutParams params = new ViewGroup.LayoutParams(RelativeLayout.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        headerView.setLayoutParams(params);
        headerView.setText("I am HeaderView");
        recyclerView.addHeaderView(headerView);

        TextView footerView = new TextView(this);
        params = new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
        footerView.setLayoutParams(params);
        footerView.setText("I am FooterView");
        recyclerView.addFooterView(footerView);

        List<String> list = new ArrayList<>();
        for (int i = 0; i < 10; i++) {
            list.add("item " + i);
        }

        MyAdapter adapter = new MyAdapter(list);
        recyclerView.setLayoutManager(new LinearLayoutManager(this)); recyclerView.setAdapter(adapter); }}Copy the code
2.5. Layout files
activity_main.xml

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="match_parent">

    <com.haocai.wraprecyclerview.WrapRecyclerView
        android:id="@+id/recyclerView"
        android:layout_width="fill_parent"
        android:layout_height="fill_parent" />

</RelativeLayout>
Copy the code
listitem.xml

<? xml version="1.0" encoding="utf-8"? > <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="40dp"
    android:orientation="vertical" >

    <TextView
        android:id="@+id/tv"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text=""
        android:textAppearance="? android:attr/textAppearanceMedium" />

</LinearLayout>
Copy the code

3. Achieve results