轉載請注明出處 http://blog.csdn.net/u011453163/article/details/58586687
RecyclerView 已經不是一個陌生的組件了,但是相對于現在的項目在還是用比較老舊的ListView,雖然功能都能實現,但是畢竟得跟上時代變化嘛。
關于RecyclerView添加 頭部和尾部,網上已經有各種大牛的版本,實現起來也不是很難。但是看一遍不如擼一遍。這里對學習RecyclerView過程中做一些記錄,部分實現也有借鑒大牛們的思路,見笑。
先上個簡單的效果圖。
RecyclerView不像ListView 一樣 提供了添加頭部和尾部的方法,但是RecyclerView和ListView一樣是支持多類型Item布局的,那么反過來 頭部和尾部 也就只是一些特定類型的布局而已。所以我們可以通過 public int getItemViewType(int position) 算出頭部和尾部的Type 來加載頭部和尾部。
這里通過兩種方式來實現這個效果,思路是一樣的。
自定義一個 ExpandAdapter 繼承 RecyclerView.Adapter
public class ExpandAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> { PRivate RecyclerView.Adapter<RecyclerView.ViewHolder> adapter; public ExpandAdapter(RecyclerView.Adapter<RecyclerView.ViewHolder> adapter) { this.adapter = adapter; } @Override public int getItemViewType(int position) { return adapter.getItemViewType(position); } @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { return adapter.onCreateViewHolder(parent, viewType); } @Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { adapter.onBindViewHolder(holder, position); } @Override public int getItemCount() { return adapter.getItemCount(); } }這里只是簡單的做一個轉換工作,以方便后期使用不需要去改動太多的東西。 1 通過 List 來實現。 接下來開始分析實現思路,首先要注意的是 public int getItemViewType(int position)默認是返回 0 的, 所以在定義頭部和尾部的type是要錯開 0 ,網上大部分大牛的思路都是定義一個比較大的起始值。
1 一個較大的起始值
private static final int TPEY_COUNT_START_TAG =100000;2 兩個存儲頭部和尾部 view的集合,和添加頭部和尾部對應的方法
List<View> headViews = new ArrayList<>(); List<View> footViews = new ArrayList<>(); public void addHeadView(View v) { if (headViews != null) { v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); headViews.add(v); } } public void addFootView(View v) { if (footViews != null) { v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); footViews.add(v); } }3 在 public int getItemViewType(int position) 計算返回的type
public int getItemViewType(int position) { if (headViews.size() > 0) { if (position < headViews.size()) { return position+ TPEY_COUNT_START_TAG; } } if (footViews.size() > 0) { if (position >= getItemCount() - footViews.size()) { return position+ TPEY_COUNT_START_TAG; } } return adapter.getItemViewType(position); }因為這里只有一個position參數可以使用,所以在position的基礎上加個一個較大的值,這里是加法之后我們要做減法來算出下標 取出對應的 頭部view和尾部view。
4 從List中取出渲染對應的頭部和尾部。
定義頭部和尾部對應的ViewHolder 便于理解
class HeaderHolder extends RecyclerView.ViewHolder { public HeaderHolder(View itemView) { super(itemView); } } class FootHolder extends RecyclerView.ViewHolder { public FootHolder(View itemView) { super(itemView); } }@Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if (headViews.size() > 0) { if (viewType- TPEY_COUNT_START_TAG >= 0 && viewType- TPEY_COUNT_START_TAG < headViews.size()) { return new HeaderHolder(headViews.get(viewType- TPEY_COUNT_START_TAG)); } } if (footViews.size() > 0) { Log.d("ExpandAdapter", "viewType:" + viewType); if (viewType- TPEY_COUNT_START_TAG >= getItemCount() - footViews.size() && viewType- TPEY_COUNT_START_TAG < getItemCount()) { return new FootHolder(footViews.get(viewType- TPEY_COUNT_START_TAG -adapter.getItemCount()-headViews.size())); } } return adapter.onCreateViewHolder(parent, viewType); }這里主要就是做一些判斷 算出下標 來取出 headViews, footViews中對應的view。
5 綁定數據
@Override public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) { if(holder instanceof FootHolder ||holder instanceof HeaderHolder){ return; } adapter.onBindViewHolder(holder, position); } @Override public int getItemCount() { return adapter.getItemCount() + headViews.size() + footViews.size(); }動態添加的頭部和尾部不需要在適配器里渲染數據,所以直接返回不管。Item總數就是頭部和尾部和普通itme的總和,這個沒必要說的。
6.由于RecyclerView 的布局形式不同。GridLayoutManager,StaggeredGridLayoutManager,LinearLayoutManager。為了適配這三者,還需要重寫兩個方法,這個是借鑒網上大牛的。
@Override public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) { adapter.onViewAttachedToWindow(holder); int position = holder.getLayoutPosition(); if ((position >= 0 && position < headViews.size())||(position>=getItemCount()-footViews.size()&&position<getItemCount())) { ViewGroup.LayoutParams lp = holder.itemView.getLayoutParams(); if (lp != null && lp instanceof StaggeredGridLayoutManager.LayoutParams) { StaggeredGridLayoutManager.LayoutParams p = (StaggeredGridLayoutManager.LayoutParams) lp; p.setFullSpan(true); } } } @Override public void onAttachedToRecyclerView(final RecyclerView recyclerView) { super.onAttachedToRecyclerView(recyclerView); if (recyclerView.getLayoutManager() instanceof GridLayoutManager) { ((GridLayoutManager) recyclerView.getLayoutManager()).setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() { @Override public int getSpanSize(int position) { if (position >= 0 && position < headViews.size()) { return ((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount(); }else if(position>=getItemCount()-footViews.size()&&position<getItemCount()){ return ((GridLayoutManager) recyclerView.getLayoutManager()).getSpanCount(); } else { return 1; } } }); } }StaggeredGridLayoutManager
GridLayoutManager
以上是使用List實現的方式,第二種的思路也是一樣的,通過兩個HashMap來存儲頭部和尾部 ,就不詳細介紹了,主要列出一些區別。
private static final int TPEY_HEADER_STATRT_INDEX =-100000; private static final int TPEY_FOOT_START_INDEX =100000; HashMap<Integer,View> headViewss=new HashMap<>(); HashMap<Integer,View> footViewss=new HashMap<>(); /**添加頭部*/ public void addHeadView(View v) { if(headViewss!=null){ v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); headViewss.put(TPEY_HEADER_STATRT_INDEX+headViewss.size(),v); } } /**添加頭部*/ public void addFootView(View v) { if(footViewss!=null){ v.setLayoutParams(new RecyclerView.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)); footViewss.put(TPEY_FOOT_START_INDEX+footViewss.size(),v); } }/**重寫適配器的兩個方法也有區別 這里也可以同set 返回key值*/ @Override public int getItemViewType(int position) { if(headViewss!=null&&position<headViewss.size()){ return position+TPEY_HEADER_STATRT_INDEX; } if(footViewss!=null&&position>=adapter.getItemCount()+headViewss.size()){ return TPEY_FOOT_START_INDEX+(position-adapter.getItemCount()-headViewss.size()); } return adapter.getItemViewType(position); } /**通過Key直接獲取*/ @Override public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { if(headViewss.containsKey(viewType)){ return new HeaderHolder(headViewss.get(viewType)); } if(footViewss.containsKey(viewType)){ return new FootHolder(footViewss.get(viewType)); } return adapter.onCreateViewHolder(parent, viewType); }/**此方法基本一樣*/ @Override public int getItemCount() { return adapter.getItemCount() + headViewss.size() + footViewss.size(); }這里因為是使用了ExpandAdapter 來做一個包裝,所以相應的使用一個自定義RecyclerView來實現。以便我們不用做太多的改動。
public class ExpandRecyclerView extends RecyclerView{ ExpandAdapter expandAdapter; public ExpandRecyclerView(Context context) { super(context); } public ExpandRecyclerView(Context context, @Nullable AttributeSet attrs) { super(context, attrs); } public ExpandRecyclerView(Context context, @Nullable AttributeSet attrs, int defStyle) { super(context, attrs, defStyle); } @Override public void setAdapter(Adapter adapter) { expandAdapter =new ExpandAdapter(adapter); super.setAdapter(expandAdapter); } public void addHeadView(View v){ if(expandAdapter !=null){ expandAdapter.addHeadView(v); expandAdapter.notifyDataSetChanged(); } } public void addFootView(View v){ if(expandAdapter !=null){ expandAdapter.addFootView(v); expandAdapter.notifyDataSetChanged(); } }}使用自定義RecyclerView之后 我們不需要去改動適配器 還是該怎么使用就怎么使用。
到此,RecyclerView的添加頭部和尾部就結束了。有什么不對的地方望指正。
新聞熱點
疑難解答