Kotlin 封装RecyclerView Adapter

Kotlin越来越流行,在Google的推动下发展的很迅猛,现在的项目大多使用上了Kotlin,其简练的语法糖确实能减少不少代码。

Adapter的封装GitHub上有很多了,但大多数封装的太好了,是的,使用太简单了,使用简单、封装力度大就导致灵活性和代码复杂性上升,谁用谁知道,当然也有封装简单的。

这里我借助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
        }
    }
}

子类的实现和RecyclerView的扩展

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,所有核心代码,没有了,也不打算发布rar,要用的直接clone下来引入项目,这是最好的方式,因为不复杂,要改随时可以改。

看上面的多类型的使用,可以发现它是支持普通Layout和DataBinding Layout的,这也是本库的一个特色,不需要多余的处理。

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,对应处理就可以了,Holder处理方式也是可以处理DataBinding Layout的,要知晓。

这里提下,可能有人会问干嘛不直接用Kotlin的Layout View 查找方法???
那样代码看起来是简单,但是现在的Studio 对这个的支持不是很好,经常报红,程序员看到红会烦躁啊!!如果还是喜欢的话实现也很简单,改成View的扩展返回就可以了,可以自己动手试下哦。

因为这里只是对不变的部分进行了封装,没有很多华丽丽的添加头部、脚部啥的功能,点击事件倒是内置了一种,当然点击事件还可以用ItemTouchHelper实现,都是可以的。

这样每次就不用写一大串的Adaper了,是不是可以开心地泡壶茶,吹口气了。

别的库都可以Item复用的,你的可以吗?
嗯嗯、、?可以的
比如

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

        }

再比如

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

是不是一样可以的 只要定义到一个地方 然后设置进去就可以了,复用也是难不倒它的。只能说Kotlin语法大法好。
好了,这个库就介绍到这里了,谢谢大家。
代码地址

参考链接
灵感来自下面这位大神,但是我基本重写了
https://github.com/armcha/Kadapter

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,456评论 5 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,370评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,337评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,583评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,596评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,572评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,936评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,595评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,850评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,601评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,685评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,371评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,951评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,934评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,167评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,636评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,411评论 2 342

推荐阅读更多精彩内容