Android app 優(yōu)雅展示復(fù)雜列表

問題描述

Android 應(yīng)用列表展示功能非常常見经柴。特別是電商 新聞 這類的應(yīng)用猴娩。一般主頁就是一個復(fù)雜的列表。復(fù)雜列表一般包含多種展示樣式饿肺,每一種樣式或多種樣式 對應(yīng)一種數(shù)據(jù)結(jié)構(gòu)蒋困,每種樣式包含N個N>=0 item。

示例效果圖

為了簡化問題敬辣,每種顏色對應(yīng)一種展示效果雪标。

實現(xiàn)思路

經(jīng)過不斷的重構(gòu)迭代,實現(xiàn)了一個非常優(yōu)雅的 RecyclerView Adapter溉跃〈迮伲可以展示各種復(fù)雜列表,而且代碼簡潔易懂撰茎,易擴展嵌牺,代碼高度復(fù)用。
列表中每一個樣式的核心邏輯有兩個龄糊,一個是樣式對應(yīng)的布局逆粹,也就是展示效果,另一個是布局中控件如何和數(shù)據(jù)關(guān)聯(lián)炫惩。這兩個功能對應(yīng)接口ViewTypeDelegateAdapter 中 onCreateViewHolder和onBindViewHolder僻弹。

 interface ViewTypeDelegateAdapter {
        fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder
        fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?)
    }

總的實現(xiàn)思路是基于委托模式,每一種展示樣式委托給一個輕量級的DelegateAdapter他嚷,實現(xiàn)展示效果蹋绽。這個類非常簡單芭毙,只需要實現(xiàn)接口中的兩個函數(shù)。

核心功能由MultiTypeAdapter 實現(xiàn)蟋字,它負(fù)責(zé)根據(jù)每種展示樣式委托給對應(yīng)的代理稿蹲。還有RecyclerView.Adapter
中的一些其他邏輯

為了實現(xiàn)委托定義了一個泛型數(shù)據(jù)結(jié)構(gòu)

    data class CommonAdapterItem(val data: Any, val type: Int, var spanSize: Int = 1)

把要展示的數(shù)據(jù)使用CommonAdapterItem 包裝一下,設(shè)置對應(yīng)的展示樣式和GridLayoutManager 對應(yīng)的spanCount鹊奖。然后把數(shù)據(jù)塞給 MultiTypeAdapter 苛聘,由它委托給對應(yīng)的實現(xiàn)類。

核心代碼和demo

代碼已經(jīng)在多個項目中使用過忠聚,經(jīng)過多次優(yōu)化已經(jīng)非常的簡單高效设哗。為了方便查看,把所有的功能放在了一個類中两蟀,即使這樣也不到200行代碼

const val RED = 1
const val GREEN = 2
const val BLUE = 3
const val YELLOW = 4
const val PURPLE = 5


class MultiTypeAdapter : RecyclerView.Adapter<CommonViewHolder>() {

    interface ViewTypeDelegateAdapter {
        fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder
        fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?)
    }

    private val mContent: MutableList<CommonAdapterItem> = mutableListOf()

    private val mFactory = DelegateAdapterFactory()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder {
        return mFactory.getDelegateAdapter(viewType).onCreateViewHolder(parent, viewType)
    }

    override fun onBindViewHolder(holder: CommonViewHolder, position: Int) {
        val type = mContent[position].type
        mFactory.getDelegateAdapter(type)
            .onBindViewHolder(holder, position, mContent[position].data)
    }

    override fun getItemViewType(position: Int): Int = mContent[position].type

    override fun getItemCount(): Int = mContent.size

    fun addItems(
        items: Collection<Any>?,
        type: Int = RED,
        append: Boolean = false,
        spanSize: Int = 1
    ) {
        items?.let {
            if (!append) {
                mContent.clear()
            }
            mContent.addAll(transform(items, type, spanSize))
            notifyDataSetChanged()
        }
    }

//    fun setOnItemClick(callback: (position: Int, data: Any?, action: Int, extra: Any?) -> Unit) {
//        mFactory.onItemClick = callback
//    }
//
//    fun setOnItemClick(callback: (position: Int, data: Any?, action: Int) -> Unit) {
//        mFactory.onItemClick = { position, data, action, _ ->
//            callback(position, data, action)
//        }
//    }

    fun getItemType(position: Int): Int = mContent[position].type

    fun clear() {
        mContent.clear()
        notifyDataSetChanged()
    }

    fun removeItem(position: Int) {
        mContent.removeAt(position)
        notifyItemRemoved(position)
    }

    fun changeItem(position: Int, item: Any?, type: Int, spanSize: Int = 1) {
        item?.let {
            if (position < mContent.size) {
                mContent[position] = transform(item, type, spanSize)
                notifyItemChanged(position)
            }
        }
    }

//    fun addItem(item: Collection<Any>?, type: Int = RED, append: Boolean = false) {
//        item?.let {
//            if (!append) {
//                mContent.clear()
//            }
//            mContent.add(CommonAdapterItem(item.toMutableList(), type))
//            notifyDataSetChanged()
//        }
//    }

    fun addItem(item: Any?, type: Int = RED, append: Boolean = false, spanSize: Int = 1) {
        item?.let {
            if (!append) {
                mContent.clear()
            }
            mContent.add(transform(item, type, spanSize))
            notifyDataSetChanged()
        }
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        super.onAttachedToRecyclerView(recyclerView)
        val manager = recyclerView.layoutManager
        if (manager is GridLayoutManager) {
            manager.spanSizeLookup = object : GridLayoutManager.SpanSizeLookup() {
                override fun getSpanSize(position: Int): Int {
                    return mContent[position].spanSize
                }
            }
        }
    }

    private fun transform(item: Any, type: Int, spanSize: Int = 1): CommonAdapterItem {
        return CommonAdapterItem(item, type, spanSize)
    }

    private fun transform(
        items: Collection<Any>,
        type: Int,
        spanSize: Int = 1
    ): List<CommonAdapterItem> {
        return items.map { CommonAdapterItem(it, type, spanSize) }
    }

    data class CommonAdapterItem(val data: Any, val type: Int, var spanSize: Int = 1)

    open class BaseDelegateAdapter(protected val layoutId: Int) : ViewTypeDelegateAdapter {

        override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CommonViewHolder {
            val view = LayoutInflater.from(parent.context).inflate(layoutId, parent, false)
            return CommonViewHolder(view)
        }

        override fun onBindViewHolder(holder: CommonViewHolder, position: Int, data: Any?) {}
    }

    class DelegateAdapterFactory {
        private val adapterCache = SparseArray<ViewTypeDelegateAdapter>() //提高性能

        fun getDelegateAdapter(type: Int): ViewTypeDelegateAdapter {
            var adapter = adapterCache[type]
            if (adapter == null) {
                adapter = when (type) {
                    RED, GREEN, BLUE, YELLOW, PURPLE -> object :
                        BaseDelegateAdapter(R.layout.viewholder_example) {
                        override fun onBindViewHolder(
                            holder: CommonViewHolder,
                            position: Int,
                            data: Any?
                        ) {
                            super.onBindViewHolder(holder, position, data)
                            if(data is String){
                                holder.get<View>(R.id.content).setBackgroundColor(Color.parseColor(data))
                            }
                        }
                    }
                    else -> {
                        BaseDelegateAdapter(android.R.layout.simple_list_item_1)
                    }
                }
                adapterCache.put(type, adapter)

            }
            return adapter
        }
    }
}

demo對應(yīng)的示例代碼

       val adapter = MultiTypeAdapter()
        recyclerView.layoutManager = GridLayoutManager(this,4)
        recyclerView.adapter = adapter
        adapter.addItem("#FF0000",RED, true,4)
        val greenList = MutableList(10){
            "#00FF00"
        }
        val blueList = MutableList(5){
            "#0000FF"
        }
        val yellowList = MutableList(5){
            "#FFFF00"
        }
        val purpleList = MutableList(5){
            "#AA66CC"
        }
        adapter.addItems(greenList, GREEN,true,1)
        adapter.addItems(blueList, BLUE,true,4)
        adapter.addItems(yellowList, YELLOW,true,2)
        adapter.addItems(purpleList, PURPLE, true,3)      
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末网梢,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子赂毯,更是在濱河造成了極大的恐慌战虏,老刑警劉巖,帶你破解...
    沈念sama閱讀 210,835評論 6 490
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件党涕,死亡現(xiàn)場離奇詭異烦感,居然都是意外死亡,警方通過查閱死者的電腦和手機膛堤,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 89,900評論 2 383
  • 文/潘曉璐 我一進店門手趣,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人肥荔,你說我怎么就攤上這事绿渣。” “怎么了燕耿?”我有些...
    開封第一講書人閱讀 156,481評論 0 345
  • 文/不壞的土叔 我叫張陵中符,是天一觀的道長。 經(jīng)常有香客問我缸棵,道長舟茶,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,303評論 1 282
  • 正文 為了忘掉前任堵第,我火速辦了婚禮,結(jié)果婚禮上隧出,老公的妹妹穿的比我還像新娘踏志。我一直安慰自己,他們只是感情好胀瞪,可當(dāng)我...
    茶點故事閱讀 65,375評論 5 384
  • 文/花漫 我一把揭開白布针余。 她就那樣靜靜地躺著饲鄙,像睡著了一般。 火紅的嫁衣襯著肌膚如雪圆雁。 梳的紋絲不亂的頭發(fā)上忍级,一...
    開封第一講書人閱讀 49,729評論 1 289
  • 那天,我揣著相機與錄音伪朽,去河邊找鬼轴咱。 笑死,一個胖子當(dāng)著我的面吹牛烈涮,可吹牛的內(nèi)容都是我干的朴肺。 我是一名探鬼主播,決...
    沈念sama閱讀 38,877評論 3 404
  • 文/蒼蘭香墨 我猛地睜開眼坚洽,長吁一口氣:“原來是場噩夢啊……” “哼戈稿!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起讶舰,我...
    開封第一講書人閱讀 37,633評論 0 266
  • 序言:老撾萬榮一對情侶失蹤鞍盗,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后跳昼,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體般甲,經(jīng)...
    沈念sama閱讀 44,088評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,443評論 2 326
  • 正文 我和宋清朗相戀三年庐舟,在試婚紗的時候發(fā)現(xiàn)自己被綠了欣除。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 38,563評論 1 339
  • 序言:一個原本活蹦亂跳的男人離奇死亡挪略,死狀恐怖历帚,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情杠娱,我是刑警寧澤挽牢,帶...
    沈念sama閱讀 34,251評論 4 328
  • 正文 年R本政府宣布,位于F島的核電站摊求,受9級特大地震影響禽拔,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜室叉,卻給世界環(huán)境...
    茶點故事閱讀 39,827評論 3 312
  • 文/蒙蒙 一睹栖、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧茧痕,春花似錦野来、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,712評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽豁辉。三九已至,卻和暖如春舀患,著一層夾襖步出監(jiān)牢的瞬間徽级,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 31,943評論 1 264
  • 我被黑心中介騙來泰國打工聊浅, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留餐抢,地道東北人。 一個月前我還...
    沈念sama閱讀 46,240評論 2 360
  • 正文 我出身青樓狗超,卻偏偏與公主長得像弹澎,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子努咐,可洞房花燭夜當(dāng)晚...
    茶點故事閱讀 43,435評論 2 348

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

  • RecyclerView控件從2014發(fā)布以來苦蒿,目前已經(jīng)普遍用于項目中,來承載各種列表內(nèi)容渗稍。同時佩迟,列表樣式也隨著項...
    Dragon_Boat閱讀 5,627評論 12 8
  • 內(nèi)容 抽屜菜單 ListView WebView SwitchButton 按鈕 點贊按鈕 進度條 TabLayo...
    8ba406212441閱讀 5,477評論 0 5
  • 懶得處理樣式了, 將就著看吧. 官網(wǎng)地址: https://developer.android.com/topic...
    Reddington_604e閱讀 1,649評論 0 1
  • 秋天的風(fēng)攜著微涼的雨 儀態(tài)萬千地走在豐腴的土地上 那些飽滿的收獲已經(jīng)悄然囤起 遍地落紅和滿天飛卷的黃葉 正在上演著...
    一彎虹閱讀 645評論 11 24
  • 雨天散步,和平時沒什么不同竿屹,只是多了一個移動的屋頂报强。彩色的。 大雨如瀑拱燃,小雨如幕秉溉。 想想白娘子,為了成就自己的愛情...
    爐子河南閱讀 521評論 0 1