乐趣区

重学RecyclerView-Adapter封装的深度思考和实现

背景

一转眼,从一开始公布文章说带大家
封装 Adapter
直到现在,过来半个月了吧,前后又仔细阅读了很多 Adapter 的框架源码,对 Adapter 的变幻无穷算是有了粗浅的意识,真的是温故而知新,借着这次机会,我也将学到的这些长处一一的列出来通知你,跟我一起重新认识 Adapter

值得一看的 Adapter 开源我的项目

  • mikepenz/FastAdapter
  • DevAhamed/MultiViewAdapter
  • davideas/FlexibleAdapter
  • sockeqwe/AdapterDelegates
  • liangjingkanji/BRV
  • evant/binding-collection-adapter
  • nitrico/LastAdapter
  • 作者 Adapter

这些开源我的项目都有哪些相同点?

  • 这几个我的项目最大的共同点是都对 DataBinding 做了扩大,看来 DataBinding 大家都很认可
  • 有 6 个我的项目都用到了 Kotlin,最高达到了 96%,看来咱们 android 小伙伴都爱学习,kotlin 的普及率好高
  • 还有一个点都对 MultiItemType 做了良好的扩大,阐明这的确是一个 Adapter 库的标配
  • 有三个我的项目对 Paging 库做了扩大,置信大家用它的机会越来越多
  • 头尾布局简直成了框架的标配
  • Kotlin DSL 或多或少的反对,写法上简洁而柔美
  • 大多数都做了比拟好的分库解决,按需依赖,不快人快语

咱们如何做一个能够称之为全而好的 Adapter 框架呢?

我抽取几个关键词来通知你

  • 适当分包 按需依赖,提供良好的扩展性
  • kotlin 反对 利用最近一个老铁的话:工欲善其事必先利其器,kotlin 绝壁是一个好利器
  • DSL 扩大 简洁的写法,利于浏览
  • DataBinding 反对 不必质疑,有就对了
  • Paging 扩大 在我看来 Paging 用了很多新的设计,值得咱们去学习
  • 头尾布局、空布局、上拉加载,拖动,动画 提供专门的包来扩大
  • DiffUtil 扩大 好的工具不要遗记应用,总会有适合的利用场景
  • anko 扩大 用了 kotlin 怎么能不必一下 anko layout,300% 的布局加载效率进步不香吗?

设计这么一个框架我的保持是什么?

你有没有陷入过一个误区?本人封装了一个货色,特地的全,什么都反对,一股脑的往里面塞货色,这个人说我想这样,你就改成这样,那个人想那样,你又开始改?难道咱们做个货色就是为了让他人指挥吗?答案必定是:不。那应该从哪几个方面思考呢?

  • 扩展性 对的,你肯定要做到可扩大,要不然你就要面临东改改,西改改的窘境,如何做到可扩大呢?一条准则搞定:依赖倒转准则,尽量依赖于形象,而不是依赖于实现,做到这一点其实还不够
  • 可靠性 能够了解为,你做的货色不要常常变,特地是形象的接口,在一开始咱们就要做到残缺,有的人说,我怎么肯能思考那么完满呢?那就要考虑一下,你是不是走到了一个误区,接口的形象在这样一个成熟的框架中,应该很好确定的,接口不要大而全,接口也要做到尽量的小,起码接口能够多继承啊,对吧。
  • 里氏替换准则: 任何基类能够呈现的中央,子类肯定能够呈现,这个准则其实好多人都不太了解,其实说白一点,用事实的例子通知你,你继承了父亲的很多基因,那你能扭转父亲的基因吗?必定不能,可代码里是能够重写的(形象办法不算哦),但这个准则就是通知咱们防止重写,一旦重写,父类就没有了意义,其实在工作中,有很多人喜爱往父类里形象很多货色,还特地喜爱在不同的子类中笼罩后重写,这些都是不好的习惯,你不应该只是老三用到了一个货色,就要给所有的人加上这个货色,我感觉是个累赘,你感觉呢?咱们在设计框架的时候,肯定要遵循这些好的设计准则。
  • 繁多职责 一个类应该有且仅有一个职责,你还好意思封装一个类,啥性能都有吗?除非它叫 Manager,但也只能治理一类货色,职责的繁多处处可见,且要处处保持。

讲了这些废话,你是不是不耐烦了,上面来点干货吧,看看 Adapter 的一些细节是如何封装的

Adapter 如何做到 ItemViewType 主动适配?咱们是如何封装的?

看源码前,我想说的是,你要明确一个原理,其实 ItemViewType 它影响的就是 ViewHolder 的复用逻辑,只有是一样的 ItemViewType,它就会触发复用,所以说 ItemViewType 封装的目标,其实是缓存 ViewHolder 而后复用,那咱们如何做到主动解决加缓存呢?

因为咱们要做到 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,提供注册、获取、查看、清理的办法

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),那咱们将来是不是也能够这样做呢?齐全能够,而且我倡议这样做,组合实现,跟插件一样,随用随插。不节约感情。

讲了这么多源码在哪?

https://github.com/ibaozi-cn/…

如何援用呢?

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

将来还有什么打算吗?

有,必定有,第一一直的欠缺设计和实现,第二一直的凝听你们的倡议或者批评,哪里设计的不好的请您大胆的通知我,正所谓知错能改善莫大焉,我不入天堂,谁入天堂?
好了这次就到这了,拜拜

退出移动版