Android高级UI开发 RecyclerView高级进阶(六)偷梁换柱之 添加头部和尾部

众所周知Listview控件可以通过addHeaderView和addFooterView来添加头部和尾部,但是我们的 RecyclerView控件却没有这样的 API,那我们就得自己扩展这样的API到RecyclerView控件里。既然Listview已经实现了这样的API,我们不防看看ANDROID listview源码,我们可以模仿它。

demo工程源码下载

效果图:

原理图:

 

一、分析listview源码

 

1. Listview源码片段1: Listview.addHeaderView(...)

添加头部 


  
  1. /**
  2. * Add a fixed view to appear at the top of the list. If this method is
  3. * called more than once, the views will appear in the order they were
  4. * added. Views added using this call can take focus if they want.
  5. * <p>
  6. * Note: When first introduced, this method could only be called before
  7. * setting the adapter with {@link #setAdapter(ListAdapter)}. Starting with
  8. * {@link android.os.Build.VERSION_CODES#KITKAT}, this method may be
  9. * called at any time. If the ListView's adapter does not extend
  10. * {@link HeaderViewListAdapter}, it will be wrapped with a supporting
  11. * instance of {@link WrapperListAdapter}.
  12. *
  13. * @param v The view to add.
  14. * @param data Data to associate with this view
  15. * @param isSelectable whether the item is selectable
  16. */
  17. public void addHeaderView(View v, Object data, boolean isSelectable) {
  18. if (v.getParent() != null && v.getParent() != this) {
  19. if (Log.isLoggable(TAG, Log.WARN)) {
  20. Log.w(TAG, "The specified child already has a parent. "
  21. + "You must call removeView() on the child's parent first.");
  22. }
  23. }
  24. final FixedViewInfo info = new FixedViewInfo();
  25. info.view = v;
  26. info.data = data;
  27. info.isSelectable = isSelectable;
  28. mHeaderViewInfos.add(info);
  29. mAreAllItemsSelectable &= isSelectable;
  30. // Wrap the adapter if it wasn't already wrapped.
  31. if (mAdapter != null) {
  32. if (!(mAdapter instanceof HeaderViewListAdapter)) {
  33. wrapHeaderListAdapterInternal();
  34. }
  35. // In the case of re-adding a header view, or adding one later on,
  36. // we need to notify the observer.
  37. if (mDataSetObserver != null) {
  38. mDataSetObserver.onChanged();
  39. }
  40. }
  41. }

  
  1. protected void wrapHeaderListAdapterInternal() {
  2. mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
  3. }

  
  1. protected HeaderViewListAdapter wrapHeaderListAdapterInternal(
  2. ArrayList<ListView.FixedViewInfo> headerViewInfos,
  3. ArrayList<ListView.FixedViewInfo> footerViewInfos,
  4. ListAdapter adapter) {
  5. return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
  6. }

 

 

我们来分析以上 listview中添加头部的API 源码,主要看红色部分,

 

 

(1)mHeaderViewInfos.add(info)

 意图是在数组mHeaderViewInfos里添加头部信息info中的vie w就是我们添加的头部布局,info.data就是 为header准备数据,info.isSelectable定义头部是否可以选择,这个一般用于TV上的光标选择。既然mHeaderViewInfos是一个数组,说明Listview可以添加多个头部。总结一下,就是mHeaderViewInfos可以添加多个 头部view。

(2)wrapHeaderListAdapterInternal函数 

 从上述代码中可以看出 该函数最终调用了

 

   return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter)
 

也就是说返回了一个全新的adapter = HeaderViewListAdapter, 观察3个构造函数参分别是头部视图、尾部视图,普通adapter.看 来这个全新的adapter不一般,它可以处理带 有头部和尾部视图以及正常条目的ListView 。总结一下,就是当listview有头部和尾部的时候,会专门为这样的listview重新封装一个HeaderViewListAdapter,这个HeaderViewListAdapter会为listview显示不同类型的布局,如头部布局、正常条目的布局、尾部布局。

2.  Listview源码片段2 setAdapter函数


   
  1. /**
  2. * Sets the data behind this ListView.
  3. *
  4. * The adapter passed to this method may be wrapped by a {@link WrapperListAdapter},
  5. * depending on the ListView features currently in use. For instance, adding
  6. * headers and/or footers will cause the adapter to be wrapped.
  7. *
  8. * @param adapter The ListAdapter which is responsible for maintaining the
  9. * data backing this list and for producing a view to represent an
  10. * item in that data set.
  11. *
  12. * @see #getAdapter()
  13. */
  14. @Override
  15. public void setAdapter(ListAdapter adapter) {
  16. if (mAdapter != null && mDataSetObserver != null) {
  17. mAdapter.unregisterDataSetObserver(mDataSetObserver);
  18. }
  19. resetList();
  20. mRecycler.clear();
  21. if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
  22. mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
  23. } else {
  24. mAdapter = adapter;
  25. }
  26. mOldSelectedPosition = INVALID_POSITION;
  27. mOldSelectedRowId = INVALID_ROW_ID;
  28. // AbsListView#setAdapter will update choice mode states.
  29. super.setAdapter(adapter);
  30. if (mAdapter != null) {
  31. mAreAllItemsSelectable = mAdapter.areAllItemsEnabled();
  32. mOldItemCount = mItemCount;
  33. mItemCount = mAdapter.getCount();
  34. checkFocus();
  35. mDataSetObserver = new AdapterDataSetObserver();
  36. mAdapter.registerDataSetObserver(mDataSetObserver);
  37. mRecycler.setViewTypeCount(mAdapter.getViewTypeCount());
  38. int position;
  39. if (mStackFromBottom) {
  40. position = lookForSelectablePosition(mItemCount - 1, false);
  41. } else {
  42. position = lookForSelectablePosition(0, true);
  43. }
  44. setSelectedPositionInt(position);
  45. setNextSelectedPositionInt(position);
  46. if (mItemCount == 0) {
  47. // Nothing selected
  48. checkSelectionChanged();
  49. }
  50. } else {
  51. mAreAllItemsSelectable = true;
  52. checkFocus();
  53. // Nothing selected
  54. checkSelectionChanged();
  55. }
  56. requestLayout();
  57. }

以上是Listview的setAdapter函数

 

   if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0)
 

表面当有头部或尾部的时候

 

 mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, adapter);
 

mAdapter实质被赋值为一个封装过的adapter,与源码片段1一样,封装过的adapter就是HeaderViewListAdapter。

也就是说,我们通常表面上调用的是setAdapter(adapter),实质当我们调用了addHeaderView函数之后,系统会帮我们把正常条目的adapter做了一次全新的封装,封装后的HeaderViewListAdapter有能力去显示头部、尾部,正常条目的。

二、模仿(一)中的Listview的源码来扩展RecyclerView(可添加头部与尾部)

1.我们要扩展RecyclerView,那自然先创建一个子类继承RecyclerView


  
  1. package com.anyikang.volunteer.sos.recyclerview;
  2. import android.content.Context;
  3. import android.support.v7.widget.RecyclerView;
  4. import android.util.AttributeSet;
  5. import android.view.View;
  6. import java.util.ArrayList;
  7. public class MyRecyclerView extends RecyclerView{
  8. private ArrayList<View> mHeaderViewInfos = new ArrayList<View>();
  9. private ArrayList<View> mFooterViewInfos = new ArrayList<View>();
  10. private Adapter mAdapter;
  11. public MyRecyclerView(Context context, AttributeSet attrs) {
  12. super(context, attrs);
  13. }
  14. public void addHeaderView(View v) {
  15. mHeaderViewInfos.add(v);
  16. // Wrap the adapter if it wasn't already wrapped.
  17. if (mAdapter != null) {
  18. if (!(mAdapter instanceof HeaderViewAdapterForRecycler )) {
  19. mAdapter = new HeaderViewAdapterForRecycler (mHeaderViewInfos, mFooterViewInfos, mAdapter);
  20. }
  21. }
  22. }
  23. public void addFooterView(View v) {
  24. mFooterViewInfos.add(v);
  25. // Wrap the adapter if it wasn't already wrapped.
  26. if (mAdapter != null) {
  27. if (!(mAdapter instanceof HeaderViewAdapterForRecycler )) {
  28. mAdapter = new HeaderViewAdapterForRecycler (mHeaderViewInfos, mFooterViewInfos, mAdapter);
  29. }
  30. }
  31. }
  32. @Override
  33. public void setAdapter(Adapter adapter) {
  34. if (mHeaderViewInfos.size() > 0|| mFooterViewInfos.size() > 0) {
  35. mAdapter = new HeaderViewAdapterForRecycler (mHeaderViewInfos, mFooterViewInfos, adapter);
  36. } else {
  37. mAdapter = adapter;
  38. }
  39. super.setAdapter(mAdapter);
  40. }
  41. }

 

是否有种似曾相识的感觉。继承于RecyclerView主要扩展了3个函数:addHeaderView增加头部、addFooterView增加尾部、重写了setAdapter. 这3个函数都干了同一件事:那就是如果我们添加了头部(尾部),我们的adapter将偷梁换柱成HeaderViewAdapterForRecycler,可显示头、中、尾条目的适配器现在是时候来分析这个顶梁柱HeaderViewAdapterForRecycler的时候了。

 

三、老套路,先分析Listview中的HeaderViewListAdapter的源码


  
  1. /*
  2. * Copyright (C) 2006 The Android Open Source Project
  3. *
  4. * Licensed under the Apache License, Version 2.0 (the "License");
  5. * you may not use this file except in compliance with the License.
  6. * You may obtain a copy of the License at
  7. *
  8. * http://www.apache.org/licenses/LICENSE-2.0
  9. *
  10. * Unless required by applicable law or agreed to in writing, software
  11. * distributed under the License is distributed on an "AS IS" BASIS,
  12. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  13. * See the License for the specific language governing permissions and
  14. * limitations under the License.
  15. */
  16. package android.widget;
  17. import android.database.DataSetObserver;
  18. import android.view.View;
  19. import android.view.ViewGroup;
  20. import java.util.ArrayList;
  21. /**
  22. * ListAdapter used when a ListView has header views. This ListAdapter
  23. * wraps another one and also keeps track of the header views and their
  24. * associated data objects.
  25. *<p>This is intended as a base class; you will probably not need to
  26. * use this class directly in your own code.
  27. */
  28. public class HeaderViewListAdapter implements WrapperListAdapter, Filterable {
  29. private final ListAdapter mAdapter;
  30. // These two ArrayList are assumed to NOT be null.
  31. // They are indeed created when declared in ListView and then shared.
  32. ArrayList<ListView.FixedViewInfo> mHeaderViewInfos;
  33. ArrayList<ListView.FixedViewInfo> mFooterViewInfos;
  34. // Used as a placeholder in case the provided info views are indeed null.
  35. // Currently only used by some CTS tests, which may be removed.
  36. static final ArrayList<ListView.FixedViewInfo> EMPTY_INFO_LIST =
  37. new ArrayList<ListView.FixedViewInfo>();
  38. boolean mAreAllFixedViewsSelectable;
  39. private final boolean mIsFilterable;
  40. public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
  41. ArrayList<ListView.FixedViewInfo> footerViewInfos,
  42. ListAdapter adapter) {
  43. mAdapter = adapter;
  44. mIsFilterable = adapter instanceof Filterable;
  45. if (headerViewInfos == null) {
  46. mHeaderViewInfos = EMPTY_INFO_LIST;
  47. } else {
  48. mHeaderViewInfos = headerViewInfos;
  49. }
  50. if (footerViewInfos == null) {
  51. mFooterViewInfos = EMPTY_INFO_LIST;
  52. } else {
  53. mFooterViewInfos = footerViewInfos;
  54. }
  55. mAreAllFixedViewsSelectable =
  56. areAllListInfosSelectable(mHeaderViewInfos)
  57. && areAllListInfosSelectable(mFooterViewInfos);
  58. }
  59. public int getHeadersCount() {
  60. return mHeaderViewInfos.size();
  61. }
  62. public int getFootersCount() {
  63. return mFooterViewInfos.size();
  64. }
  65. public boolean isEmpty() {
  66. return mAdapter == null || mAdapter.isEmpty();
  67. }
  68. private boolean areAllListInfosSelectable(ArrayList<ListView.FixedViewInfo> infos) {
  69. if (infos != null) {
  70. for (ListView.FixedViewInfo info : infos) {
  71. if (!info.isSelectable) {
  72. return false;
  73. }
  74. }
  75. }
  76. return true;
  77. }
  78. public boolean removeHeader(View v) {
  79. for (int i = 0; i < mHeaderViewInfos.size(); i++) {
  80. ListView.FixedViewInfo info = mHeaderViewInfos.get(i);
  81. if (info.view == v) {
  82. mHeaderViewInfos.remove(i);
  83. mAreAllFixedViewsSelectable =
  84. areAllListInfosSelectable(mHeaderViewInfos)
  85. && areAllListInfosSelectable(mFooterViewInfos);
  86. return true;
  87. }
  88. }
  89. return false;
  90. }
  91. public boolean removeFooter(View v) {
  92. for (int i = 0; i < mFooterViewInfos.size(); i++) {
  93. ListView.FixedViewInfo info = mFooterViewInfos.get(i);
  94. if (info.view == v) {
  95. mFooterViewInfos.remove(i);
  96. mAreAllFixedViewsSelectable =
  97. areAllListInfosSelectable(mHeaderViewInfos)
  98. && areAllListInfosSelectable(mFooterViewInfos);
  99. return true;
  100. }
  101. }
  102. return false;
  103. }
  104. public int getCount() {
  105. if (mAdapter != null) {
  106. return getFootersCount() + getHeadersCount() + mAdapter.getCount();
  107. } else {
  108. return getFootersCount() + getHeadersCount();
  109. }
  110. }
  111. public boolean areAllItemsEnabled() {
  112. if (mAdapter != null) {
  113. return mAreAllFixedViewsSelectable && mAdapter.areAllItemsEnabled();
  114. } else {
  115. return true;
  116. }
  117. }
  118. public boolean isEnabled(int position) {
  119. // Header (negative positions will throw an IndexOutOfBoundsException)
  120. int numHeaders = getHeadersCount();
  121. if (position < numHeaders) {
  122. return mHeaderViewInfos.get(position).isSelectable;
  123. }
  124. // Adapter
  125. final int adjPosition = position - numHeaders;
  126. int adapterCount = 0;
  127. if (mAdapter != null) {
  128. adapterCount = mAdapter.getCount();
  129. if (adjPosition < adapterCount) {
  130. return mAdapter.isEnabled(adjPosition);
  131. }
  132. }
  133. // Footer (off-limits positions will throw an IndexOutOfBoundsException)
  134. return mFooterViewInfos.get(adjPosition - adapterCount).isSelectable;
  135. }
  136. public Object getItem(int position) {
  137. // Header (negative positions will throw an IndexOutOfBoundsException)
  138. int numHeaders = getHeadersCount();
  139. if (position < numHeaders) {
  140. return mHeaderViewInfos.get(position).data;
  141. }
  142. // Adapter
  143. final int adjPosition = position - numHeaders;
  144. int adapterCount = 0;
  145. if (mAdapter != null) {
  146. adapterCount = mAdapter.getCount();
  147. if (adjPosition < adapterCount) {
  148. return mAdapter.getItem(adjPosition);
  149. }
  150. }
  151. // Footer (off-limits positions will throw an IndexOutOfBoundsException)
  152. return mFooterViewInfos.get(adjPosition - adapterCount).data;
  153. }
  154. public long getItemId(int position) {
  155. int numHeaders = getHeadersCount();
  156. if (mAdapter != null && position >= numHeaders) {
  157. int adjPosition = position - numHeaders;
  158. int adapterCount = mAdapter.getCount();
  159. if (adjPosition < adapterCount) {
  160. return mAdapter.getItemId(adjPosition);
  161. }
  162. }
  163. return -1;
  164. }
  165. public boolean hasStableIds() {
  166. if (mAdapter != null) {
  167. return mAdapter.hasStableIds();
  168. }
  169. return false;
  170. }
  171. public View getView(int position, View convertView, ViewGroup parent) {
  172. // Header (negative positions will throw an IndexOutOfBoundsException)
  173. int numHeaders = getHeadersCount();
  174. if (position < numHeaders) {
  175. return mHeaderViewInfos.get(position).view;
  176. }
  177. // Adapter
  178. final int adjPosition = position - numHeaders;
  179. int adapterCount = 0;
  180. if (mAdapter != null) {
  181. adapterCount = mAdapter.getCount();
  182. if (adjPosition < adapterCount) {
  183. return mAdapter.getView(adjPosition, convertView, parent);
  184. }
  185. }
  186. // Footer (off-limits positions will throw an IndexOutOfBoundsException)
  187. return mFooterViewInfos.get(adjPosition - adapterCount).view;
  188. }
  189. public int getItemViewType(int position) {
  190. int numHeaders = getHeadersCount();
  191. if (mAdapter != null && position >= numHeaders) {
  192. int adjPosition = position - numHeaders;
  193. int adapterCount = mAdapter.getCount();
  194. if (adjPosition < adapterCount) {
  195. return mAdapter.getItemViewType(adjPosition);
  196. }
  197. }
  198. return AdapterView.ITEM_VIEW_TYPE_HEADER_OR_FOOTER;
  199. }
  200. public int getViewTypeCount() {
  201. if (mAdapter != null) {
  202. return mAdapter.getViewTypeCount();
  203. }
  204. return 1;
  205. }
  206. public void registerDataSetObserver(DataSetObserver observer) {
  207. if (mAdapter != null) {
  208. mAdapter.registerDataSetObserver(observer);
  209. }
  210. }
  211. public void unregisterDataSetObserver(DataSetObserver observer) {
  212. if (mAdapter != null) {
  213. mAdapter.unregisterDataSetObserver(observer);
  214. }
  215. }
  216. public Filter getFilter() {
  217. if (mIsFilterable) {
  218. return ((Filterable) mAdapter).getFilter();
  219. }
  220. return null;
  221. }
  222. public ListAdapter getWrappedAdapter() {
  223. return mAdapter;
  224. }
  225. }

 

核心源码分析:

 

1. HeaderViewListAdapter构造函数

它有3个参数,头、尾、正常条目的mAdapter,这个与我们(一)中分析的一样,也是传递这3个内容。

2. getCount()函数

这个函数返回真正的条目数:headerCount + footerCount + mAdapter.count

3. getItem(int position)

返回position条目上要显示的数据: 这里有也是3部分,其中第一个if判断该position是否是头部,第二个if判断是否是中间条目,最后是尾部条目,根据不同的情况返回对应的数据

 return mHeaderViewInfos.get(position).data;
 
 return mAdapter.getItem(adjPosition);//这个是创建普通adapter时,new adapter的时候传递给adapter的数据集合的position索引里的数据、
 

 

 return mFooterViewInfos.get(adjPosition - adapterCount).data;//构造函数传递过来的数组
 

 

4.getview函数

 

这个函数再熟悉不过了,就是为条目返回要显示的布局VIEW以及可在这个函数里为VIEW中元素赋值,如为条目上的TextView赋值一个字符串文本。在这里也和3中一样返回对应的VIEW(头部VIEW、普通条目VIEW、尾部VIEW):

return mHeaderViewInfos.get(position).view;

return mAdapter.getView(adjPosition,convertView,parent);

return mFooterViewInfos.get(adjPosition - adapterCount).view;

这里提醒一下,mAdapter就是我们普通条目的MyAdapter,就是我们在不需要添加 头尾部的时候,使用listview显示数据得先创建一个MyAdapter,比如MyAdapter继承BaseAdapter,然后重写getview等方法,我相信你懂我说的。

 

总结一下,观察以上HeaderViewListAdapter的4个函数我们会发现它们总是离不开mAdapter,也就是说它们总要改造一番:在原mAdapter的基础上都额外涉及到了header和footer,只有这样才能满足listview正常显示头尾部的需求。

 

四、模仿(三)中的HeaderViewListAdapter为RecyclerView自定义适合它的
HeaderViewListAdapter

1.我们要扩展adapter,那自然先创建一个子类HeaderViewAdapterForRecycler继承Adapter,这个HeaderViewAdapterForRecycler就是我们经常提到的全新的adapter.

   
  1. package com.anyikang.volunteer.sos.recyclerview;
  2. import android.support.v7.widget.RecyclerView.Adapter;
  3. import android.support.v7.widget.RecyclerView.ViewHolder;
  4. import android.view.View;
  5. import android.view.ViewGroup;
  6. import java.util.ArrayList;
  7. public class HeaderViewAdapterForRecycler extends Adapter {
  8. private Adapter mAdapter;
  9. ArrayList<View> mHeaderViewInfos;
  10. ArrayList<View> mFooterViewInfos;
  11. /**
  12. *
  13. * @param headerViewInfos 头布局VIEW
  14. * @param footerViewInfos 尾布局VIWE
  15. * @param adapter 普通ITEM的适配器adapter
  16. */
  17. public HeaderViewAdapterForRecycler (ArrayList<View> headerViewInfos,
  18. ArrayList<View> footerViewInfos, Adapter adapter) {
  19. mAdapter = adapter;
  20. if (headerViewInfos == null) {
  21. mHeaderViewInfos = new ArrayList<View>();
  22. } else {
  23. mHeaderViewInfos = headerViewInfos;
  24. }
  25. if (footerViewInfos == null) {
  26. mFooterViewInfos = new ArrayList<View>();
  27. } else {
  28. mFooterViewInfos = footerViewInfos;
  29. }
  30. }
  31. /**
  32. *
  33. * @return
  34. */
  35. @Override
  36. public int getItemCount() {
  37. if (mAdapter != null) {
  38. //从此列表的条目数为:头+尾+普通条目适配器的 条数(即列表中间条目数)
  39. return getFootersCount() + getHeadersCount() + mAdapter.getItemCount();
  40. } else {
  41. return getFootersCount() + getHeadersCount();
  42. }
  43. }
  44. @Override
  45. public void onBindViewHolder(ViewHolder holder, int position) {
  46. int numHeaders = getHeadersCount();
  47. if (position < numHeaders) {
  48. //判断是头布局的话,不用填充数据因为我们已经在 MainActivity里为头VIEW填充了数据
  49. return ;
  50. }
  51. final int adjPosition = position - numHeaders;
  52. int adapterCount = 0;
  53. if (mAdapter != null) {
  54. adapterCount = mAdapter.getItemCount();
  55. //中间条目的数据 我们直接调度 普通条目adapter的onBindViewHolder来为条目VIEW填充数据
  56. if (adjPosition < adapterCount) {
  57. mAdapter.onBindViewHolder(holder, adjPosition);
  58. return ;
  59. }
  60. }
  61. //其它情况就是尾布局VIEW,不用填充数据因为我们已经在 MainActivity里为尾VIEW填充了数据
  62. }
  63. /**
  64. * 得到条目类型:INVALID_TYPE表示当前条目是头部
  65. * @param position
  66. * @return
  67. */
  68. @Override
  69. public int getItemViewType(int position) {
  70. //
  71. int numHeaders = getHeadersCount();
  72. if (position < numHeaders) {//是头部
  73. return -1;
  74. }
  75. //正常条目部分
  76. // Adapter
  77. final int adjPosition = position - numHeaders;
  78. int adapterCount = 0;
  79. if (mAdapter != null) {
  80. adapterCount = mAdapter.getItemCount();
  81. if (adjPosition < adapterCount) {
  82. return mAdapter.getItemViewType(adjPosition);
  83. }
  84. }
  85. //footer部分
  86. return 1;
  87. }
  88. /**
  89. * 根据getItemViewType获得的viewType来返回对应的布局
  90. * @param parent
  91. * @param viewType
  92. * @return
  93. */
  94. @Override
  95. public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
  96. //header
  97. if(viewType == -1){
  98. return new HeaderViewHolder(mHeaderViewInfos.get(0));
  99. }else if(viewType == 1){//footer
  100. return new HeaderViewHolder(mFooterViewInfos.get(0));
  101. }
  102. // Footer (off-limits positions will throw an IndexOutOfBoundsException)
  103. return mAdapter.onCreateViewHolder(parent, viewType);
  104. }
  105. public int getHeadersCount() {
  106. return mHeaderViewInfos.size();
  107. }
  108. public int getFootersCount() {
  109. return mFooterViewInfos.size();
  110. }
  111. private static class HeaderViewHolder extends ViewHolder{
  112. public HeaderViewHolder(View view) {
  113. super(view);
  114. }
  115. }
  116. }

 

2. 普通条目的adapter

由于HeaderViewAdapterForRecycler 重新包装了普通条目的adapter,我们在此有必要帖出普通(中间条目)adapter的源码:


  
  1. package com.anyikang.volunteer.sos.recyclerview;
  2. import android.support.v7.widget.RecyclerView;
  3. import android.view.View;
  4. import android.view.ViewGroup;
  5. import android.widget.ImageView;
  6. public class MyRecyclerAdapter extends RecyclerView.Adapter<MyRecyclerAdapter.MyViewHolder> {
  7. private int[] list;
  8. private OnItemClickListener mOnItemClickListener;
  9. public MyRecyclerAdapter(int[] list) {
  10. this.list = list;
  11. }
  12. class MyViewHolder extends RecyclerView.ViewHolder{
  13. ImageView imv;
  14. public MyViewHolder(View view) {
  15. super(view);
  16. imv = (ImageView)view.findViewById(R.id.imv);
  17. }
  18. }
  19. @Override
  20. public int getItemCount() {
  21. return list.length;
  22. }
  23. @Override
  24. public void onBindViewHolder(MyViewHolder holder, final int position) {
  25. holder.imv.setImageResource(list[position]);
  26. if(mOnItemClickListener!=null){
  27. holder.itemView.setOnClickListener(new View.OnClickListener() {
  28. @Override
  29. public void onClick(View v) {
  30. mOnItemClickListener.onItemClick(v, position);
  31. }
  32. });
  33. }
  34. }
  35. @Override
  36. public MyViewHolder onCreateViewHolder(ViewGroup viewGroup, int arg1) {
  37. MyViewHolder holder = new MyViewHolder(View.inflate(viewGroup.getContext(), R.layout.list_item2, null));
  38. return holder;
  39. }
  40. /*public void addData(int position){
  41. list.add(position,"additem"+position);
  42. notifyItemInserted(position);
  43. }
  44. public void removeData(int position){
  45. list.remove(position);
  46. notifyItemRemoved(position);
  47. }*/
  48. public interface OnItemClickListener{
  49. void onItemClick(View view, int position);
  50. }
  51. public void setOnItemClickListener(OnItemClickListener listener){
  52. this.mOnItemClickListener = listener;
  53. }
  54. }

 

五、如何使用我们自定义好的MyRecyclerView与HeaderViewAdapterForRecycler

 

梳理使用过程:

(1)在MainAcitivity的布局文件里声明MyRecyclerView:


  
  1. <com.anyikang.volunteer.sos.recyclerview.MyRecyclerView
  2. android:layout_margin="50dp"
  3. android:id="@+id/recylerview"
  4. android:layout_width="match_parent"
  5. android:layout_height="wrap_content"
  6. />

(2)先定义普通adapter(用于显示中间条目)

代码,见(四)中的 第2
 

(3)定义HeaderViewAdapterForRecycler(具有处理理头部和尾部能力)

 

它相当于一个代理类,可以根据是否有头或尾的需求来重新封装了普通adapter
 

 

代码,见(四)中的 第1
 

 

(4)在MainActivity里使用全新的RecyclerView

 

核心步骤:

 

  
  1. //实例化布局中的recylerview控件
  2. recylerview = (MyRecyclerView)findViewById(R.id.recylerview);

  
  1. recylerview.addHeaderView(head); //添加头部
 recylerview.addFooterView(footer);//添加尾部
 
 adapter = new MyRecyclerAdapter(list); //普通条目(中间条目)的adapter	
 
 recylerview.setAdapter(adapter);   // setAdapter里发现有header或footer就会 将我们的adapter“偷梁换柱”
 

六、回马枪

重温原理, 直接上图:

demo工程源码下载

文章来源: blog.csdn.net,作者:冉航--小虾米,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/gaoxiaoweiandy/article/details/80411911

(完)