天天看点

利用装饰模式为RecyclerView添加头部与尾部

装饰模式

装饰模式指的是在不必改变原类文件和使用继承的情况下,动态地扩展一个对象的功能。它是通过创建一个包装对象,也就是装饰来包裹真实的对象。

简单的来说,就是不使用继承的前提下来扩展一个对象的功能。一般传入某个对象作为参数。

Java里面最常见的装饰设计模式就是:

//将某个文件以流的形式读取
File file = new File ("hello.txt"); 
FileInputStream in=new FileInputStream(file); 
BufferedInputStream inBuffered=new BufferedInputStream (in); 
//简写成这样
BufferedInputStream inBuffered = 
new BufferedInputStream (new FileInputStream(new File ("hello.txt"))); 
           

RecyclerView添加头部与尾部

一般情况下,我们给recyclerView添加头部会有这样的思路(就不上代码了):

  1. 新建一个adapter,给两个标志位,用来表明是否是头部或者尾部
  2. 在getItemViewType根据position返回不同的viewType
  3. 在onCreateViewHolder方法里面根据viewType匹配,如果是头部就加载头部的ViewHolder,尾部就加载尾部的ViewHolder
  4. 在onBindViewHolder里面根据getItemViewType(position)来判断加载不同的数据

这样写完全没问题,但是如果我们想像listView那样,直接通过addHeadView方法传入view布局。这个时候,就要用到我们的装饰模式了。

首先,我们依然要新建adapter继承recyclerView的adapter,这是必须的,命名就叫RvAdapterWrapper,在构造方法里,将RecyclerView.Adapter作为参数传入,这个类就当做装饰RecyclerView.Adapter的类,除了重写必须实现的方法外,新增几个添加移除头部尾部的方法,如:

class RvAdapterWrapper(adapter:RecyclerView.Adapter<RecyclerView.ViewHolder>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    private var rvAdapter:RecyclerView.Adapter<RecyclerView.ViewHolder> = adapter  
    private var headerViews = arrayListOf<View>()
    private var footerViews = arrayListOf<View>()


    //添加头部
    fun addHeaderView(view:View){
        if (!headerViews.contains(view)){
            headerViews.add(view)
            notifyDataSetChanged()
        }
    }

    //移除头部
    fun removeHeaderView(view:View){
        if (headerViews.contains(view)){
            headerViews.remove(view)
            notifyDataSetChanged()
        }
    }

    //添加尾部
    fun addFooterView(view:View){
        if (!footerViews.contains(view)){
            footerViews.add(view)
            notifyDataSetChanged()
        }
    }

    //移除尾部
    fun removeFooterView(view:View){
        if (footerViews.contains(view)){
            footerViews.remove(view)
            notifyDataSetChanged()
        }
    }
}
           

然后,在必须实现的方法里面稍作修改:

override fun onCreateViewHolder(parent: ViewGroup, position: Int):                  RecyclerView.ViewHolder {
        //判断位置是否是在头部
        if (position<headerViews.size){
            return createHeaderFooterHolder(headerViews[position])
        }
        //正常的内容显示的位置
        if (position<headerViews.size+rvAdapter.itemCount){
            //从headerView长度开始
            return rvAdapter.onCreateViewHolder(parent,position-headerViews.size)
        }
        //尾部显示的位置
        return createHeaderFooterHolder(footerViews[position-headerViews.size-rvAdapter.itemCount])
    }

    //头部ViewHolder 啥也不用做
    private fun createHeaderFooterHolder(view: View): RecyclerView.ViewHolder {
        return object : RecyclerView.ViewHolder(view){}
    }

    //返回的count要加上头部尾部长度
    override fun getItemCount(): Int {
        return rvAdapter.itemCount+headerViews.size+footerViews.size
    }

    //直接把位置返回,为了给onCreateViewHolder创造位置
    override fun getItemViewType(position: Int): Int {
        return position
    }

    //头部尾部直接返回,内容区域正常加载
    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        if (position<headerViews.size){
            return
        }
        if (position<headerViews.size+rvAdapter.itemCount){
            rvAdapter.onBindViewHolder(holder, position-headerViews.size)
        }
    }
           

稍微解释一下,首先我们需要在getItemCount里面返回所有的内容的个数,包括头部尾部内容区域,然后在onCreateViewHolder方法里面根据位置判断加载的内容,如果在头部区域就加载头部,在recyclerview列表区域就加载列表,onCreateViewHolder里面有一个viewType是通过getItemViewType返回,在这里我们就直接把position返回,同样在onBindViewHolder方法里根据position判断头部尾部区域,然后再进行相应的操作。

值得注意的是,由于我们的position是添加了头部的,所以在真正设置列表内容的时候,要减去头部的个数。

这个时候,这个用来装饰recyclerView adapter类的代码就差不多写完了。

然后在activity里面使用,按照recyclerView的使用流程,创建adapter之后,再新建一个我们自己的RvAdapterWrapper,将刚刚创建的adapter传进去,然后再调用addHeaderView方法添加一个头部,这样就完成了,运行看看效果,完美!

//新建的recyclerview的adapter
RecyclerAdapter mRealAdapter = new RecyclerAdapter();
RvAdapterWrapper rvadapterWrapper= new RvAdapterWrapper(mRealAdapter);
// setAdapter
mRecyclerView.setAdapter(rvadapterWrapper);
View headerView = LayoutInflater.from(this).inflate(R.layout.layout_header_view,mRecyclerView,false);
rvadapterWrapper.addHeaderView(headerView);
           

但是这样看着,多多少少有点怪怪的,因为每次使用都要新建一个Wrapper类来包裹我们的adapter,貌似违背了面向对象的最少知识原则,如果能去掉这步操作就更好了,接下来就再进一步的优化一下。

我们理想状态是使用recyclerView直接addHeaderView,这样就新建一个类继承recyclerView,简单点,就叫WrapperRv,然后稍作修改,首先是在setAdapter方法里直接替换adapter为我们的RvAdapterWrapper,然后加入添加移除头部尾部的方法。如:

override fun setAdapter(adapter: Adapter<*>?) {
        wrapper = RvAdapterWrapper(adapter as Adapter<ViewHolder>)
        super.setAdapter(wrapper)
    }

    fun addHeaderView(view: View){
        wrapper?.apply {
            this.addHeaderView(view)
        }
    }

    fun removeHeaderView(view: View){
        wrapper?.apply {
            this.removeHeaderView(view)
        }
    }

    fun addFooterView(view: View){
        wrapper?.apply {
            this.addFooterView(view)
        }
    }

    fun removeFooterView(view: View){
        wrapper?.apply {
            this.removeFooterView(view)
        }
    }
           

再次使用的时候,直接就使用我们自定义的recyclerView,就可以有添加头部尾部的方法了。

mRv= (WrapperRv) findViewById(R.id.rv);
mRv.setLayoutManager(new LinearLayoutManager(this));
mRv.setAdapter(new RecyclerAdapter());
View headerView = LayoutInflater.from(this).inflate(R.layout.layout_header_view,mRecyclerView,false);
mRv.addHeaderView(headerView);
           

运行,效果还是一样的,但是,还有个问题,就是我们经常在对一些数据进行操作的时候,需要notifyDatasetChanged(),通知数据改变,在这里,我们改变一个数据之后,发现页面上并没有修改,这是因为我们修改的只是recyclerView 的adapter而不是我们的RvAdapterWrapper,因此,在RvAdapterWrapper里面,我们需要注册对传入的adapter作监听,如,在这里再调用一次notifyDataSetChanged方法,这样就可以了。

rvAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
            @Override
            public void onChanged() {
                notifyDataSetChanged();
            }
        });