Android 优雅地为RecyclerView 添加HeaderView和FooterView

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);
    }
}

SparseArrayCompat说明

这里你可以看到,对于多个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();
    }
}

方法说明

getItemViewType

由于我们增加了headerView和footerView首先需要复写的就是getItemCount和getItemViewType。

getItemCount很好理解;

对于getItemType,可以看到我们的返回值是mHeaderViews.keyAt(position),这个值其实就是我们addHeaderView时的key,footerView是一样的处理方式,这里可以看出我们为每一个headerView创建了一个itemType。

onCreateViewHolder

可以看到,我们分别判断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

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

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();
    }
}

最后来看一下运行效果:
Android 优雅地为RecyclerView 添加HeaderView和FooterView_第1张图片

到这里只是适配了布局管理器为LinearLayoutManager的情况,如果是GridLayoutManager,StaggeredGridLayoutManager会有一下问题,怎么解决下次补上

你可能感兴趣的:(recyclerview使用)