Kotlin实战(二): 实现RecyclerView多种Item布局

前言

RecyclerView出来很久了,可以说一出来就将ListView给比下去了,当然,Recyclerview有它的好,ListView的好,并不是说一定要用Recyclerview,最适用自己项目的才是最好的。

在这里我们将用Kotlin来实现RecyclerView的多种item布局,和单个item布局,同时写一个通用的Adapter。

使用

先将写完的代码的使用方式展示一下:

一种item布局

class SingleItemAdapter(mContext: Context, mDatas: List<TestBean>)
    : DelegateItemAdapter<TestBean>(mContext, mDatas) {
    init {
        addItemViewDelegate(SingleItemDelegate())
    }
}

效果图

SingleItem

多种item布局

class MultiItemAdapter(mContext: Context, mDatas: List<TestBean>) 
    : DelegateItemAdapter<TestBean>(mContext, mDatas) {
    init {
        addItemViewDelegate(LeftDelegate())
        addItemViewDelegate(CenterDelegate())
        addItemViewDelegate(RightDelegate())
    }
}

效果图

MultiItem

梳理

总体流程是这样的,首先创建itemView,在里面设置layoutId和数据处理,然后创建一个类继承DelegateItemAdapter,并在主构造方法里面添加不同的itemView,然后Adapter通过DelegateManager类来管理对应的itemView进行操作。

ItemView

我们的itemView是实现DelegateType接口,然后在里面设置相对应的layoutId,对数据进行操作处理:

class SingleItemDelegate : DelegateType<TestBean> {
    override val itemViewLayoutId: Int
        get() = R.layout.item_left

    override fun isItemViewType(item: TestBean, position: Int): Boolean = true

    override fun convert(context: Context, holder: ViewHolder, item: TestBean, position: Int) {
        with(holder.itemView) {
            item_left_text.text = item.text
            setOnClickListener {
                context.toast("SingleItemDelegate")
            }
        }
    }
}

ViewHolder

在使用Recyclerview的时候,必须有ViewHolder,通常情况下,我们需要写一个通用的ViewHolder,但是在Kotlin中的话,就不需要那样写,因为Kotlin可以直接将布局的id来当成变量使用,所以我们需要写一个ViewHolder类来继承Recyclerview里面的Viewholder类。

使用id当变量的话,要在app下面的build.gradle里面加个plugin: 'kotlin-android-extensions'

extensions
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {

    companion object {
        /**
         * 创建ViewHolder
         *
         * @param itemView itemView
         * @return ViewHolder
         */
        fun createViewHolder(itemView: View): ViewHolder {
            val holder = ViewHolder(itemView)
            return holder
        }

        /**
         * 创建ViewHolder
         *
         * @param context Context
         * @param parent ViewGroup
         * @param layoutId layoutId
         * @return ViewHolder
         */
        fun createViewHolder(context: Context,
                             parent: ViewGroup, layoutId: Int): ViewHolder {
            val itemView = LayoutInflater.from(context).inflate(layoutId, parent,
                    false)
            val holder = ViewHolder(itemView)
            return holder
        }
    }

}

这样子ViewHolder就搞定了,不需要在写那些设置text、image、绑定事件相关的代码了。

Adapter

那么我们改如何区分多种itemView呢,在这里我们引用了一个接口:

/**
 * itemView属性
 */
interface DelegateType<in T> { // 由于T只是作为参数,所以T是contravariant逆变,加in
    /**
     * 获取layoutId
     */
    val itemViewLayoutId: Int

    /**
     * 判断类型
     *
     * @param item data数据
     * @param position 当前position
     * @return true显示数据
     */
    fun isItemViewType(item: T, position: Int): Boolean

    /**
     * 显示数据
     *
     * @param context Context
     * @param holder ViewHolder
     * @param item data数据
     * @param position 当前position
     */
    fun convert(context: Context, holder: ViewHolder, item: T, position: Int)
}

根据layoutId来创建对应的ViewHolder,并且通过isItemViewType方法来匹配是否是当前itemView类型,然后通过convert来显示数据。

Adapter是继承Recyclerview里面的Adapter,传入ViewHolder,传入泛型数据集,那么我们可以通过传入的数据集里面的类型来判断不同的itemView,在这里需要重写几个方法:

getItemViewType方法
根据数据来返回不同类型:

/**
 * 获取itemView类型
 *
 * @param position 当前position
 */
override fun getItemViewType(position: Int): Int = if (!useItemViewDelegateManager())
    super.getItemViewType(position)
else
    mDelegateManager.getItemViewType(mDatas[position], position)

onCreateViewHolder方法
根据mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId返回的layoutId来生成对应的ViewHolder

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder.createViewHolder(mContext, parent, mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId)

onBindViewHolder方法
用于数据处理和显示:

override fun onBindViewHolder(holder: ViewHolder, position: Int) = convert(holder, mDatas[position])

上面的mDelegateManager是管理itemView的类,可以添加删除itemView,并且显示数据:

/**
* 获取itemView的类型
*
* @param item data数据
* @param position 当前position
* @return Int
*/
fun getItemViewType(item: T, position: Int): Int {
    (0.. delegateCount - 1)
            .filter { mDelegates.valueAt(it).isItemViewType(item, position) }
            .forEach { return mDelegates.keyAt(it) }
    throw IllegalArgumentException(
            "No ItemViewDelegate added that matches position = $position in data = $item source");
}

/**
* 显示数据
*
* @param context Context
* @param holder ViewHolder
* @param item data数据
* @param position 当前position
*/
fun convert(context: Context, holder: ViewHolder, item: T, position: Int) {
    for (i in 0..delegateCount - 1) {
        val delegate = mDelegates.valueAt(i)
        if (delegate.isItemViewType(item, position)) {
            delegate.convert(context, holder, item, position);
            return@convert
        }
    }
    throw IllegalArgumentException(
            "No ItemViewDelegateManager added that matches position= $position in data = $item source")
}

/**
* 添加itemView
*
* @param delegate itemView
* @throws IllegalArgumentException 已存在不能再添加
* @return DelegateManager
*/
fun addDelegate(delegate: DelegateType<T>): DelegateManager<T> {
    for (i in 0..delegateCount - 1) {
        if (mDelegates.valueAt(i).itemViewLayoutId == delegate.itemViewLayoutId) {
            throw IllegalArgumentException("An ItemViewDelegate is already registered for the delegate = $delegate.")
        }
    }
    mDelegates.put(mDelegates.size(), delegate)
    return this
}

/**
* 添加itemView
*
* @param viewType 代表itemView的下标
* @param delegate itemView
* @throws IllegalArgumentException 已存在不能再添加
* @return DelegateManager
*/
fun addDelegate(viewType: Int, delegate: DelegateType<T>): DelegateManager<T> {
    if (mDelegates.get(viewType) != null) {
        throw IllegalArgumentException("An ItemViewDelegate is already registered for the viewType = $viewType. Already registered ItemViewDelegate is ${mDelegates.get(viewType)}")
    }
    mDelegates.put(viewType, delegate)
    return this
}

/**
 * 获取itemView
 *  
 * @param viewType 代表itemView的下标
 * @return DelegateType
 */
fun getItemViewDelegate(viewType: Int): DelegateType<T> = mDelegates.get(viewType)

/**
 * 获取itemView的LayoutId
 *
 * @param viewType 代表itemView的下标
 * @return Int
*/
fun getItemViewLayoutId(viewType: Int): Int = getItemViewDelegate(viewType).itemViewLayoutId

/**
* 获取itemView的类型
*
* @param delegate itemView
* @return Int
*/
fun getItemViewType(delegate: DelegateType<T>): Int = mDelegates.indexOfValue(delegate)

通过使用addDelegate方法添加itemView,然后通过getItemViewType方法来获取itemView的类型,最后通过convert方法来进行数据操作和显示,这样子我们的Adapter就写出来了。

总结

每次写Recyclerview的时候都要重复写这些Adapter,ViewHolder的代码,这里将它写成通用的,可以省去很多的时间。

注意:项目是在3.0版本的Android Studio上运行。

源码:Github,欢迎star。

参考:为RecyclerView打造通用Adapter 让RecyclerView更加好用

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念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

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 171,398评论 25 707
  • 这篇文章分三个部分,简单跟大家讲一下 RecyclerView 的常用方法与奇葩用法;工作原理与ListView比...
    LucasAdam阅读 4,374评论 0 27
  • 今天参加了当地跑团组织的长跑拉练,30公里左右,最后跑下来是31公里,用时3小时30分钟,第一次跑这么远,路上和几...
    处处1阅读 3,056评论 4 4
  • 从小我就被大家认为是一个安静的孩子,别的小孩撒丫子蹦跳的时候,我喜欢在阳光的世界里,闭着眼,眼前便是一个全新的世界...
    刘老三的小书屋阅读 289评论 0 2
  • 今天奥斯卡最佳男主角得主是《海边的漫彻斯特》男主。实至名归的得主,刚看完被感动的,眼泪花花的流。 刚开始看时lee...
    拾贰未熟阅读 562评论 0 0