RecyclerView的确很强大,但它并没有实现像ListView那样,可以为列表添加头布局或尾布局,网上查找了一下方法不一,觉得鸿洋大神的方法不错,在这里做下记录。
鸿洋大神原博客链接:
http://blog.csdn.net/lmj623565791/article/details/51854533
要知道headerView和footerView其实也是item的一种,只是它们显示的位置不同而已,这样我们就可以通过设置itemType来实现。
按照上面的原理,发现当我们在Adapter添加特殊的itemType后,Adapter中的getItemType、getItemCount、onBindViewHolder、onCreateViewHolder等几乎所有的方法都要做相应的改变。
实现起来很多细节需要注意,把握不好就很容易出错。这样看来直接改Adapte的代码似乎不太划算,我们最好能够设计一个类,可以无缝的为原有的Adapter添加headerView和footerView。
以下是通过类似装饰者模式,去设计一个类,增强原有Adapter的功能,使其支持addHeaderView和addFooterView。这样我们就可以不去改动我们之前已经完成的代码,灵活的去扩展功能了。
public class WindyWrapperAdapter extends RecyclerView.Adapter {
private static final int TYPE_HEADER_START = 100000;
private static final int TYPE_FOOTER_START = 200000;
private SparseArrayCompat mHeaderViews = new SparseArrayCompat<>();
private SparseArrayCompat mFooterViews = new SparseArrayCompat<>();
private RecyclerView.Adapter mInnerAdapter;
public WindyWrapperAdapter(RecyclerView.Adapter innerAdapter) {
this.mInnerAdapter = innerAdapter;
}
private boolean isHeaderViewPos(int position) {
return position < mHeaderViews.size();
}
private boolean isFooterViewPos(int position) {
return position >= getItemCount() - mFooterViews.size();
}
public void addHeaderView(View headView) {
mHeaderViews.put(mHeaderViews.size() + TYPE_HEADER_START, headView);
}
public void addFooterView(View footerView) {
mFooterViews.put(mFooterViews.size() + TYPE_FOOTER_START, footerView);
}
}
这里你可以看到,对于多个HeaderView,讲道理我们首先想到的应该是使用List,而这里我们为什么要使用SparseArrayCompat呢?
SparseArrayCompat有什么特点呢?它类似于Map,只不过在某些情况下比Map的性能要好,并且只能存储key为int的情况。
并且可以看到我们对每个HeaderView,都有一个特定的key与其对应,第一个headerView对应的是TYPE_HEADER_START,第二个对应的是TYPE_HEADER_START+1;
为什么要这么做呢?
这两个问题都需要到复写onCreateViewHolder的时候来说明。
public class WindyWrapperAdapter extends RecyclerView.Adapter {
@Override
public RecyclerView.ViewHolder onCreateViewHolder(final ViewGroup parent, final int viewType) {
if (mHeaderViews.get(viewType) != null) {
return WindyViewHolder.createViewHolder(mHeaderViews.get(viewType));
} else if (mFooterViews.get(viewType) != null) {
return WindyViewHolder.createViewHolder(mFooterViews.get(viewType));
}
return mInnerAdapter.createViewHolder(parent, viewType);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (isHeaderViewPos(position) || isFooterViewPos(position)) {
return;
}
mInnerAdapter.onBindViewHolder(holder, position - mHeaderViews.size());
}
@Override
public int getItemViewType(int position) {
if (isHeaderViewPos(position)) {
return mHeaderViews.keyAt(position);
}
if (isFooterViewPos(position)) {
return mFooterViews.keyAt(position - mHeaderViews.size() - mInnerAdapter.getItemCount());
}
return mInnerAdapter.getItemViewType(position);
}
@Override
public int getItemCount() {
return mInnerAdapter.getItemCount() + mFooterViews.size() + mHeaderViews.size();
}
}
由于我们增加了headerView和footerView首先需要复写的就是getItemCount和getItemViewType。
getItemCount很好理解;
对于getItemType,可以看到我们的返回值是mHeaderViews.keyAt(position),这个值其实就是我们addHeaderView时的key,footerView是一样的处理方式,这里可以看出我们为每一个headerView创建了一个itemType。
可以看到,我们分别判断viewType,如果是headview或者是footerview,我们则为其单独创建WindyHolder,WindyHolder是为了区分而写的一个继承至RecyclerView.ViewHolder的类,没什么实现,这里还是贴一下代码:
public class WindyViewHolder extends RecyclerView.ViewHolder {
public WindyViewHolder(View itemView) {
super(itemView);
}
static WindyViewHolder createViewHolder(View itemView) {
return new WindyViewHolder(itemView);
}
static WindyViewHolder createViewHolder(Context context, int layoutId, ViewGroup parent) {
View itemView = LayoutInflater.from(context).inflate(layoutId, parent,false);
return createViewHolder(itemView);
}
}
这里,我们就可以解答之前的问题了:
为什么我要用SparseArrayCompat而不是List?
为什么我要让每个headerView对应一个itemType,而不是固定的一个?
对于headerView假设我们有多个,那么onCreateViewHolder返回的ViewHolder中的itemView应该对应不同的headerView,如果是List,那么不同的headerView应该对应着:list.get(0),list.get(1)等。
但是问题来了,该方法并没有position参数,只有itemType参数,如果itemType还是固定的一个值,那么你是没有办法根据参数得到不同的headerView的。
所以,我利用SparseArrayCompat,将其key作为itemType,value为我们的headerView,在onCreateViewHolder中,直接通过itemType,即可获得我们的headerView,然后构造ViewHolder对象。而且我们的取值是从100000开始的,正常的itemType是从0开始取值的,所以正常情况下,是不可能发生冲突的。
onBindViewHolder比较简单,发现是HeaderView或者FooterView直接return即可,因为对于头部和底部我们仅仅做展示即可,对于事件应该是在addHeaderView等方法前设置。
这样就初步完成了我们的装饰类,
使用起来非常简单,贴一下简要代码:
public class MainActivity extends AppCompatActivity {
private RecyclerView mRecyclerView;
private List mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ActionBar actionBar = getSupportActionBar();
if (actionBar!=null){
actionBar.hide();
}
initData();
mRecyclerView = (RecyclerView) findViewById(R.id.recycler_view);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
mRecyclerView.setLayoutManager(linearLayoutManager);
InnerAdapter innerAdapter = new InnerAdapter(mData);
WindyWrapperAdapter adapter = new WindyWrapperAdapter(innerAdapter);
adapter.addHeaderView(LayoutInflater.from(this).inflate(R.layout.head01_layout, mRecyclerView, false));
mRecyclerView.setAdapter(adapter);
}
private void initData() {
for (int i = 0; i < 50; i++) {
mData.add("data:" + i);
}
}
}
InnerAdapter是一个继承至RecyclerView.Adapter的类,很简单的实现:
public class InnerAdapter extends RecyclerView.Adapter {
private List mData = new ArrayList<>();
public InnerAdapter(List mData) {
this.mData = mData;
}
@Override
public RecyclerView.ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
return WindyViewHolder.createViewHolder(parent.getContext(), R.layout.item_layout,parent);
}
@Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
TextView tv = (TextView) holder.itemView.findViewById(R.id.item_tv);
tv.setText(mData.get(position));
}
@Override
public int getItemCount() {
return mData.size();
}
}
到这里只是适配了布局管理器为LinearLayoutManager的情况,如果是GridLayoutManager,StaggeredGridLayoutManager会有一下问题,怎么解决下次补上