最近一直在看《Android源码设计模式解析与实战》一书,不过也没有忘了自己的爱好,关注那些炫酷的控件,也看到了很多下拉刷新的库,以前一直都有PullToRefreshListView、XListView等下拉刷新库,然后最近就在想,会用是会用了,不过怎么着最后自己能力强了,也得明白它的原理吧,所以在网上看了看如何实现上拉加载更多和下拉刷新的文章,发现讲得不错的也很多,也有好几种方式,下拉刷新现在最简单的当然是利用SwipeRefreshLayout了。这确实很简单,但是我想让上拉和下拉都统一,所以也就没有选择SwipeRefreshLayout,而是通过添加头布局和脚布局,这里我讲的RecyclerView,因为RecyclerView没有原生的添加头布局和脚布局的方法,所有这两个方法也得自己实现,ListView自己就有添加头布局和脚布局的方法,所以实现起来应该要更简单一点,后面我会再研究一下如何实现ListView的下拉刷新。下面是我参考的文章。
https://idisfkj.github.io/2016/07/05/RecyclerView%E6%B7%BB%E5%8A%A0header%E4%B8%8Efooter/
首先,既然ListView中直接就有添加HeaderView和FooterView的方法,就先看看它是怎么实现的。
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 (!(mAdapter instanceof 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();
}
}
}
if (!(mAdapter instanceof HeaderViewListAdapter)) {
wrapHeaderListAdapterInternal();
}
点进去:
protected void wrapHeaderListAdapterInternal() {
mAdapter = wrapHeaderListAdapterInternal(mHeaderViewInfos, mFooterViewInfos, mAdapter);
}
再继续看:
protected HeaderViewListAdapter wrapHeaderListAdapterInternal(
ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> footerViewInfos,
ListAdapter adapter) {
return new HeaderViewListAdapter(headerViewInfos, footerViewInfos, adapter);
}
再看看HeaderViewListAdapter的构造方法:
public HeaderViewListAdapter(ArrayList<ListView.FixedViewInfo> headerViewInfos,
ArrayList<ListView.FixedViewInfo> 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);
}
其实这是个装饰者模式,添加HeaderView的时候将原来的Adapter重新构造成HeaderViewListAdapter,这个HeaderViewListAdapter里面就包括了新添加的头布局,所以我们可以仿照ListView添加HeaderView的方法来为RecyclerView添加HeaderView和FooterView。
重写一个HeaderRecyclerView继承RecyclerView,首先也添加一个ViewInfo类:
class FixedViewInfo {
View view;
int viewType;
}
public ArrayList<FixedViewInfo> mHeaderViewInfos = new ArrayList<>();
public ArrayList<FixedViewInfo> mFooterViewInfos = new ArrayList<>();
public Adapter mAdapter;
private boolean isShouldSpan;
public static final int BASE_HEADER_VIEW_TYPE = -1 << 10;
public static final int BASE_FOOTER_VIEW_TYPE = -1 << 11;
然后为它添加一个addHeaderView()和addFooterView()方法:
public void addHeaderView(View view) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(params);
// view.setVisibility(GONE);
FixedViewInfo info = new FixedViewInfo();
info.view = view;
info.viewType = BASE_HEADER_VIEW_TYPE + mHeaderViewInfos.size();
mHeaderViewInfos.clear();
mHeaderViewInfos.add(info);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
public View getHeaderView(int position) {
if (mHeaderViewInfos.isEmpty()) {
throw new IllegalStateException("you must add a HeaderView before!");
}
return mHeaderViewInfos.get(position).view;
}
public void addFooterView(View view) {
LinearLayout.LayoutParams params = new LinearLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT);
view.setLayoutParams(params);
// view.setVisibility(GONE);
FixedViewInfo info = new FixedViewInfo();
info.view = view;
info.viewType = BASE_FOOTER_VIEW_TYPE + mFooterViewInfos.size();
mFooterViewInfos.clear();
mFooterViewInfos.add(info);
if (mAdapter != null) {
mAdapter.notifyDataSetChanged();
}
}
public View getFooterView(int position) {
if (mFooterViewInfos.isEmpty()) {
throw new IllegalStateException("you must add a FooterView before!");
}
return mFooterViewInfos.get(position).view;
}
@Override
public void setAdapter(Adapter adapter) {
//装饰者模式,添加HeaderView和FooterView,WrapperRecyclerViewAdapter是关键
if (!(adapter instanceof WrapperRecyclerViewAdapter))
mAdapter = new WrapperRecyclerViewAdapter(mHeaderViewInfos, mFooterViewInfos, adapter);
super.setAdapter(mAdapter);
if (isShouldSpan) {
//为了适配GridLayoutManager和StaggeredGridLayoutManager
((WrapperRecyclerViewAdapter) mAdapter).adjustSpanSize(this);
}
}
@Override
public Adapter getAdapter() {
return mAdapter;
}
重写setLayoutManager()方法:
@Override
public void setLayoutManager(LayoutManager layout) {
if (layout instanceof GridLayoutManager || layout instanceof StaggeredGridLayoutManager)
isShouldSpan = true;
super.setLayoutManager(layout);
}
然后看关键的WrapperRecyclerViewAdapter类:
class WrapperRecyclerViewAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
private ArrayList<HeaderRecyclerView.FixedViewInfo> mHeaderViewInfos;
private ArrayList<HeaderRecyclerView.FixedViewInfo> mFooterViewInfos;
private RecyclerView.Adapter mAdapter;
private ArrayList<HeaderRecyclerView.FixedViewInfo> EMPTY_INFO_LIST = new ArrayList<>();
private boolean isStaggered;
WrapperRecyclerViewAdapter(ArrayList<HeaderRecyclerView.FixedViewInfo> headerViewInfos,
ArrayList<HeaderRecyclerView.FixedViewInfo> footerViewInfos,
RecyclerView.Adapter adapter) {
mAdapter = adapter;
//判断是否有头布局
if (headerViewInfos == null) {
mHeaderViewInfos = EMPTY_INFO_LIST;
} else {
mHeaderViewInfos = headerViewInfos;
}
//判断是否有脚布局
if (footerViewInfos == null) {
mFooterViewInfos = EMPTY_INFO_LIST;
} else {
mFooterViewInfos = footerViewInfos;
}
}
private int getHeadersCount() {
return mHeaderViewInfos.size();
}
private int getFootersCount() {
return mFooterViewInfos.size();
}
public boolean isEmpty() {
return mAdapter == null;
}
public boolean removeHeader(View view) {
for (int i = 0; i < mHeaderViewInfos.size(); i++) {
if (mHeaderViewInfos.get(i).view == view) {
mHeaderViewInfos.remove(i);
return true;
}
}
return false;
}
public boolean removeFooter(View view) {
for (int i = 0; i < mFooterViewInfos.size(); i++) {
if (mFooterViewInfos.get(i).view == view) {
mFooterViewInfos.remove(i);
return true;
}
}
return false;
}
/**
* Description:适配GridLayoutManager和StaggeredGridLayoutManager
* Date:2017/5/14
*/
void adjustSpanSize(RecyclerView recyclerView) {
if (recyclerView.getLayoutManager() instanceof GridLayoutManager) {
final GridLayoutManager manager = (GridLayoutManager) recyclerView.getLayoutManager();
manager.setSpanSizeLookup(new GridLayoutManager.SpanSizeLookup() {
@Override
public int getSpanSize(int position) {
int numHeaders = getHeadersCount();
int adjPosition = position - numHeaders;
if (position < numHeaders || adjPosition >= mAdapter.getItemCount())
return manager.getSpanCount();
return 1;
}
});
}
if (recyclerView.getLayoutManager() instanceof StaggeredGridLayoutManager) {
isStaggered = true;
}
}
/**
* Description:根据头布局脚布局是否可见设置高度
* Date:2017/5/14
*/
private void setVisibility(View view) {
ViewGroup.LayoutParams params = view.getLayoutParams();
if (view.getVisibility() == View.GONE) {
params.width = 0;
params.height = 0;
} else {
params.width = LinearLayout.LayoutParams.MATCH_PARENT;
params.height = LinearLayout.LayoutParams.WRAP_CONTENT;
}
view.setLayoutParams(params);
}
/**
* Description:根据当前item属于头布局,脚布局或者普通item返回不同view
* Date:2017/5/14
*/
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
if (viewType >= HeaderRecyclerView.BASE_HEADER_VIEW_TYPE && viewType < HeaderRecyclerView.BASE_HEADER_VIEW_TYPE + getHeadersCount()) {
View view = mHeaderViewInfos.get(viewType - HeaderRecyclerView.BASE_HEADER_VIEW_TYPE).view;
setVisibility(view);
return viewHolder(view);
} else if (viewType >= HeaderRecyclerView.BASE_FOOTER_VIEW_TYPE && viewType < HeaderRecyclerView.BASE_FOOTER_VIEW_TYPE + getFootersCount()) {
View view = mFooterViewInfos.get(viewType - HeaderRecyclerView.BASE_FOOTER_VIEW_TYPE).view;
setVisibility(view);
return viewHolder(view);
}
return mAdapter.onCreateViewHolder(parent, viewType);
}
/**
* Description:根据不同item类型填充数据
* Date:2017/5/14
*/
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return;
}
int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
mAdapter.onBindViewHolder(holder, adjPosition);
return;
}
}
}
@Override
public long getItemId(int position) {
int numHeaders = getHeadersCount();
if (mAdapter != null && position >= numHeaders) {
int adjPosition = position - numHeaders;
int adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemId(adjPosition);
}
}
return -1;
}
/**
* Description:这也是一个关键,根据view的位置,来判断是头布局还是脚布局还是正常item
* Date:2017/5/14
*/
@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViewInfos.get(position).viewType;
}
int adjPosition = position - numHeaders;
int adapterPosition = 0;
if (mAdapter != null) {
adapterPosition = mAdapter.getItemCount();
if (adjPosition < adapterPosition) {
return mAdapter.getItemViewType(adjPosition);
}
}
return mFooterViewInfos.get(position - adapterPosition - getHeadersCount()).viewType;
}
/**
* Description:返回item数目
* Date:2017/5/14
*/
@Override
public int getItemCount() {
if (mAdapter != null) {
return getHeadersCount() + getFootersCount() + mAdapter.getItemCount();
} else {
return getHeadersCount() + getFootersCount();
}
}
private RecyclerView.ViewHolder viewHolder(final View itemView) {
if (isStaggered) {
StaggeredGridLayoutManager.LayoutParams params = new StaggeredGridLayoutManager.LayoutParams(StaggeredGridLayoutManager.LayoutParams.MATCH_PARENT,
0);
params.setFullSpan(true);
itemView.setLayoutParams(params);
}
return new RecyclerView.ViewHolder(itemView) {
};
}
}
接下来只要在xml布局文件中将RecyclerView替换为自定义的HeaderRecyclerView,MainActivity中正常使用RecyclerView即可:
xml布局:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:animateLayoutChanges="true"
tools:context="com.qinshou.qsrefreshrecyclerview.MainActivity">
<com.qinshou.qsrefreshrecyclerview.HeaderRecyclerView
android:id="@+id/hrv_test"
android:layout_width="match_parent"
android:layout_height="match_parent">
</com.qinshou.qsrefreshrecyclerview.HeaderRecyclerView>
</RelativeLayout>
MainActivity中:
mList = new ArrayList<>();
for (int i = 0; i < 100; i++) {
mList.add("my love " + i);
}
hrvTest = (HeaderRecyclerView) findViewById(R.id.hrv_test);
mTestAdapter = new TestAdapter(mList);
LinearLayoutManager mLinearLayoutManager = new LinearLayoutManager(this);
hrvTest.setLayoutManager(mLinearLayoutManager);
hrvTest.setAdapter(mTestAdapter);
View headerView = LayoutInflater.from(this).inflate(R.layout.header_layout, null);
hrvTest.addHeaderView(headerView);
View footerView = LayoutInflater.from(this).inflate(R.layout.footer_layout, null);
hrvTest.addFooterView(footerView);
一个普通的TestAdapter:
class TestAdapter extends RecyclerView.Adapter<TestAdapter.TestViewHolder> {
private List<String> list;
TestAdapter(List<String> list) {
this.list = list;
}
@Override
public TestViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = LayoutInflater.from(parent.getContext()).inflate(R.layout.item_hrvtest, parent, false);
return new TestViewHolder(view);
}
@Override
public void onBindViewHolder(TestViewHolder holder, int position) {
holder.tvTest.setText(list.get(position));
}
@Override
public int getItemCount() {
return list.size();
}
class TestViewHolder extends RecyclerView.ViewHolder {
TextView tvTest;
TestViewHolder(View itemView) {
super(itemView);
tvTest = (TextView) itemView.findViewById(R.id.tv_test);
}
}
}
运行程序,可以看到成功添加的头布局和脚布局:
我限制了只能添加一个头布局和脚布局,所以在addHeaderView()方法中会先清空一下list:
mHeaderViewInfos.clear();
mHeaderViewInfos.add(info);
其实模仿着ListView来写,实现添加头布局和脚布局也不难,主要是刚开始也没想到这么简单,在学习了自定义View后都没怎么用到过,想实现什么效果基本上也是去网上找现成的,一般来说也是原生>第三方>自定义,开源是个好东西,这次也是为了了解一下它的原理和增加一下自己的知识面才决定实现这个效果,下拉刷新和上拉加载更多会在另一篇博客记录的。