天天看點

利用裝飾模式為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();
            }
        });