超級封裝RecyclerView的適配器Adapter 只需二三十行代碼

前言

android開發中,RecyclerView是很常用的控件,而且功能也很強大,並且各種三方封裝或者擴展庫也是非常多,如:BaseQuickAdapter,XRecyclerview,當然還有我以前封裝的LtRecyclerView

比如BaseQuickAdapter雖然封裝的非常方便,但那是相對於java語言,那用kotlin能不能使Adapter的封裝更方便呢?答案是可以

第一次簡單封裝

/**
 * 封裝適配器,適用於單條目
 *
 * @param T bean類的泛型
 * @param VH ViewHolder的泛型
 */
abstract class BaseAdapterOneType<T, VH : RecyclerView.ViewHolder>(val list: MutableList<T>) : RecyclerView.Adapter<VH>() {
    /**
     * 給view設置數據
     */
    abstract fun setData(b: T, i: Int, h: VH)

    override fun onBindViewHolder(holder: VH, position: Int) = setData(list[position], position, holder)

    override fun getItemCount() = list.size
}

使用起來需要繼承該類,並重寫setData方法和寫一個ViewHolder,如下:

    fun initView(){
        .....
        rv.adapter = MainAdapter(arrayListOf())//設置適配器
    }

    class MainAdapter(list: MutableList<String>) : BaseAdapterOneType<String, MainAdapterVH>(list) {
        override fun onCreateViewHolder(p0: ViewGroup, p1: Int): MainAdapterVH = MainAdapterVH(LayoutInflater.from(p0.context).inflate(R.layout.item_pop_text, p0, false))//設置佈局

        override fun setData(b: String, i: Int, h: MainAdapterVH) {//設置數據
            h.tv.text = b
        }
    }

    class MainAdapterVH(val v: View) : RecyclerView.ViewHolder(v) {//查找View
        val tv = v.tv1
    }

但是這樣和java差不多,寫起來還是比較麻煩

第二次封裝,簡少所需代碼

abstract class BaseAdapterOneType3<T>(val list: MutableList<T>, @LayoutRes val layoutId: Int) : RecyclerView.Adapter<BaseViewHolder>() {
    /**
     * 給view設置數據
     */
    abstract fun setData(v: View, b: T, i: Int, h: BaseViewHolder)

    override fun onBindViewHolder(holder: BaseViewHolder, position: Int) = setData(holder.itemView, list[position], position, holder)

    override fun getItemCount() = list.size

    override fun onCreateViewHolder(p0: ViewGroup, p1: Int): BaseViewHolder = BaseViewHolder(LayoutInflater.from(p0.context).inflate(layoutId, p0, false))
}

class BaseViewHolder(private val view: View) : RecyclerView.ViewHolder(view)

使用起來就很方便了,只需要重寫setData方法,如下:

    fun initView(){
        ...
        rv.adapter = MainAdapter(arrayListOf())
    }

    class MainAdapter(list: MutableList<String>) : BaseAdapterOneType3<String>(list, R.layout.item_pop_text) {
        override fun setData(v: View, b: String, i: Int, h: BaseViewHolder) {
            //利用Kotlin的特性,直接使用id來查找控件
            v.tv1.text = b
        }
    }

這樣封裝後用起來就方便的多了

等等,你以爲這樣就結束了嗎?看性能!

我們來看一下MainAdapter編譯後的字節碼做了什麼

ps:通過Koltin自帶的插件,然後點擊Decompile按鈕來還原成java代碼

如下:

首先構造和下面的泛型轉換的setData方法不用看,檢查Null的也不用看,主要看下面兩行

TextView var10000 = (TextView)v.findViewById(id.tv1);
var10000.setText((CharSequence)b);

通過上面的代碼會發現,每次走setData(onBindViewHolder)都會進行findViewById,如果是一個經常滾動的列表,則會頻繁的調用setData方法,則沒有用到RecyclerView.ViewHolder,效率變低

中間我想過,參考Activity的方式在公用的ViewHolder裏維護一個HashMap<Int,View>,並提供一個get方法,但是這樣跟BaseQuickAdapter的寫法就一樣了,比較繁瑣,pass

後來又想,通過by委託給一個類,讓其存儲鍵值對和泛型信息,但是後來發現並不行,直到有一次靈光一現!

第三次封裝,既少寫代碼又性能ok

abstract class BaseLtAdapterOneType<T>(var list: MutableList<T>, @LayoutRes private val itemLayoutId: Int) : RecyclerView.Adapter<BaseLtViewHolder>() {
    abstract fun setData(v: BaseLtViewHolder.ViewFind, b: T, i: Int, h: BaseLtViewHolder)

    override fun onBindViewHolder(holder: BaseLtViewHolder, position: Int) = setData(holder.viewFind, list[position], position, holder)

    override fun getItemCount() = list.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = BaseLtViewHolder(parent.inflate(itemLayoutId))
}

/**
 * 使用方便的ViewHolder
 */
class BaseLtViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
    val viewFind: ViewFind = ViewFind().apply { this.setView([email protected]) }

    /**
     * 使用kt的框架來快捷查找view,並且帶有緩存
     */
    class ViewFind : Fragment() {
        private lateinit var mView: View

        fun setView(view: View) {
            this.mView = view
        }

        override fun getView(): View? {
            return mView
        }
    }
}

實現思路,有一次我想,既然Fragment中能用Kotlin的框架直接來findViewById,那能不能找找是從哪個方法獲取父View的,然後發現原來是getView這個方法,而且還可以進行復寫,於是就給RecyclerView.ViewHolder的itemView加了一層Fragment,然後在setData中用Fragment來查找View,這樣既寫着方便,並且在Fragment中還有緩存(Kotlin框架的實現),簡直完美

使用方法如下:

    fun initView(){
        ...
        rv.adapter = MainAdapter(arrayListOf())
    }

    class MainAdapter(list: MutableList<String>) : BaseLtAdapterOneType<String>(list, R.layout.item_pop_text) {
        override fun setData(v: BaseLtViewHolder.ViewFind, b: String, i: Int, h: BaseLtViewHolder) {
            //在這裏直接通過Fragment來查找View,並設置屬性
            v.tv1.text = b
        }
    }

並且查看編譯後的代碼確實有緩存:

利用IDEA的Live Templates來快捷生成代碼

有的同學說,還是很多樣板代碼,不想寫怎麼辦,emmm...有的辦,使用Live Templates

打開設置,根據圖的步驟添加

起一個名字,然後說明用途,並粘貼進入以下代碼

class $className$(list: MutableList<$T$>) : BaseLtAdapterOneType<$T$>(list, R.layout.$next$) {
    override fun setData(v: BaseLtViewHolder.ViewFind, b: $T$, i: Int, h: BaseLtViewHolder) {
        $code$
    }
}

按照下面圖示勾上Kotlin

然後點擊右邊的Edit variables按鈕,設置成如下圖的樣子

然後新建一個Kotlin File,在空白處輸入badapter,就會自動生成實現類,填入泛型和layoutId後就可以開心的用了

結語

emmm,好了,封裝結束,小夥伴們可以直接複製最後一種封裝方式,然後快樂的用RecyclerView編碼了

當然,如果用的是別人封裝過的adapter,也可以使用該方式進行二次封裝,可以書寫更方便

 

RecyclerView.Adapter還能在簡化或優化性能嗎?其實還有存貨,不過等下次有時間了在寫吧 \滑稽

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章