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閱讀 230,106評論 6 542
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,441評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,211評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,736評論 1 317
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,475評論 6 412
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,834評論 1 328
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,829評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,009評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,559評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,306評論 3 358
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,516評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,038評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,728評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,132評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,443評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,249評論 3 399
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,484評論 2 379

推薦閱讀更多精彩內容

  • Android 自定義View的各種姿勢1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 172,728評論 25 708
  • 這篇文章分三個部分,簡單跟大家講一下 RecyclerView 的常用方法與奇葩用法;工作原理與ListView比...
    LucasAdam閱讀 4,410評論 0 27
  • 今天參加了當地跑團組織的長跑拉練,30公里左右,最后跑下來是31公里,用時3小時30分鐘,第一次跑這么遠,路上和幾...
    處處1閱讀 3,117評論 4 4
  • 從小我就被大家認為是一個安靜的孩子,別的小孩撒丫子蹦跳的時候,我喜歡在陽光的世界里,閉著眼,眼前便是一個全新的世界...
    劉老三的小書屋閱讀 307評論 0 2
  • 今天奧斯卡最佳男主角得主是《海邊的漫徹斯特》男主。實至名歸的得主,剛看完被感動的,眼淚花花的流。 剛開始看時lee...
    拾貳未熟閱讀 579評論 0 0