天天看點

RecyclerView添加HeaderView和FooterView

前面已經說過RecyclerView的使用,RecyclerView通過其高度的可定制性深受大家的青睐,也有非常多的使用者開始對它進行封裝或者改造,進而滿足越來越多的需求,相信用過的一定會愛不釋手。

在實際開發中經常會遇到給清單添加标題這樣的需求,可是要給RecyclerView加個headerView或者footerView卻發現沒有這樣的方法,怎麼辦呢?網上搜到的大多數方案都是通過控制Adapter的ItemType來設定的,不管是添加headerView還是footerView,它們都是Item的一種,隻不過顯示在特定的位置,那麼我們完全可以通過為其設定ItemType來完成。是以核心就是根據不同的ItemType去加載不同的布局。

有了思路,我們就簡單實作下吧

擴充卡代碼如下:

public class HeaderAndFooterAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int ITEM_TYPE_HEADER = 1;
    private static final int ITEM_TYPE_FOOTER = 2;
    private Context mContext;
    private List<String> mDatas = new ArrayList<>();


    public HeaderAndFooterAdapter(Context mContext, List<String> mDatas) {
        this.mDatas = mDatas;
        this.mContext = mContext;
    }


    /*根據getItemViewType()方法傳回的不同類型建立不同的ViewHolder*/
    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (viewType == ITEM_TYPE_HEADER) {

            View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
            HeaderHolder viewHolder = new HeaderHolder(view);
            return viewHolder;

        } else if (viewType == ITEM_TYPE_FOOTER) {
            View view = LayoutInflater.from(mContext).inflate(android.R.layout.simple_list_item_1, parent, false);
            FootHolder viewHolder = new FootHolder(view);
            return viewHolder;
        } else {
            View view = LayoutInflater.from(mContext).inflate(R.layout.item_recyclerview_main, parent, false);
            ItemHolder viewHolder = new ItemHolder(view);
            return viewHolder;
        }
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (holder instanceof HeaderHolder) {
            HeaderHolder headerHolder = (HeaderHolder) holder;
            headerHolder.textViewHeader.setText(mDatas.get(position));
        } else if (holder instanceof FootHolder) {
            FootHolder footHolder = (FootHolder) holder;
            footHolder.textViewFoot.setText(mDatas.get(position));
        } else {
            ItemHolder itemHolder = (ItemHolder) holder;
            itemHolder.textViewItem.setText(mDatas.get(position));
        }

    }

    @Override
    public int getItemCount() {
        return mDatas == null ? 0 : mDatas.size();
    }

    /*根據位置來傳回不同的item類型*/
    @Override
    public int getItemViewType(int position) {
        if (position == 0) {
            return ITEM_TYPE_HEADER;
        } else if (position + 1 == getItemCount()) {
            return ITEM_TYPE_FOOTER;
        } else
            return 0;
    }

    /*頭部Item*/
    class HeaderHolder extends RecyclerView.ViewHolder {
        public TextView textViewHeader;

        public HeaderHolder(View itemView) {
            super(itemView);
            textViewHeader = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }
    /*底部Item*/
    class FootHolder extends RecyclerView.ViewHolder {
        public TextView textViewFoot;

        public FootHolder(View itemView) {
            super(itemView);
            textViewFoot = (TextView) itemView.findViewById(android.R.id.text1);
        }
    }

    class ItemHolder extends RecyclerView.ViewHolder {
        public TextView textViewItem;

        public ItemHolder(View itemView) {
            super(itemView);
            textViewItem = (TextView) itemView.findViewById(R.id.tv_main_item);
        }
    }
}      
  • getItemViewType

    由于我們增加了headerView和footerView首先需要複寫的就是getItemCount和getItemViewType。getItemCount很好了解,對于getItemType,傳回的就是item所代表的type值。

  • onCreateViewHolder

    可以看到,我們分别判斷viewType,如果是headerView或者是footView,我們則為其單獨建立ViewHolder。

  • onBindViewHolder

    onBindViewHolder比較簡單,隻要根據不同的ViewHolder來進行綁定資料即可。

Activity類如下:

public class MainActivity extends AppCompatActivity {
    private RecyclerView mRecyclerView;
    private List<String> mDatas = new ArrayList<>();
    private HeaderAndFooterAdapter mAdapter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        mRecyclerView = (RecyclerView) findViewById(R.id.ryv_main_content);
        LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
        mRecyclerView.setLayoutManager(linearLayoutManager);
        mAdapter=new HeaderAndFooterAdapter(this,mDatas);
        mRecyclerView.setAdapter(mAdapter);
        initData();
        mAdapter.notifyDataSetChanged();
    }

    private void initData() {
        mDatas.add("清單頭");
        for (int i=0;i<20;i++){
            mDatas.add("item"+i);
        }
        mDatas.add("清單尾");
    }
}      

效果如下:

好了,現在問題來了,假設我們現在已經完成了RecyclerView的編寫,忽然有個需求,需要在清單上加個HeaderView,此時我們該怎麼辦呢?打開我們的Adapter,然後按照我們上述的原理,添加特殊的ViewType,然後修改代碼完成。這是比較正常的做法了,但是有個問題是,如果需要添加多個viewType,那麼可能我們的Adapter需要修改的幅度就比較大了,比如getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等,幾乎所有的方法都要進行改變。如果一個項目中多個RecyclerView都需要在其清單中添加headerView,想想都頭大了,是以直接改Adapter的代碼是非常不劃算的,最好能夠設計一個類,可以無縫的為原有的Adapter添加headerView和footerView。我也是看到了鴻洋大神的這篇文章才有了收獲,思路是通過類似裝飾者模式,去設計一個類,增強原有Adapter的功能,使其支援addHeaderView和addFooterView。這樣我們就可以不去改動我們之前已經完成的代碼,靈活的去擴充功能了。幸好大學的課本沒丢,又翻出出來,重新看看裝飾者模式,哈哈~~

初步實作

裝飾類

public class HeaderAndFooterWrapper<T> extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
    private static final int BASE_ITEM_TYPE_HEADER = 0x10;
    private static final int BASE_ITEM_TYPE_FOOTER = 0x20;

    private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
    private SparseArrayCompat<View> mFootViews = new SparseArrayCompat<>();

    private RecyclerView.Adapter mInnerAdapter;

    public HeaderAndFooterWrapper(RecyclerView.Adapter adapter) {
        mInnerAdapter = adapter;
    }

    @Override
    public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
        if (mHeaderViews.get(viewType) != null) {
            ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mHeaderViews.get(viewType));
            return holder;

        } else if (mFootViews.get(viewType) != null) {
            ViewHolder holder = ViewHolder.createViewHolder(parent.getContext(), mFootViews.get(viewType));
            return holder;
        }
        return mInnerAdapter.onCreateViewHolder(parent, viewType);
    }

    @Override
    public int getItemViewType(int position) {
        if (isHeaderViewPos(position)) {
            return mHeaderViews.keyAt(position);
        } else if (isFooterViewPos(position)) {
            return mFootViews.keyAt(position - getHeadersCount() - getRealItemCount());
        }
        return mInnerAdapter.getItemViewType(position - getHeadersCount());
    }

    private int getRealItemCount() {
        return mInnerAdapter.getItemCount();
    }

    @Override
    public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
        if (isHeaderViewPos(position)) {
            return;
        }
        if (isFooterViewPos(position)) {
            return;
        }
        mInnerAdapter.onBindViewHolder(holder, position - getHeadersCount());
    }

    @Override
    public int getItemCount() {
        return getHeadersCount() + getFootersCount() + getRealItemCount();
    }
 
    private boolean isHeaderViewPos(int position) {
        return position < getHeadersCount();
    }

    private boolean isFooterViewPos(int position) {
        return position >= getHeadersCount() + getRealItemCount();
    }

    public void addHeaderView(View view) {
        mHeaderViews.put(mHeaderViews.size() + BASE_ITEM_TYPE_HEADER, view);
    }

    public void addFootView(View view) {
        mFootViews.put(mFootViews.size() + BASE_ITEM_TYPE_FOOTER, view);
    }

    public int getHeadersCount() {
        return mHeaderViews.size();
    }

    public int getFootersCount() {
        return mFootViews.size();
    }

}      

注意這幾個方法:

  • 可以看到我們的傳回值是mHeaderViews.keyAt(position),這個值其實就是我們 addHeaderView時的key,footerView是一樣的處理方式,也就是說每一個headerView都會建立不同的itemType。
  • 可以看到,我們分别判斷viewType,如果是HeaderView或者是FooterView,我們則為其單獨建立ViewHolder,這裡的 ViewHolder我就直接拿鴻洋大神的用了,大家也可以自己寫,隻需要将對應的 headerView作為itemView傳入ViewHolder的構造即可。而這裡也就展現出SparseArrayCompat的作用了,對于headerView假設我們有多個,那麼onCreateViewHolder傳回的ViewHolder中的itemView應該對應不同的headerView,如果是List,那麼不同的headerView應該對應着:list.get(0),list.get(1)等。但是onCreateViewHolder方法并沒有position參數,隻有itemType參數,如果itemType還是固定的一個值,那麼你是沒有辦法根據參數得到不同的headerView的。

是以,我利用SparseArrayCompat,将其itemType作為key,value為我們的headerView,在 onCreateViewHolder中,直接通過itemType,即可獲得我們的headerView,然後構造ViewHolder對象。

效果:

效果差不多,我們再來看看GridLayoutMananger的效果:

雖然效果不是我們想要的,但邏輯是正确的,好了,現在改下代碼吧

針對GridLayoutManager

在HeaderAndFooterWrapper類中複寫onAttachedToRecyclerView方法

@Override
    public void onAttachedToRecyclerView(RecyclerView recyclerView) {
        mInnerAdapter.onAttachedToRecyclerView(recyclerView);

        RecyclerView.LayoutManager layoutManager = recyclerView.getLayoutManager();
        if (layoutManager instanceof GridLayoutManager) {
            final GridLayoutManager gridLayoutManager = (GridLayoutManager) layoutManager;
            final GridLayoutManager.SpanSizeLookup spanSizeLookup = gridLayoutManager.getSpanSizeLookup();

            gridLayoutManager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
                @Override
                public int getSpanSize(int position) {
                    int viewType = getItemViewType(position);
                    if (mHeaderViews.get(viewType) != null) {
                        return gridLayoutManager.getSpanCount();
                    } else if (mFootViews.get(viewType) != null) {
                        return gridLayoutManager.getSpanCount();
                    }
                    if (spanSizeLookup != null)
                        return spanSizeLookup.getSpanSize(position);
                    return 1;
                }
            });
            gridLayoutManager.setSpanCount(gridLayoutManager.getSpanCount());
        }
    }      

現在看一下運作效果:

對于StaggeredGridLayoutManager

當然了,我們怎麼能忘了最重要的StaggeredGridLayoutManager呢 ?StaggeredGridLayoutManager并沒有setSpanSizeLookup這樣的方法,但是處理依然不複雜,重寫onViewAttachedToWindow方法,如下:

@Override
    public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
        mInnerAdapter.onViewAttachedToWindow(holder);
        int position = holder.getLayoutPosition();
        if (isHeaderViewPos(position) || isFooterViewPos(position)) {
            ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams();

            if (lp != null
                    && lp instanceof StaggeredGridLayoutManager.LayoutParams) {

                StaggeredGridLayoutManager.LayoutParams p =
                        (StaggeredGridLayoutManager.LayoutParams) lp;

                p.setFullSpan(true);
            }
        }
    }
      

哦了,就是這樣

源代碼

參考:

http://blog.csdn.net/lmj623565791/article/details/51854533

繼續閱讀