上一篇博客中分析了ListView添加Header的原理,接下来我们就参考listview的实现原理来为RecyclerView添加Header。
RecyclerView的Adapter已经不再是基于View进行复用,而是基于ViewHolder进行复用;创建viewholder是基于重写的view type方法。因此,我们在recyclerview中直接保存View的集合,由于recyclerview的Adapter是基于view type,因此不再使用ArrayList进行保存,使用SparseArrayCompat进行保存。
private SparseArrayCompat<View> mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat<View> mFooterViews = new SparseArrayCompat<>();
private View mLoaderView;
private int mCounts;
这里使用mCounts记录添加Header等的次数,防止key重复。我们来看一下addHeaderView、removeHeaderView方法(Footer和Loader类似,就不进行分析了):
public void addHeaderView(View v) {
mHeaderViews.put(HeaderViewRecyclerAdapter.VIEW_TYPE_HEADER + mCounts++, v);
// Wrap the adapter if it wasn't already wrapped.
if (mAdapter != null) {
if (!(mAdapter instanceof HeaderViewRecyclerAdapter)) {
wrapHeaderViewRecyclerAdapter();
}
// In the case of re-adding a header view, or adding one later on,
// we need to notify the observer.
mAdapter.notifyItemInserted(mHeaderViews.size() - 1);
}
}
public boolean removeHeaderView(View v) {
if (mHeaderViews.size() > 0) {
boolean result = false;
if (mAdapter != null && ((HeaderViewRecyclerAdapter) mAdapter).removeHeader(v)) {
result = true;
}
removeFixedView(v, mHeaderViews);
return result;
}
return false;
}
这里和listview还是类似的,对notify方法做下对应修改。接下来看下setAdapter:
@Override
public void setAdapter(Adapter adapter) {
if (mHeaderViews.size() > 0 || mFooterViews.size() > 0 || mLoaderView != null) {
mAdapter = wrapHeaderViewRecyclerAdapter(mHeaderViews, mFooterViews, mLoaderView, adapter);
} else {
mAdapter = adapter;
}
super.setAdapter(mAdapter);
}
这里也和listview是相似的,如果有Header、Footer或者Loader就创建HeaderViewRecyclerAdapter进行包装。
接下来看重点的HeaderViewRecyclerAdapter:
private class HeaderViewRecyclerAdapter extends RecyclerView.Adapter<RecyclerView.ViewHolder> {
public static final int VIEW_TYPE_HEADER = Integer.MAX_VALUE >> 1;
public static final int VIEW_TYPE_FOOTER = Integer.MAX_VALUE >> 2;
public static final int VIEW_TYPE_LOADER = Integer.MAX_VALUE;
private SparseArrayCompat<View> mHeaderViews;
private SparseArrayCompat<View> mFooterViews;
private View mLoaderView;
private RecyclerView.Adapter mAdapter;
HeaderViewRecyclerAdapter(SparseArrayCompat<View> mHeaderViews, SparseArrayCompat<View> mFooterViews, View mLoaderView,
RecyclerView.Adapter adapter) {
this.mHeaderViews = mHeaderViews;
this.mFooterViews = mFooterViews;
this.mLoaderView = mLoaderView;
if (mAdapter != null) {
mAdapter.unregisterAdapterDataObserver(mObserver);
}
mAdapter = adapter;
if (mAdapter != null) {
mAdapter.registerAdapterDataObserver(mObserver);
}
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup viewGroup, int viewType) {
if (mHeaderViews.get(viewType) != null) {
return new ViewHolder(mHeaderViews.get(viewType));
} else if (mFooterViews.get(viewType) != null) {
return new ViewHolder(mFooterViews.get(viewType));
} else if (viewType == VIEW_TYPE_LOADER) {
return new ViewHolder(mLoaderView);
}
if (mAdapter != null)
return mAdapter.createViewHolder(viewGroup, viewType);
throw new RuntimeException("position should match header or footer or loader");
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return;
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
mAdapter.onBindViewHolder(holder, adjPosition);
}
}
}
@Override
public int getItemCount() {
return getFootersCount() + getHeadersCount() + getLoadersCount() + getCount();
}
private int getCount() {
return mAdapter != null ? mAdapter.getItemCount() : 0;
}
@Override
public int getItemViewType(int position) {
int numHeaders = getHeadersCount();
if (position < numHeaders) {
return mHeaderViews.keyAt(position);
}
// Adapter
final int adjPosition = position - numHeaders;
int adapterCount = 0;
if (mAdapter != null) {
adapterCount = mAdapter.getItemCount();
if (adjPosition < adapterCount) {
return mAdapter.getItemViewType(adjPosition);
}
}
int numFooters = getFootersCount();
final int footPosition = position - numHeaders - adapterCount;
if (footPosition < numFooters) {
return mFooterViews.keyAt(footPosition);
}
return VIEW_TYPE_LOADER;
}
private int getHeadersCount() {
return mHeaderViews.size();
}
private int getFootersCount() {
return mFooterViews.size();
}
private int getLoadersCount() {
return mLoaderView != null ? 1 : 0;
}
private boolean removeHeader(View v) {
int index = mHeaderViews.indexOfValue(v);
if (index >= 0) {
mHeaderViews.removeAt(index);
notifyItemRemoved(index);
return true;
}
return false;
}
private boolean removeFooter(View v) {
int index = mFooterViews.indexOfValue(v);
if (index >= 0) {
mFooterViews.removeAt(index);
notifyItemRemoved(getHeadersCount() + getCount() + index);
return true;
}
return false;
}
private boolean removeLoader() {
boolean success = mLoaderView != null;
if (success) {
mLoaderView = null;
notifyItemRemoved(getItemCount());
}
return success;
}
@Override
public void onViewAttachedToWindow(RecyclerView.ViewHolder holder) {
if (holder instanceof ViewHolder) return;
if (mAdapter != null) mAdapter.onViewAttachedToWindow(holder);
}
@Override
public void onViewDetachedFromWindow(RecyclerView.ViewHolder holder) {
if (holder instanceof ViewHolder) return;
if (mAdapter != null) mAdapter.onViewDetachedFromWindow(holder);
}
@Override
public void onViewRecycled(RecyclerView.ViewHolder holder) {
if (holder instanceof ViewHolder) return;
if (mAdapter != null) mAdapter.onViewRecycled(holder);
}
private RecyclerView.AdapterDataObserver mObserver = new RecyclerView.AdapterDataObserver() {
@Override
public void onChanged() {
notifyDataSetChanged();
}
@Override
public void onItemRangeChanged(int positionStart, int itemCount) {
notifyItemRangeChanged(positionStart + getHeadersCount(), itemCount);
}
@Override
public void onItemRangeInserted(int positionStart, int itemCount) {
notifyItemRangeInserted(positionStart + getHeadersCount(), itemCount);
}
@Override
public void onItemRangeMoved(int fromPosition, int toPosition, int itemCount) {
int headerViewsCountCount = getHeadersCount();
notifyItemRangeChanged(fromPosition + headerViewsCountCount,
toPosition + headerViewsCountCount + itemCount);
}
@Override
public void onItemRangeRemoved(int positionStart, int itemCount) {
notifyItemRangeRemoved(positionStart + getHeadersCount(), itemCount);
}
};
}
代码比较多,重点主要是两块:
- listview是根据position获取对应的View,而recyclerview是根据view type创建view holder,因此采用了
mHeaderViews.keyAt(position)
来获取唯一的view type。 - listview只有一个notifyDataChanged方法,recyclerview多了很多类notify方法,比如notifyItemInserted(position)。我们要考虑position的位置矫正,这里采用的对被包装Adapter注册AdapterDataObserver,然后处理Header导致的位置问题。
recyclerview的Adapter是带有泛型的,为了防止类型转换错误,自定义了ViewHolder,并在onViewRecycled等方法中进行类型判断,防止类型转换错误。
具体的代码可以参见GitHub:HeaderRecyclerView