Paging痛点:必须继承PagingDataAdapter吗?| 技术点评

3,742 阅读4分钟

前言

本文主要包括以下内容
1.使用Paging必须继承PagingDataAdapter吗?
2.使用AsyncPagingDataDiffer实现Paging功能示例
3.更进一步,利用装饰模式实现Paging功能

如果觉得本文对您有所帮助,请帮忙点赞,谢谢~

使用Paging必须继承PagingDataAdapter吗?

Paging3最近已经发布了beta版本,相信几个月内应该就会正式release了
使用Paging3可以比较方便地简化加载更多逻辑,可以更轻松的在RecyclerView中逐步妥善地加载数据

最近在查阅关于Paging3的资料时,看到有不少文章说使用Paging3必须继承于PagingAdapter,这其实是错误的
如果我们点开PagingDataAdapter的源码,可以看到其实他的主要工作是由AsyncPagingDataDiffer完成的

界面层中的主要 Paging 库组件是 PagingDataAdapter,它是一种处理分页数据的 RecyclerView 适配器。 此外,您也可以使用随附的 AsyncPagingDataDiffer 组件来构建自己的自定义适配器。

由于在实际开发中,我们的adapter往往都已经有了自己的基类了,再继承PagingDataAdapter并不方便
所以实际开发中基于AsyncPagingDataDiffer来构建自己的自定义适配器是一个更加实用的选择

使用AsyncPagingDataDiffer自定义适配器

1.首先看看原有的BaseAdapter

我们的项目中往往已经有了Adapter的基类,我们这里先定义一个简单的

abstract class BaseAdapter<T:DifferData,VH:RecyclerView.ViewHolder>:RecyclerView.Adapter<VH>() {
    protected var mDataList = mutableListOf<T>()

    fun setDataList(dataList:List<T>){
        mDataList = dataList.toMutableList()
        notifyDataSetChanged()
    }

    override fun getItemCount(): Int {
        return mDataList.size
    }
}

我们这里定义的BaseAdapter非常简单,只做模拟作用
主要就是支持setDataList功能,其他就没有什么了

2.自定义PagingAdapter

我们可以通过我自定义PagingAdapter来实现Paging功能,同时继承于BaseAdapter,并不影响原有功能

abstract class PagingAdapter<T:DifferData,VH:RecyclerView.ViewHolder> : BaseAdapter<T,VH>() {
    private val differ = AsyncPagingDataDiffer<T>(
        diffCallback =DifferCallback(),
        updateCallback = AdapterListUpdateCallback(this),
        mainDispatcher = Dispatchers.Main,
        workerDispatcher = Dispatchers.Default
    )

    init {
    	//监听数据,加载成功后给BaseAdapter赋值
        differ.addLoadStateListener {
            if (it.append is LoadState.NotLoading) {
                val items = differ.snapshot().items
                setDataList(items)
            }
        }
    }

    suspend fun submitList(pagingData: PagingData<T>) {
        differ.submitData(pagingData)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
    	//这一步必不可少,因为Paging就是通过getItem触发预加载的
        differ.getItem(position)
    }
}

PagingAdapter主要做了以下工作
1.定义differ,供后续submitList后更新数据使用
2.提供submitList方法更新数据
3.在onBindViewHolder中调用getItem,这一步是必不可少的,因为Paging预载下一页就是通过getItem触发的
4.监听加载状态,在加载成功后调用setDataList给BaseAdapter中赋值即可

3.使用自定义的PagingAdapter

当我们要开发新的Adapter时,直接继承PagingAdapter即可

class DemoAdapter:PagingAdapter<NewsBean.StoriesBean,DemoAdapter.ViewHolder>() {
    class ViewHolder(itemView:View):RecyclerView.ViewHolder(itemView)

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
        return ViewHolder(LayoutInflater.from(parent.context).inflate(R.layout.item_news,parent,false))
    }

    override fun onBindViewHolder(holder: ViewHolder, position: Int) {
        super.onBindViewHolder(holder, position)
        holder.itemView.run {
            val data = mDataList[position]
            tv_title.text = data.title
            Glide.with(context).load(data.images?.get(0)).into(iv_cover)
        }
    }
}

原本继承于BaseAdapterDemoAdapter,几乎不用修改代码,改为继承于PagingAdapter,就实现了功能的增强,支持了自动预加载的功能

实现装饰模式实现Paging功能

上文我们通过AsyncPagingDataDiffer自定义适配器,继承于BaseAdapter,实现了功能的增强,这样做有什么问题?

1.继承方式有什么问题?

上文所用的方法,本质还是重重继承的方式

  • 增加了继承层次,影响代码的可维护性
    我们在BaseAdapterDemoAdapter之间,又增加了一层PagingAdapter,如果我们以后要添加新功能时,可能又要增加新的Adapter基类,这是难以维护的
  • 子类的实现依赖于父类的实现,破坏了类的封装性 如果我们要搞清楚子类有哪些方法与属性,必须层层跟进阅读父类的代码,同时子类的实现依赖于父类的实现,如果父类修改会影响所有子类的逻辑,会带来不可预知的bug

2.使用装饰方式实现

1.首先定义一个PagingWrapAdapter

class PagingWrapAdapter<T : DifferData, VH : RecyclerView.ViewHolder>(
    private val innerAdapter: RecyclerView.Adapter<VH>,
    private val callback: ((List<T>) -> Unit)
) : RecyclerView.Adapter<VH>() {
    private val differ = AsyncPagingDataDiffer<T>(
        diffCallback = DifferCallback(),
        updateCallback = AdapterListUpdateCallback(this),
        mainDispatcher = Dispatchers.Main,
        workerDispatcher = Dispatchers.Default
    )

    init {
        differ.addLoadStateListener {
            if (it.append is LoadState.NotLoading) {
                val items = differ.snapshot().items
                callback.invoke(items)
            }
        }
    }

    suspend fun submitList(pagingData: PagingData<T>) {
        differ.submitData(pagingData)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): VH {
        return innerAdapter.onCreateViewHolder(parent, viewType)
    }

    override fun onBindViewHolder(holder: VH, position: Int) {
        differ.getItem(position)
        innerAdapter.onBindViewHolder(holder, position)
    }

    override fun getItemCount(): Int {
        return innerAdapter.itemCount
    }
}

PagingWrapAdapter主要做了以下工作
1.继承于RecyclerView.Adapter,并实现几个默认方法
2.构建'differ',实现Paging功能 2.传入一个innerAdapter,将以上方法的实现由其代理实现

UML图如下所示:

2.调用方式

private val mAdapter by lazy {
    val readAdapter = Demo2Adapter()
    PagingWrapAdapter<NewsBean.StoriesBean, Demo2Adapter.ViewHolder>(readAdapter) {
        readAdapter.setDataList(it)
    }
}

可以看出,原有的adapter不需要任何修改,只需要传入PagingWrapAdapter中,即可实现预加载功能

总结

由于我们实际项目中的adapter常常已经有基类了,再去继承PaginDataAdapter并不方便
基于AsyncPagingDataDiffer构建自定义适配器是一个更好的选择

使用继承方式也会带来可维护性等问题,更好的选择是使用装饰模式实现对功能的增强以达到:实现Paging功能并不修改原有代码的目标

Show Me The Code

本文所有代码可见:PagingAdapter

本文正在参与「掘金 2021 春招闯关活动」, 点击查看活动详情