Kotlin實(shí)戰(zhàn)(二): 實(shí)現(xiàn)RecyclerView多種Item布局

前言

RecyclerView出來很久了文判,可以說一出來就將ListView給比下去了苞七,當(dāng)然,Recyclerview有它的好浪藻,ListView的好,并不是說一定要用Recyclerview乾翔,最適用自己項(xiàng)目的才是最好的爱葵。

在這里我們將用Kotlin來實(shí)現(xiàn)RecyclerView的多種item布局,和單個(gè)item布局反浓,同時(shí)寫一個(gè)通用的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

梳理

總體流程是這樣的,首先創(chuàng)建itemView雷则,在里面設(shè)置layoutId和數(shù)據(jù)處理辆雾,然后創(chuàng)建一個(gè)類繼承DelegateItemAdapter,并在主構(gòu)造方法里面添加不同的itemView月劈,然后Adapter通過DelegateManager類來管理對應(yīng)的itemView進(jìn)行操作度迂。

ItemView

我們的itemView是實(shí)現(xiàn)DelegateType接口,然后在里面設(shè)置相對應(yīng)的layoutId猜揪,對數(shù)據(jù)進(jìn)行操作處理:

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的時(shí)候惭墓,必須有ViewHolder,通常情況下而姐,我們需要寫一個(gè)通用的ViewHolder腊凶,但是在Kotlin中的話,就不需要那樣寫拴念,因?yàn)镵otlin可以直接將布局的id來當(dāng)成變量使用钧萍,所以我們需要寫一個(gè)ViewHolder類來繼承Recyclerview里面的Viewholder類。

使用id當(dāng)變量的話丈莺,要在app下面的build.gradle里面加個(gè)plugin: 'kotlin-android-extensions'

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

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

        /**
         * 創(chuàng)建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就搞定了划煮,不需要在寫那些設(shè)置text、image缔俄、綁定事件相關(guān)的代碼了弛秋。

Adapter

那么我們改如何區(qū)分多種itemView呢器躏,在這里我們引用了一個(gè)接口:

/**
 * itemView屬性
 */
interface DelegateType<in T> { // 由于T只是作為參數(shù),所以T是contravariant逆變蟹略,加in
    /**
     * 獲取layoutId
     */
    val itemViewLayoutId: Int

    /**
     * 判斷類型
     *
     * @param item data數(shù)據(jù)
     * @param position 當(dāng)前position
     * @return true顯示數(shù)據(jù)
     */
    fun isItemViewType(item: T, position: Int): Boolean

    /**
     * 顯示數(shù)據(jù)
     *
     * @param context Context
     * @param holder ViewHolder
     * @param item data數(shù)據(jù)
     * @param position 當(dāng)前position
     */
    fun convert(context: Context, holder: ViewHolder, item: T, position: Int)
}

根據(jù)layoutId來創(chuàng)建對應(yīng)的ViewHolder登失,并且通過isItemViewType方法來匹配是否是當(dāng)前itemView類型,然后通過convert來顯示數(shù)據(jù)挖炬。

Adapter是繼承Recyclerview里面的Adapter揽浙,傳入ViewHolder,傳入泛型數(shù)據(jù)集意敛,那么我們可以通過傳入的數(shù)據(jù)集里面的類型來判斷不同的itemView馅巷,在這里需要重寫幾個(gè)方法:

getItemViewType方法
根據(jù)數(shù)據(jù)來返回不同類型:

/**
 * 獲取itemView類型
 *
 * @param position 當(dāng)前position
 */
override fun getItemViewType(position: Int): Int = if (!useItemViewDelegateManager())
    super.getItemViewType(position)
else
    mDelegateManager.getItemViewType(mDatas[position], position)

onCreateViewHolder方法
根據(jù)mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId返回的layoutId來生成對應(yīng)的ViewHolder

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

onBindViewHolder方法
用于數(shù)據(jù)處理和顯示:

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

上面的mDelegateManager是管理itemView的類,可以添加刪除itemView草姻,并且顯示數(shù)據(jù):

/**
* 獲取itemView的類型
*
* @param item data數(shù)據(jù)
* @param position 當(dāng)前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");
}

/**
* 顯示數(shù)據(jù)
*
* @param context Context
* @param holder ViewHolder
* @param item data數(shù)據(jù)
* @param position 當(dāng)前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的下標(biāo)
* @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的下標(biāo)
 * @return DelegateType
 */
fun getItemViewDelegate(viewType: Int): DelegateType<T> = mDelegates.get(viewType)

/**
 * 獲取itemView的LayoutId
 *
 * @param viewType 代表itemView的下標(biāo)
 * @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方法來進(jìn)行數(shù)據(jù)操作和顯示撩独,這樣子我們的Adapter就寫出來了敞曹。

總結(jié)

每次寫Recyclerview的時(shí)候都要重復(fù)寫這些Adapter,ViewHolder的代碼综膀,這里將它寫成通用的澳迫,可以省去很多的時(shí)間。

注意:項(xiàng)目是在3.0版本的Android Studio上運(yùn)行剧劝。

源碼:Github橄登,歡迎star。

參考:為RecyclerView打造通用Adapter 讓RecyclerView更加好用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末担平,一起剝皮案震驚了整個(gè)濱河市示绊,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌暂论,老刑警劉巖面褐,帶你破解...
    沈念sama閱讀 217,084評論 6 503
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異取胎,居然都是意外死亡展哭,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,623評論 3 392
  • 文/潘曉璐 我一進(jìn)店門闻蛀,熙熙樓的掌柜王于貴愁眉苦臉地迎上來匪傍,“玉大人,你說我怎么就攤上這事觉痛∫酆猓” “怎么了?”我有些...
    開封第一講書人閱讀 163,450評論 0 353
  • 文/不壞的土叔 我叫張陵薪棒,是天一觀的道長手蝎。 經(jīng)常有香客問我榕莺,道長,這世上最難降的妖魔是什么棵介? 我笑而不...
    開封第一講書人閱讀 58,322評論 1 293
  • 正文 為了忘掉前任钉鸯,我火速辦了婚禮,結(jié)果婚禮上邮辽,老公的妹妹穿的比我還像新娘唠雕。我一直安慰自己,他們只是感情好吨述,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,370評論 6 390
  • 文/花漫 我一把揭開白布岩睁。 她就那樣靜靜地躺著,像睡著了一般揣云。 火紅的嫁衣襯著肌膚如雪笙僚。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,274評論 1 300
  • 那天灵再,我揣著相機(jī)與錄音,去河邊找鬼亿笤。 笑死翎迁,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的净薛。 我是一名探鬼主播汪榔,決...
    沈念sama閱讀 40,126評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼肃拜!你這毒婦竟也來了痴腌?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 38,980評論 0 275
  • 序言:老撾萬榮一對情侶失蹤燃领,失蹤者是張志新(化名)和其女友劉穎士聪,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體猛蔽,經(jīng)...
    沈念sama閱讀 45,414評論 1 313
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡剥悟,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,599評論 3 334
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了曼库。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片区岗。...
    茶點(diǎn)故事閱讀 39,773評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖毁枯,靈堂內(nèi)的尸體忽然破棺而出慈缔,到底是詐尸還是另有隱情,我是刑警寧澤种玛,帶...
    沈念sama閱讀 35,470評論 5 344
  • 正文 年R本政府宣布藐鹤,位于F島的核電站瓤檐,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏教藻。R本人自食惡果不足惜距帅,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,080評論 3 327
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望括堤。 院中可真熱鬧碌秸,春花似錦、人聲如沸悄窃。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,713評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽轧抗。三九已至恩敌,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間横媚,已是汗流浹背纠炮。 一陣腳步聲響...
    開封第一講書人閱讀 32,852評論 1 269
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留灯蝴,地道東北人恢口。 一個(gè)月前我還...
    沈念sama閱讀 47,865評論 2 370
  • 正文 我出身青樓,卻偏偏與公主長得像穷躁,于是被迫代替她去往敵國和親耕肩。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,689評論 2 354

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

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,095評論 25 707
  • 這篇文章分三個(gè)部分问潭,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法猿诸;工作原理與ListView比...
    LucasAdam閱讀 4,388評論 0 27
  • 今天參加了當(dāng)?shù)嘏軋F(tuán)組織的長跑拉練,30公里左右狡忙,最后跑下來是31公里梳虽,用時(shí)3小時(shí)30分鐘,第一次跑這么遠(yuǎn)灾茁,路上和幾...
    處處1閱讀 3,085評論 4 4
  • 從小我就被大家認(rèn)為是一個(gè)安靜的孩子怖辆,別的小孩撒丫子蹦跳的時(shí)候,我喜歡在陽光的世界里删顶,閉著眼竖螃,眼前便是一個(gè)全新的世界...
    劉老三的小書屋閱讀 295評論 0 2
  • 今天奧斯卡最佳男主角得主是《海邊的漫徹斯特》男主。實(shí)至名歸的得主逗余,剛看完被感動(dòng)的特咆,眼淚花花的流。 剛開始看時(shí)lee...
    拾貳未熟閱讀 570評論 0 0