Kotlin 封裝RecyclerView Adapter

Kotlin越來越流行痹仙,在Google的推動下發(fā)展的很迅猛的畴,現(xiàn)在的項(xiàng)目大多使用上了Kotlin灰嫉,其簡練的語法糖確實(shí)能減少不少代碼扼倘。

Adapter的封裝GitHub上有很多了状蜗,但大多數(shù)封裝的太好了需五,是的,使用太簡單了诗舰,使用簡單警儒、封裝力度大就導(dǎo)致靈活性和代碼復(fù)雜性上升,誰用誰知道眶根,當(dāng)然也有封裝簡單的蜀铲。

這里我借助Kotlin的簡單語法再次操刀封裝了一下。

先看下使用

單類型的使用

val adapter=recyclerView.setUp(users, R.layout.item_layout, { holder, item ->
            var binding = DataBindingUtil.getBinding<ItemLayoutBinding>(holder.itemView)
            binding.nameText.text = item.name
            ...
        })

多類型的使用

recyclerView.setUP(users,
                listItems = *arrayOf(
                        ListItem(R.layout.item_layout, { holder, item ->
                            var binding = DataBindingUtil.getBinding<ItemLayoutBinding>(holder.itemView)
                            binding?.nameText?.text = item.name
                           ...
                        }, {
                            Snackbar.make(window.decorView, it.name, Snackbar.LENGTH_SHORT).show()
                        }),
                        ListItem(R.layout.item_layout2, { holder, item ->
                            val nameText: TextView = holder.getView(R.id.nameText)
                            nameText.text = item.name
                           ...
                        }, {

                        })
                ))

使用就是如此簡單属百,再來看下代碼是不是過度封裝

Adapter的基類

abstract class AbstractAdapter<ITEM> constructor(protected var itemList: List<ITEM>)
    : RecyclerView.Adapter<AbstractAdapter.Holder>() {

    override fun getItemCount() = itemList.size

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): Holder {
        val view = createItemView(parent, viewType)
        val viewHolder = Holder(view)
        val itemView = viewHolder.itemView
        itemView.setOnClickListener {
            val adapterPosition = viewHolder.adapterPosition
            if (adapterPosition != RecyclerView.NO_POSITION) {
                onItemClick(itemView, adapterPosition)
            }
        }
        return viewHolder
    }


    fun update(items: List<ITEM>) {
        updateAdapterWithDiffResult(calculateDiff(items))
    }

    private fun updateAdapterWithDiffResult(result: DiffUtil.DiffResult) {
        result.dispatchUpdatesTo(this)
    }

    private fun calculateDiff(newItems: List<ITEM>) =
            DiffUtil.calculateDiff(DiffUtilCallback(itemList, newItems))

    fun add(item: ITEM) {
        itemList.toMutableList().add(item)
        notifyItemInserted(itemList.size)
    }

    fun remove(position: Int) {
        itemList.toMutableList().removeAt(position)
        notifyItemRemoved(position)
    }

    final override fun onViewRecycled(holder: Holder) {
        super.onViewRecycled(holder)
        onViewRecycled(holder.itemView)
    }

    protected open fun onViewRecycled(itemView: View) {
    }

    protected open fun onItemClick(itemView: View, position: Int) {
    }

    protected abstract fun createItemView(parent: ViewGroup, viewType: Int): View

    class Holder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val views = SparseArray<View>()

        fun <T : View> getView(viewId: Int): T {
            var view = views[viewId]
            if (view == null) {
                view = itemView.findViewById(viewId)
                views.put(viewId, view)
            }
            return view as T
        }
    }
}

子類的實(shí)現(xiàn)和RecyclerView的擴(kuò)展

class SingleAdapter<ITEM>(items: List<ITEM>,
                          private val layoutResId: Int,
                          private val bindHolder: (Holder, ITEM) -> Unit)
    : AbstractAdapter<ITEM>(items) {

    private var itemClick: (ITEM) -> Unit = {}

    constructor(items: List<ITEM>,
                layoutResId: Int,
                bindHolder: (Holder, ITEM) -> Unit,
                itemClick: (ITEM) -> Unit = {}) : this(items, layoutResId, bindHolder) {
        this.itemClick = itemClick
    }

    override fun createItemView(parent: ViewGroup, viewType: Int): View {
        var view = parent inflate layoutResId
        if (view.tag?.toString()?.contains("layout/") == true) {
            DataBindingUtil.bind<ViewDataBinding>(view)
        }
        return view
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        bindHolder(holder, itemList[position])
    }

    override fun onItemClick(itemView: View, position: Int) {
        itemClick(itemList[position])
    }
}


class MultiAdapter<ITEM : ListItemI>(private val items: List<ITEM>,
                                     private val bindHolder: (Holder, ITEM) -> Unit)
    : AbstractAdapter<ITEM>(items) {

    private var itemClick: (ITEM) -> Unit = {}
    private lateinit var listItems: Array<out ListItem<ITEM>>

    constructor(items: List<ITEM>,
                listItems: Array<out ListItem<ITEM>>,
                bindHolder: (Holder, ITEM) -> Unit,
                itemClick: (ITEM) -> Unit = {}) : this(items, bindHolder) {
        this.itemClick = itemClick
        this.listItems = listItems
    }

    override fun createItemView(parent: ViewGroup, viewType: Int): View {
        var view = parent inflate getLayoutId(viewType)
        if (view.tag?.toString()?.contains("layout/") == true) {
            DataBindingUtil.bind<ViewDataBinding>(view)
        }
        return view
    }

    private fun getLayoutId(viewType: Int): Int {
        var layoutId = -1
        listItems.forEach {
            if (it.layoutResId == viewType) {
                layoutId = it.layoutResId
                return@forEach
            }
        }
        return layoutId
    }

    override fun getItemViewType(position: Int): Int {
        return items[position].getType()
    }

    override fun onBindViewHolder(holder: Holder, position: Int) {
        bindHolder(holder, itemList[position])
    }

    override fun onItemClick(itemView: View, position: Int) {
        itemClick(itemList[position])
    }
}


fun <ITEM> RecyclerView.setUp(items: List<ITEM>,
                              layoutResId: Int,
                              bindHolder: (AbstractAdapter.Holder, ITEM) -> Unit,
                              itemClick: (ITEM) -> Unit = {},
                              manager: RecyclerView.LayoutManager = LinearLayoutManager(this.context)): AbstractAdapter<ITEM> {
    val singleAdapter by lazy {
        SingleAdapter(items, layoutResId, { holder, item ->
            bindHolder(holder, item)
        }, {
            itemClick(it)
        })
    }
    layoutManager = manager
    adapter = singleAdapter
    return singleAdapter
}


fun <ITEM : ListItemI> RecyclerView.setUP(items: List<ITEM>,
                                          manager: RecyclerView.LayoutManager = LinearLayoutManager(this.context),
                                          vararg listItems: ListItem<ITEM>): AbstractAdapter<ITEM> {

    val multiAdapter by lazy {
        MultiAdapter(items, listItems, { holder, item ->
            var listItem: ListItem<ITEM>? = getListItem(listItems, item)
            listItem?.bindHolder?.invoke(holder, item)
        }, { item ->
            var listItem: ListItem<ITEM>? = getListItem(listItems, item)
            listItem?.itemClick?.invoke(item)
        })
    }
    layoutManager = manager
    adapter = multiAdapter
    return multiAdapter
}

private fun <ITEM : ListItemI> getListItem(listItems: Array<out ListItem<ITEM>>, item: ITEM): ListItem<ITEM>? {
    var listItem: ListItem<ITEM>? = null
    listItems.forEach {
        if (it.layoutResId == item.getType()) {
            listItem = it
            return@forEach
        }
    }
    return listItem
}

class ListItem<ITEM>(val layoutResId: Int,
                     val bindHolder: (holder: AbstractAdapter.Holder, item: ITEM) -> Unit,
                     val itemClick: (item: ITEM) -> Unit = {})


interface ListItemI {
    fun getType(): Int
}

ok记劝,所有核心代碼,沒有了族扰,也不打算發(fā)布rar厌丑,要用的直接clone下來引入項(xiàng)目,這是最好的方式渔呵,因?yàn)椴粡?fù)雜怒竿,要改隨時(shí)可以改。

看上面的多類型的使用扩氢,可以發(fā)現(xiàn)它是支持普通Layout和DataBinding Layout的耕驰,這也是本庫的一個(gè)特色,不需要多余的處理录豺。

1.普通的Layout 這樣處理

ListItem(R.layout.item_layout2, { holder, item ->
                            val nameText: TextView = holder.getView(R.id.nameText)
                            nameText.text = item.name
                        }

通過Holder來操作View朦肘,里面有做緩存的饭弓。

  1. DataBinding Layout
ListItem(R.layout.item_layout, { holder, item ->
                            var binding = DataBindingUtil.getBinding<ItemLayoutBinding>(holder.itemView)
                            binding.nameText.text = item.name
                        }

是不是只要自己知道是哪中Layout,對應(yīng)處理就可以了媒抠,Holder處理方式也是可以處理DataBinding Layout的昧甘,要知曉封孙。

這里提下,可能有人會問干嘛不直接用Kotlin的Layout View 查找方法摆霉?际乘?或渤?
那樣代碼看起來是簡單忿危,但是現(xiàn)在的Studio 對這個(gè)的支持不是很好镊靴,經(jīng)常報(bào)紅,程序員看到紅會煩躁帮鄙!!窍株!如果還是喜歡的話實(shí)現(xiàn)也很簡單民轴,改成View的擴(kuò)展返回就可以了,可以自己動手試下哦球订。

因?yàn)檫@里只是對不變的部分進(jìn)行了封裝后裸,沒有很多華麗麗的添加頭部、腳部啥的功能冒滩,點(diǎn)擊事件倒是內(nèi)置了一種微驶,當(dāng)然點(diǎn)擊事件還可以用ItemTouchHelper實(shí)現(xiàn),都是可以的开睡。

這樣每次就不用寫一大串的Adaper了因苹,是不是可以開心地泡壺茶,吹口氣了篇恒。

別的庫都可以Item復(fù)用的扶檐,你的可以嗎?
嗯嗯胁艰、款筑、?可以的
比如

val item: (AbstractAdapter.Holder, User) -> Unit = { holder, user ->

        }

再比如

ListItem(R.layout.item_layout, { holder, item ->
                            var binding = DataBindingUtil.getBinding<ItemLayoutBinding>(holder.itemView)
                        }, {//點(diǎn)擊事件
                            Snackbar.make(window.decorView, it.name, Snackbar.LENGTH_SHORT).show()
                        })

是不是一樣可以的 只要定義到一個(gè)地方 然后設(shè)置進(jìn)去就可以了腾么,復(fù)用也是難不倒它的奈梳。只能說Kotlin語法大法好。
好了解虱,這個(gè)庫就介紹到這里了攘须,謝謝大家。
代碼地址

參考鏈接
靈感來自下面這位大神饭寺,但是我基本重寫了
https://github.com/armcha/Kadapter

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末阻课,一起剝皮案震驚了整個(gè)濱河市叫挟,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌限煞,老刑警劉巖抹恳,帶你破解...
    沈念sama閱讀 218,122評論 6 505
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異署驻,居然都是意外死亡奋献,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,070評論 3 395
  • 文/潘曉璐 我一進(jìn)店門旺上,熙熙樓的掌柜王于貴愁眉苦臉地迎上來瓶蚂,“玉大人,你說我怎么就攤上這事宣吱∏哉猓” “怎么了?”我有些...
    開封第一講書人閱讀 164,491評論 0 354
  • 文/不壞的土叔 我叫張陵征候,是天一觀的道長杭攻。 經(jīng)常有香客問我,道長疤坝,這世上最難降的妖魔是什么兆解? 我笑而不...
    開封第一講書人閱讀 58,636評論 1 293
  • 正文 為了忘掉前任,我火速辦了婚禮跑揉,結(jié)果婚禮上锅睛,老公的妹妹穿的比我還像新娘。我一直安慰自己历谍,他們只是感情好现拒,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,676評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著望侈,像睡著了一般具练。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上甜无,一...
    開封第一講書人閱讀 51,541評論 1 305
  • 那天扛点,我揣著相機(jī)與錄音,去河邊找鬼岂丘。 笑死陵究,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的奥帘。 我是一名探鬼主播铜邮,決...
    沈念sama閱讀 40,292評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了松蒜?” 一聲冷哼從身側(cè)響起扔茅,我...
    開封第一講書人閱讀 39,211評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎秸苗,沒想到半個(gè)月后召娜,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,655評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡惊楼,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,846評論 3 336
  • 正文 我和宋清朗相戀三年玖瘸,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片檀咙。...
    茶點(diǎn)故事閱讀 39,965評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡雅倒,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出弧可,到底是詐尸還是另有隱情蔑匣,我是刑警寧澤,帶...
    沈念sama閱讀 35,684評論 5 347
  • 正文 年R本政府宣布棕诵,位于F島的核電站殖演,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏年鸳。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,295評論 3 329
  • 文/蒙蒙 一丸相、第九天 我趴在偏房一處隱蔽的房頂上張望搔确。 院中可真熱鬧,春花似錦灭忠、人聲如沸膳算。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,894評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽涕蜂。三九已至,卻和暖如春映琳,著一層夾襖步出監(jiān)牢的瞬間机隙,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,012評論 1 269
  • 我被黑心中介騙來泰國打工萨西, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留有鹿,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,126評論 3 370
  • 正文 我出身青樓谎脯,卻偏偏與公主長得像葱跋,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,914評論 2 355

推薦閱讀更多精彩內(nèi)容