重學RecyclerView Adapter封裝的深度思考和實現

背景

一轉眼,從一開始發佈文章說帶你們 封裝Adapter 直到如今,過去半個月了吧,先後又仔細閱讀了不少Adapter的框架源碼,對Adapter的變幻無窮算是有了深入的認識,真的是溫故而知新,藉着此次機會,我也將學到的這些優勢一一的列出來告訴你,跟我一塊兒從新認識Adapterandroid

值得一看的Adapter開源項目

這些開源項目都有哪些相同點?git

  • 這幾個項目最大的共同點是都對DataBinding作了擴展,看來DataBinding你們都很承認
  • 有6個項目都用到了Kotlin,最高達到了96%,看來咱們android小夥伴都愛學習,kotlin的普及率好高
  • 還有一個點都對MultiItemType作了良好的擴展,說明這確實是一個Adapter庫的標配
  • 有三個項目對Paging庫作了擴展,相信你們用它的機會愈來愈多
  • 頭尾佈局幾乎成了框架的標配
  • Kotlin DSL或多或少的支持,寫法上簡潔而優美
  • 大多數都作了比較好的分庫處理,按需依賴,不拖泥帶水

咱們如何作一個能夠稱之爲全而好的Adapter框架呢?

我抽取幾個關鍵詞來告訴你github

  • 適當分包按需依賴,提供良好的擴展性
  • kotlin支持 利用最近一個老鐵的話:工欲善其事必先利其器,kotlin絕壁是一個好利器
  • DSL擴展 簡潔的寫法,利於閱讀
  • DataBinding支持 不用質疑,有就對了
  • Paging擴展 在我看來Paging用了不少新的設計,值得咱們去學習
  • 頭尾佈局、空佈局、上拉加載,拖動,動畫 提供專門的包來擴展
  • DiffUtil擴展 好的工具不要忘記使用,總會有合適的應用場景
  • anko擴展 用了kotlin怎麼能不用一下anko layout,300%的佈局加載效率提升不香嗎?

設計這麼一個框架個人堅持是什麼?

你有沒有陷入過一個誤區?本身封裝了一個東西,特別的全,什麼都支持,一股腦的往裏面塞東西,這我的說我想這樣,你就改爲這樣,那我的想那樣,你又開始改?難道咱們作個東西就是爲了讓別人指揮嗎?答案確定是:不。那應該從哪幾個方面考慮呢?算法

  • 擴展性 對的,你必定要作到可擴展,要否則你就要面臨東改改,西改改的困境,如何作到可擴展呢?一條原則搞定:依賴倒轉原則,儘可能依賴於抽象,而不是依賴於實現,作到這一點其實還不夠
  • 可靠性 能夠理解爲,你作的東西不要常常變,特別是抽象的接口,在一開始咱們就要作到完整,有的人說,我怎麼肯能考慮那麼完美呢?那就要考慮一下,你是否是走到了一個誤區,接口的抽象在這樣一個成熟的框架中,應該很好肯定的,接口不要大而全,接口也要作到儘可能的小,起碼接口能夠多繼承啊,對吧。
  • 里氏替換原則: 任何基類能夠出現的地方,子類必定能夠出現,這個原則其實好多人都不太理解,其實說白一點,用現實的例子告訴你,你繼承了父親的不少基因,那你能改變父親的基因嗎?確定不能,可代碼裏是能夠重寫的(抽象方法不算哦),但這個原則就是告訴咱們避免重寫,一旦重寫,父類就沒有了意義,其實在工做中,有不少人喜歡往父類裏抽象不少東西,還特別喜歡在不一樣的子類中覆蓋後重寫,這些都是很差的習慣,你不該該只是老三用到了一個東西,就要給全部的人加上這個東西,我感受是個累贅,你以爲呢?咱們在設計框架的時候,必定要遵循這些好的設計原則。
  • 單一職責 一個類應該有且僅有一個職責,你還好意思封裝一個類,啥功能都有嗎?除非它叫Manager,但也只能管理一類東西,職責的單一到處可見,且要到處堅持。

講了這些廢話,你是否是不耐煩了,下面來點乾貨吧,看看Adapter的一些細節是如何封裝的緩存

Adapter 如何作到ItemViewType自動適配?咱們是如何封裝的?

看源碼前,我想說的是,你要明白一個原理,其實ItemViewType它影響的就是ViewHolder的複用邏輯,只要是同樣的ItemViewType,它就會觸發複用,因此說ItemViewType封裝的目的,實際上是緩存ViewHolder而後複用,那咱們如何作到自動處理加緩存呢?bash

因爲咱們要作到onCreateViewHolder的下移,而咱們抽象了ViewModel層來負責組織View和Model,那麼ViewHolder就成了咱們View的載體,抽象一個ViewHolderFactory讓ViewModel繼承,達到onCreateViewHolder的調用下移數據結構

typealias GenericViewHolderFactory = ViewHolderFactory<out RecyclerView.ViewHolder>

interface ViewHolderFactory<VH : RecyclerView.ViewHolder> {
    fun getViewHolder(parent: ViewGroup, layoutInflater: LayoutInflater): VH
}
複製代碼

再看兩處代碼,這是咱們實現的兩個函數,onCreateViewHolder回調的時候只給了viewType,那麼咱們就只能想辦法,用Map緩存一個起來上面的ViewHolderFactory,這樣就能夠根據viewType拿到對應的ViewHolderFactory,是否是就完美了?架構

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int)

override fun getItemViewType(position: Int)
複製代碼

緩存ViewHolderFactory代碼以下,第一步我先抽象一個ViewHolderFactoryCache接口,保證之後的擴展性,並默認實現一個DefaultViewHolderFactoryCache,提供註冊、獲取、檢查、清理的方法app

interface ViewHolderFactoryCache<VHF : GenericViewHolderFactory> {
    fun register(type: Int, item: VHF): Boolean
    operator fun get(type: Int): VHF
    fun contains(type: Int): Boolean
    fun clear()
}

class DefaultViewHolderFactoryCache<VHF : GenericViewHolderFactory> : ViewHolderFactoryCache<VHF> {
    private val typeInstances = SparseArray<VHF>()
    override fun register(type: Int, item: VHF): Boolean {
        if (typeInstances.indexOfKey(type) < 0) {
            typeInstances.put(type, item)
            return true
        }
        return false
    }

    override fun get(type: Int): VHF {
        return typeInstances.get(type)
    }

    override fun contains(type: Int) = typeInstances.indexOfKey(type) >= 0
    override fun clear() {
        typeInstances.clear()
    }
}
複製代碼

而後再去Adapter裏實現, 我選擇讓ViewModel實現ViewHolderFactory,緩存起來後,在onCreateViewHolder中再取出來而後賦值。框架

private val defaultViewHolderFactoryCache = DefaultViewHolderFactoryCache<ViewHolderFactory<VH>>()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val defaultViewHolder = defaultViewHolderFactoryCache[viewType].getViewHolder(parent, sparseArray.get(0) ?: LayoutInflater.from(parent.context))
        defaultViewHolder.itemView.setTag(R.id.list_adapter, this)
        return defaultViewHolder
    }
    
    override fun getItemViewType(position: Int): Int {
        val item = getItem(position) ?: return 0
        val type = item.itemViewType
        if (!defaultViewHolderFactoryCache.contains(type)) {
            item as ViewHolderFactory<VH>
            defaultViewHolderFactoryCache.register(type, item)
        }
        return type
    }
    
    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        defaultViewHolderFactoryCache.clear()
        sparseArray.clear()
    }
複製代碼

在onDetachedFromRecyclerView後清理掉緩存數據。有個細節這裏沒看到,來看下

interface ViewModel<M, VH : RecyclerView.ViewHolder, Adapter:IAdapter<*>> :
    ViewHolderFactory<VH> {
    var model: M?
    var adapter: Adapter?
    val itemViewType: Int
        get() = layoutRes

    @get:LayoutRes
    val layoutRes: Int
    fun bindVH(
        viewHolder: VH,
        model: M,
        payloads: List<Any>
    )

    fun unBindVH(viewHolder: VH)
}
複製代碼

這是ViewModel,它其實也是個接口,但在Kotlin裏接口是能夠有實現的(kotlin的優點就在這),因此我讓itemViewType默認就是layoutRes,我人爲這種抽象的實現,徹底合理,不一樣的layout佈局就是不一樣的ViewHolder,而既然都是同樣的layout,ViewHolder也徹底能夠同樣對嗎?對的。

如何作一個通用的ViewModel,如何實現DSL呢?

通用的ViewHolder就不用說了,在以前的博客裏已經分析了,想看的請轉一個資深的Android是否是應該學會本身作一個超級的RecyclerView.Adapter,這裏面的是初期封裝實現的,底層我作了優化,暫且只看ViewHolder,那ViewModel應該如何作呢?咱們來看下上面的ViewModel接口,分析一下它的職責,以下

  • 繼承ViewHolderFactory,負責getViewHolder
  • bindVH 負責綁定數據
  • unBindVH 解綁時觸發
  • layoutRes 負責佈局的引用
  • itemViewType 默認就是佈局的應用id
  • 持有Model數據,能夠是任何類型
  • 持有adapter 須要繼承自IAdapter

其實對於使用者來講,最須要作的就是,將業務的Model綁定到ViewModel上,並接受bindVH回調來實現綁定,配置一個layoutRes就完事兒了,下面直接看下繼承的實現

typealias  DefaultViewModelType <M, Adapter> = ViewModel<M, DefaultViewHolder, Adapter>

abstract class DefaultItemViewModel<M, A : IAdapter<*>> : DefaultViewModelType<M, A> {

    override var adapter: A? = null
    override var model: M? = null
    private var bindView: BindView? = null
    private var bindViewPayload: BindViewPayload? = null
    private var itemClick: ItemClick<M>? = null

    open fun onBindViewHolder(f: (DefaultViewHolder) -> Unit) {
        bindView = f
    }

    open fun onBindViewHolder(f: (DefaultViewHolder, Any) -> Unit) {
        bindViewPayload = f
    }

    open fun onItemClick(f: (viewModel: ArrayItemViewModel<M>, viewHolder: DefaultViewHolder) -> Unit) {
        itemClick = f
    }

    override fun getViewHolder(
        parent: ViewGroup,
        layoutInflater: LayoutInflater
    ): DefaultViewHolder {
        return DefaultViewHolder(layoutInflater.inflate(layoutRes, parent, false)).apply {
            itemView.setOnClickListener {
                itemClick?.invoke(
                    adapter?.getItem(adapterPosition) as @ParameterName(name = "viewModel") ArrayItemViewModel<M>,
                    this
                )
            }
        }
    }

    override fun bindVH(viewHolder: DefaultViewHolder, model: M, payloads: List<Any>) {
        if (payloads.isNotEmpty()) {
            this.model =  payloads[0] as M
            bindViewPayload?.invoke(viewHolder, payloads[0])
        }else{
            bindView?.invoke(viewHolder)
        }
    }

    override fun unBindVH(viewHolder: DefaultViewHolder) {}
}

typealias  ArrayViewModelType <M> = DefaultItemViewModel<M, ArrayListAdapter>

open class ArrayItemViewModel<M>(override val layoutRes: Int) : ArrayViewModelType<M>()

複製代碼

實現綁定有兩個部分,一個是有payloads狀況,咱們回調函數bindViewPayload,不然回調bindView函數,這樣能夠實現ItemView的局部刷新,業務層調用onBindViewHolder傳入一個高級函數,這個高級函數最終賦值給bindView,而後接收回調就能夠,這裏用了通用的DefaultViewHolder,初始化後直接setOnClickListener,爲何這麼作:就是爲了防止業務端出現屢次的setOnClickListener,一次設置終身收益,這裏DefaultItemViewModel<M, A : IAdapter<*>>特地給Adapter留了擴展的入口,由於咱們的Adapter會有好幾個,但均可以複用這塊的邏輯,ArrayItemViewModel就是針對ArrayListAdapter的一個擴展,下面作一個DSL支持,請看代碼

fun <M> arrayItemViewModelDsl(
    layoutRes: Int,
    init: ArrayItemViewModel<M>.() -> Unit
): ArrayItemViewModel<M> {
    return ArrayItemViewModel<M>(layoutRes).apply {
        init()
    }
}

fun arrayListAdapter(block: ArrayListAdapter.() -> Unit): ArrayListAdapter {
    return ArrayListAdapter().apply {
        block()
    }
}

fun ListAdapter<*, *>.into(
    recyclerView: RecyclerView,
    layoutManager: RecyclerView.LayoutManager? = null
) = apply {
    recyclerView.layoutManager = layoutManager ?: LinearLayoutManager(recyclerView.context)
    recyclerView.adapter = this
}

複製代碼

第一步擴展ArrayItemViewModel,第二步擴展ArrayListAdapter,第三擴展Adapter抽象類,綁定到RecyclerView,來看下使用的效果

arrayListAdapter {
            //循環添加ItemViewModel
            (0..10).map {
                add(
                    // ItemViewModel 對象 函數中傳入佈局IdRes
                    arrayItemViewModelDsl<ModelTest>(if (it % 2 == 0) R.layout.item_test else R.layout.item_test_2) {
                        // Model 數據模型
                        model = ModelTest("title$it", "subTitle$it")
                        // 綁定數據
                        onBindViewHolder { viewHolder ->
                            viewHolder.getView<TextView>(R.id.tv_title)?.text = model?.title
                            viewHolder.getView<TextView>(R.id.tv_subTitle)?.text = model?.subTitle
                        }
                        // 點擊處理
                        onItemClick { vm, vh ->
                            //這裏須要注意,爲何直接從該對象獲取的Model是不正確的?由於ViewHolder的複用
                            //致使click事件實際上是在另一個VM裏觸發的
                            Log.d("arrayItemViewModel", "不正確的model${model}")
                            Log.d("arrayItemViewModel", "正確的model${vm.model}")
                            Log.d("arrayItemViewModel", "adapter$adapter")
                            Log.d("arrayItemViewModel", "viewHolder${vh.adapterPosition}")
                            //修改Model數據
                            vm.model?.title = "測試更新"
                            //用Adapter更新數據
                            adapter?.set(vh.adapterPosition, vm)
                        }
                    }
                )
            }
            // 綁定 RecyclerView
            into(rv_list_dsl)
        }
複製代碼

看完例子,有沒有以爲還行?我卻是以爲還Ok吧。

如何擴展成Anko Layout呢?

咱們都知道Anko layout能夠提高UI的加載效率,減小cpu的使用,這裏引用一個大佬的總結,來看下

從xml 到 運行 再到 讀取xml文件 最終到生成UI元素,最主要的就是經歷了文件流的讀取

而DSL直接編輯代碼測量佈局而後繪製,省了這麼多的步驟,能不快嗎?到底快多少呢?來看另外一張圖,一個大佬的測試

最低的手機型號都快了近359%,這是什麼操做?流不流逼?咱們先不說DSL的寫法極大的高了加載效率,它還有其餘優點,DSL寫起來也比XML簡潔易懂,來看個例子

class AnkoLayoutComponent(private val ankoListAdapter: ArrayListAdapter) : AnkoComponent<AnkoLayoutActivity> {

    override fun createView(ui: AnkoContext<AnkoLayoutActivity>) = with(ui) {

        verticalLayout {

            recyclerView {
                bindListAdapter(ankoListAdapter)
            }.lparams(matchParent) {
                weight = 1F
            }
            // Anko 兼容 xml佈局的加載
            include<View>(R.layout.include_button_bottom)

        }

    }

}
複製代碼

這個例子,我想告訴你的是,anko layout 不光是它的獨有的寫法,還徹底兼容XML,這也給那些想慢慢過渡的朋友,一個好的方式。那麼主題來了,如何擴展Adapter 也用上這個呢?其實關鍵點就是View,來看下AnkoComponent接口

interface AnkoComponent<in T> {
    fun createView(ui: AnkoContext<T>): View
}
複製代碼

再看下ViewHolder構造函數,這不是正好嗎?AnkoComponent生成的View,直接給ViewHolder不就能夠直接跳過XML嗎?

public ViewHolder(@NonNull View itemView) {
            if (itemView == null) {
                throw new IllegalArgumentException("itemView may not be null");
            }
            this.itemView = itemView;
        }
複製代碼

實現代碼以下,直接繼承ArrayItemViewModel,而後實現getViewHolder函數,最終經過AnkoComponent的實例對象createView,就是這麼簡單。

public abstract class AnkoItemViewModel<M, AnkoView extends AnkoComponent<ViewGroup>>
        extends ArrayItemViewModel<M> {

    public AnkoItemViewModel() {
        super(0);
    }

    public abstract AnkoView onCreateView();

    @NotNull
    @Override
    public DefaultViewHolder getViewHolder(@NotNull ViewGroup parent, @NotNull LayoutInflater layoutInflater) {
        AnkoView ankoView = onCreateView();
        View view = ankoView.createView(AnkoContext.Companion.create(parent.getContext(), parent, false));
        view.setTag(R.id.list_adapter_anko_view, ankoView);
        return new DefaultViewHolder(view);
    }

    @Override
    public int getItemViewType() {
        return this.hashCode();
    }

    public AnkoView getAnkoView(RecyclerView.ViewHolder viewHolder) {
        return (AnkoView) viewHolder.itemView.getTag(R.id.list_adapter_anko_view);
    }

}

複製代碼

用法示例

class AnkoViewModelTest : AnkoItemViewModel<ModelTest, AnkoItemView>() {
    init {
        onBindViewHolder { viewHolder ->
            getAnkoView(viewHolder).tvTitle?.text = model?.title
            getAnkoView(viewHolder).tvSubTitle?.text = model?.subTitle
            getAnkoView(viewHolder).itemClick = {
                Log.d("AnkoViewModelTest", "正確的model${model}")
                Log.d("AnkoViewModelTest", "正確的model${model}")

                Log.d("AnkoViewModelTest", "adapter$adapter")
                Log.d("AnkoViewModelTest", "viewHolder${viewHolder.adapterPosition}")
                model?.title = "點擊更新"
                adapter?.set(viewHolder.adapterPosition, this)
            }
        }
    }
    override fun onCreateView(): AnkoItemView {
        return AnkoItemView()
    }
}

class AnkoItemView : AnkoComponent<ViewGroup> {
    ...
}
複製代碼

繼承AnkoItemViewModel,配置一下Model和AnkoView就好了,就是這麼的簡單,而後在Adapter裏這麼使用,添加一個對應的實例就好了

listAdapter.add(AnkoViewModelTest().apply {
                    model = ModelTest("標題${++index}", "副標題")
                })
複製代碼

擴展了這麼多,整體上是如何設計的?

interface IAdapter<VM> {
    fun getItem(position: Int): VM?
}

複製代碼

最上層IAdapter接口,只有一個函數經過positon 獲取ViewModel,中間層 ListAdapter

abstract class ListAdapter<VM : ViewModel<*,*,*>, VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>(), IAdapter<VM> {

    private val defaultViewHolderFactoryCache = DefaultViewHolderFactoryCache<ViewHolderFactory<VH>>()
    private val sparseArray = SparseArray<LayoutInflater>(1)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        val defaultViewHolder = defaultViewHolderFactoryCache[viewType].getViewHolder(parent, sparseArray.get(0) ?: LayoutInflater.from(parent.context))
        defaultViewHolder.itemView.setTag(R.id.list_adapter, this)
        return defaultViewHolder
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        onBindViewHolder(holder, position, Collections.emptyList())
    }

    override fun onBindViewHolder(holder: VH, position: Int, payloads: MutableList<Any>) {
        if(position != RecyclerView.NO_POSITION){
            // Do your binding here
            holder.itemView.setTag(R.id.list_adapter, this)
            val item = getItem(position) as? ViewModel<Any, RecyclerView.ViewHolder, IAdapter<*>>
            item?.let {
                item.adapter = this
                item.model?.let { it1 -> item.bindVH(holder, it1, payloads) }
                holder.itemView.setTag(R.id.list_adapter_item, item)
            }
        }
    }
    override fun getItemViewType(position: Int): Int {
        val item = getItem(position) ?: return 0
        val type = item.itemViewType
        if (!defaultViewHolderFactoryCache.contains(type)) {
            item as ViewHolderFactory<VH>
            defaultViewHolderFactoryCache.register(type, item)
        }
        return type
    }

    override fun onViewRecycled(holder: VH) {
        (holder.itemView.getTag(R.id.list_adapter_item) as ViewModel<*, VH, *>).apply {
            unBindVH(holder)
        }
        holder.itemView.setTag(R.id.list_adapter_item, null)
        holder.itemView.setTag(R.id.list_adapter, null)
    }

    override fun onAttachedToRecyclerView(recyclerView: RecyclerView) {
        val context = recyclerView.context
        sparseArray.append(0, LayoutInflater.from(context))
    }

    override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
        defaultViewHolderFactoryCache.clear()
        sparseArray.clear()
    }
}
複製代碼

爲何這麼設計,我說幾個理由

  • 第一 在getItemViewType的時候,參數就是position,這樣我就能夠經過postion拿到ViewModel,經過ViewModel就能夠實現ViewHolderFactory,而後經過DefaultViewHolderFactoryCache緩存ViewHolder就好了。
  • 第二 ListAdapter只幹了很簡單的事情,緩存ViewHolder,並在onBindViewHolder的時候,觸發ViewModel的bindVH。職責上儘可能簡單,纔好複用。
  • 第三 集成它之後只須要擴展對應的數據結構,如ArrayListAdapter的ObservableArrayList,SortedListAdapter的SortedList,還有PagingListAdapter的AsyncPagingDataDiffer,這樣面臨不一樣的數據接口,不斷擴展就行。

這樣設計好嗎?是否能夠將不一樣的數據結構抽象成一種呢?不都是列表嗎?還能咋地?答案是能夠的,但我爲何沒有這麼作呢?第一個理由,分包處理,按需依賴,保持架構的足夠清晰簡潔,易懂易維護,還有個理由這三個數據結構說白了都是對數據列表作了不一樣程度的封裝,ObservableArrayList主要就是擴展了ArrayList實現數據更新回調,而後觸發Adapter更新,SortedList就是個工具類,沒有擴展List,它一直保持着一個有序的列表,並且用到了二分查找算法來實現快速的定位更新,AsyncPagingDataDiffer就更多了,線程、分頁、狀態、比較等,更復雜的功能封裝。三個Adapter的代碼就不貼了,能夠去看源碼哦?

經過封裝PagingListAdapter,發現了新大陸,想知道嗎?

接下來就揭曉,爲何我不着急封裝空佈局、頭尾佈局、上啦加載等經常使用業務組件?

  • 第一 看了Paging 3 版本的PagingDataAdapter後,我發現一個更好的頭尾佈局實現方案
  • 第二 這些組件的封裝,其實無形中加大了框架的複雜度,而更重要的緣由是,不一樣的App,這些都是須要本身實現的,那麼如何提供這樣一個擴展入口才是最主要的,而不是實現這些功能

基於這些,我更想提供一些例子來讓你更加容易實現想要的,而不是按照個人接口規範來,若是個人接口規範很差不完整,你還不得不換其餘的框架,這豈不是讓你很尷尬,大家有沒有遇到一個項目引用好幾個Adapter框架?確定有吧。

下面咱們看下Paging如何作到了優雅的封裝呢?

fun withLoadStateHeader(
        header: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            header.loadState = loadStates.prepend
        }
        return ConcatAdapter(header, this)
    }

    fun withLoadStateFooter(
        footer: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            footer.loadState = loadStates.append
        }
        return ConcatAdapter(this, footer)
    }

    fun withLoadStateHeaderAndFooter(
        header: LoadStateAdapter<*>,
        footer: LoadStateAdapter<*>
    ): ConcatAdapter {
        addLoadStateListener { loadStates ->
            header.loadState = loadStates.prepend
            footer.loadState = loadStates.append
        }
        return ConcatAdapter(header, this, footer)
    }
複製代碼

這是PagingDataAdapter 提供的三個類函數,發現了什麼?ConcatAdapter,對的就這個東西。再往裏看下

public final class ConcatAdapter extends Adapter<ViewHolder> 
複製代碼

就是對RecyclerView.Adapter 的一個擴展,官方解釋ConcatAdapter容許咱們按順序顯示多個適配器的內容,也就是將原先綁定一個Adapter,如今變成了能夠按順序綁定多個,這樣組合實現,是否是很新穎,我以前接觸過一個WrapperAdapter,也是相似的設計,但我那個是這樣

這種方案也是大多數熱門Adapter框架解耦的方法,利用WrapperAdapter能夠實現頭尾佈局,空佈局等等吧。可當我接觸到ConcatAdapter後發現還有這種設計

這種方式更適合作頭尾佈局的擴展,徹底的解耦,也不須要WrapperAdapter來實現方法的回調。最開心的是ConcatAdapter在RecyclerView庫的1.2.0版本就有了,之後咱們能夠基於這個作不少事情,包括上啦加載,下拉加載等,Paging的頭尾狀態如何控制的呢?

abstract class LoadStateAdapter<VH : RecyclerView.ViewHolder> : RecyclerView.Adapter<VH>() {
  
    var loadState: LoadState = LoadState.NotLoading(endOfPaginationReached = false)
        set(loadState) {
            if (field != loadState) {
                val oldItem = displayLoadStateAsItem(field)
                val newItem = displayLoadStateAsItem(loadState)

                if (oldItem && !newItem) {
                    notifyItemRemoved(0)
                } else if (newItem && !oldItem) {
                    notifyItemInserted(0)
                } else if (oldItem && newItem) {
                    notifyItemChanged(0)
                }
                field = loadState
            }
        }

    final override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        return onCreateViewHolder(parent, loadState)
    }

    final override fun onBindViewHolder(holder: VH, position: Int) {
        onBindViewHolder(holder, loadState)
    }

    final override fun getItemViewType(position: Int): Int = getStateViewType(loadState)

    final override fun getItemCount(): Int = if (displayLoadStateAsItem(loadState)) 1 else 0

    abstract fun onCreateViewHolder(parent: ViewGroup, loadState: LoadState): VH

    abstract fun onBindViewHolder(holder: VH, loadState: LoadState)

    open fun displayLoadStateAsItem(loadState: LoadState): Boolean {
        return loadState is LoadState.Loading || loadState is LoadState.Error
    }
}

複製代碼

一樣實現RV的Adapter,抽象一個LoadState狀態,而後根據它的狀態,notifyItemRemoved(0)或者notifyItemInserted(0)或者notifyItemChanged(0),那咱們將來是否是也能夠這樣作呢?徹底能夠,並且我建議這樣作,組合實現,跟插件同樣,隨用隨插。不浪費感情。

講了這麼多源碼在哪?

github.com/ibaozi-cn/R…

如何引用呢?

allprojects {
    repositories {
        // 首先項目根目錄的build.gradle文件中加入這一行 
        maven { url 'https://jitpack.io' }
    }
}

//核心庫
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-core:V1.0.0

//下面都是可選項

//anko layout 擴展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-anko:V1.0.0
//diffutil 擴展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-diff:V1.0.0
//data binding擴展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-binding:V1.0.0
// paging3 擴展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-paging:V1.0.0
// sortedlist 擴展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-sorted:V1.0.0
// flexbox 擴展
implementation com.github.ibaozi-cn.RecyclerViewAdapter:adapter-flex:V1.0.0
複製代碼

將來還有什麼計劃嗎?

有,確定有,第一不斷的完善設計和實現,第二不斷的聆聽大家的建議或者批評,哪裏設計的很差的請您大膽的告訴我,正所謂知錯能改善莫大焉,我不入地獄,誰入地獄? 好了此次就到這了,拜拜

相關文章
相關標籤/搜索