一个资深的Android是否是应该学会本身作一个超级的RecyclerView.Adapter

前言

一晃五六年,岁月蹉跎,不由感叹:曾几什么时候,沉迷于框架不能自拔,无论作什么需求都要找一个框架出来,而后用了一段时间后,发现诸多问题,不少时候又不得不将就着用,难道咱们就应该被别人左右吗?答案是No,仍是得试着提升本身的架构能力,来应对将来更多的挑战。你越是醒悟的快,你的进步就会越快,阅读源码是痛苦的,可愈来愈多的痛苦终将会成就你,不信你跟着我往下看。缓存

本期内容

  • 经常使用Adapter比较
  • 原生Adapter痛点在哪
  • 从零开始,咱们本身写一个舒服的Adapter

经常使用Adapter

名字 Star 未解决问题 最后一次更新时间 包大小(aar)
BaseRecyclerViewAdapterHelper 20.2k 162 27天前 81KB (V2.9.5)
baseAdapter 4.5K 107 4年前 10.53 KB (v3.0.3)
FlexibleAdapter 3.3K 55 15个月前 123KB (v5.0.5)
FastAdapter 3.1K 3 8天前 164KB (v5.1.0)

经过这些基础数据的比较,让你选择,你会怎么选?固然首先会剔除掉baseAdapter、FlexibleAdapter,一年以上不维护,问题50个以上,框架底子再好,选择之后就要靠本身了不是,咱们再来看问题最少更新最频繁的是FastAdapter,可它的包足足大了BaseRecyclerViewAdapterHelper一倍,请问做者你干了哈,要这么大的吗?若是大家公司对包大小有要求,这个基本被pass了,可能你以为164KB不大,可若是再多几个框架,叠加起来也会大啊,能省则省呗。看来最合适的只有BaseRecyclerViewAdapterHelper,可它的问题又是最多,太难了,一点也不简单。不如本身作一个算了,对了,咱们仍是选择本身搞一个呗。bash

原生Adapter几个痛点

  • Adapter 不通用,每遇到新的业务都要建立新的Adapter
  • ViewHolder 也不通用,问题同上
  • 集合数据的更新不能主动让Adapter通知页面刷新
  • ItemViewType 须要本身维护一套常量控制
  • onBindViewHolder 随着业务的复杂,变得愈来愈臃肿

从零开始,咱们本身写一个舒服的Adapter

之前咱们的实现,要分别实现Adapter、ViewHolder、Model,并且它们之间耦合严重,遇到一个复杂的列表真是苦不堪言。

如今咱们要实现以下目标架构

  • 通用ViewHolder,再也不重写ViewHolder
  • 通用Adapter,再也不重写Adapter
  • 只关注实现ViewModel,并实现View根据ViewModel的变化而变化,自动作到局部刷新(不是简单粗暴的NotifyDataSetChange)

通用的ViewHolder

class DefaultViewHolder(val view: View) : RecyclerView.ViewHolder(view) {
    /**
     * views缓存
     */
    private val views: SparseArray<View> = SparseArray()
    val mContext: Context = view.context

    fun <T : View> getView(@IdRes viewId: Int): T? {
        return retrieveView(viewId)
    }

    private fun <T : View> retrieveView(@IdRes viewId: Int): T? {
        var view = views[viewId]
        if (view == null) {
            view = itemView.findViewById(viewId)
            if (view == null) return null
            views.put(viewId, view)
        }
        return view as T
    }
}
复制代码

ViewHolder职责很单一,就是负责持有View的引用,辅助你用对的View作对的事,这里优化了一点就是用到SparseArray缓存,其实就是作了简单的优化,防止再次findViewById形成没必要要的损耗。BaseRecyclerViewAdapterHelper的ViewHolder也是用的这个实现,这是你们公认的比较靠谱的写法。app

ViewModel抽象

ViewModel这层很关键,它负责View数据的绑定逻辑和负责加载哪一个Layout,来直接看代码框架

public abstract class ViewModel<M, VH extends RecyclerView.ViewHolder> {

    public M model;
    public VH viewHolder;

    public abstract void onBindView(RecyclerView.Adapter<?> adapter);

    int getItemViewType() {
        return getLayoutRes();
    }

    @LayoutRes
    public abstract int getLayoutRes();

}
复制代码
  • M 数据源的抽象,负责提供什么样子的数据
  • VH 默认是DefaultViewHolder,固然也能够有其余的扩展,这里给扩展留有余地
  • onBindView 负责将M绑定到VH的逻辑,这里回传Adapter是为了之后不一样的ViewModel有数据交互的状况,这里就能够经过Adapter拿到关联的值,而且能够经过它去刷新其余Item,是否是很聪明。
  • getItemViewType 这个你们应该知道,这里是RecyclerView适配不一样布局Layout的关键参数,默认是getLayoutRes,由于不一样的布局=不一样的LayoutRes,固然你也能够扩展变动逻辑,但目前来看不必变。
  • getLayoutRes 也就是R.layout.item_layout,获取布局的引用。

这么设计最大的亮点就是少了ItemViewType的维护,让你看看别人的设计,下面是别人的代码,维护ItemViewType,吓人不,若是之后再多一种EMPTY_VIEW,那我是否是得扩展一个EMPTY_VIEW2啊,并且还要修改这里的逻辑,这么设计不科学啊,应该永远或者说尽可能不要动底层逻辑才对,由于你动了底层逻辑就要面临的全面测试。ide

在我看来,最好的设计是永远不要关心ItemViewType的逻辑,而所谓的头部View和底部View只是你维护在List顶端和低端的数据,最终根据List的排序绑定到ItemView上,而不是经过ItemViewType去控制,这点你细细品味。而EmptyView更像是一个压在RecyclerView上面的栈,或者你把List改为一个Empty的ViewModel并全屏展现的RecyclerView上,当有真实数据的时候将其移除掉,总之咱们操做的就是ViewModel的去和留,保持Adapter底层逻辑的简洁。

通用Adapter

史上最简单的通用Adapter就要出现了,鼓掌把朋友布局

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

    protected val layouts: SparseIntArray by lazy(LazyThreadSafetyMode.NONE) { SparseIntArray() }

     override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): DefaultViewHolder {
        return DefaultViewHolder(LayoutInflater.from(parent.context).inflate(layouts[viewType], parent, false))
    }

    override fun getItemViewType(position: Int): Int {
        val item = getItem(position)
        layouts.append(item.itemViewType, item.layoutRes)
        return item.itemViewType
    }

    override fun onBindViewHolder(holder: DefaultViewHolder, position: Int) {
        val item = getItem(position)
        item.onBindView(this)
    }

    abstract fun getItem(position: Int): VM

}

class ArrayListAdapter<M> : ListAdapter<ArrayListViewModel<M>>() {

    private val observableDataList = ObservableArrayList<ArrayListViewModel<M>>()

    init {

        observableDataList.addOnListChangedCallback(object : OnListChangedCallback<ObservableArrayList<ArrayListViewModel<M>>>() {

            override fun onChanged(sender: ObservableArrayList<ArrayListViewModel<M>>) {
                notifyDataSetChanged()
            }

            override fun onItemRangeChanged(sender: ObservableArrayList<ArrayListViewModel<M>>, positionStart: Int, itemCount: Int) {
                notifyItemRangeChanged(positionStart, itemCount)
            }

            override fun onItemRangeInserted(sender: ObservableArrayList<ArrayListViewModel<M>>, positionStart: Int, itemCount: Int) {
                notifyItemRangeInserted(positionStart, itemCount)
            }

            override fun onItemRangeMoved(sender: ObservableArrayList<ArrayListViewModel<M>>, fromPosition: Int, toPosition: Int, itemCount: Int) {
                notifyItemMoved(fromPosition, toPosition)
            }

            override fun onItemRangeRemoved(sender: ObservableArrayList<ArrayListViewModel<M>>, positionStart: Int, itemCount: Int) {
                notifyItemRangeRemoved(positionStart, itemCount)
            }
        })

    }

    override fun getItem(position: Int): ArrayListViewModel<M> {
        return observableDataList[position]
    }

    override fun getItemCount(): Int {
        return observableDataList.size
    }
    
    fun add(index: Int, element: ArrayListViewModel<M>) {
        observableDataList.add(index, element)
    }

    fun removeAt(index: Int): ArrayListViewModel<M> {
        return observableDataList.removeAt(index)
    }

    fun set(index: Int, element: ArrayListViewModel<M>): ArrayListViewModel<M> {
        return observableDataList.set(index, element)
    }
}
复制代码

70多行代码搞定,超级简单把。测试

ListAdapter 抽象类优化

  • layouts SparseIntArray的实现,以itemViewType为Key负责缓存layoutRes,这里这样写实际上是为了兼容你扩展了ViewModel的getItemViewType的实现逻辑,固然默认状况下itemViewType就是layoutRes,因此也能够不用缓存,但咱们保持咱们框架的扩展性,打开这个大门让你自定义。有的人就喜欢给itemViewType定义特殊的常量,我能有什么办法,有人确定反驳,你这无法自定义啊,设计的好垃圾,哈哈,随他去。ui

  • onCreateViewHolder 好多人都喜欢给Adapter传Context进来而后建立LayoutInflater,其实否则,你彻底能够用parent.context,学会了没?这里用到了layouts缓存的layoutRes,来加载对应的View布局

  • getItemViewType 根据position获取对应的ViewModel,而后经过ViewModel拿到itemViewType,而后顺便缓存下layoutRes,嗯,完美。

  • onBindViewHolder 经过position拿到对应的ViewModel,而后回调ViewModel的onBindView,触发Model绑定到对应的View上,嗯,完美。

  • getItem 返回对应的ViewModel,子类负责实现,由于子类实现缓存的List是不一样的实现,因此对应的获取方式有可能会不一样,因此须要抽象出来。

ArrayListAdapter

  • observableDataList ObservableArrayList的实现,是Databinding里的实现,是一个对ArrayList的包装子类,若是你项目没有引用Databinding,那么请你学我,把这三个类拿过来就ok了
    贴图不复制类名可耻(我没作到,你呢?):
CallbackRegistry
ListChangeRegistry
ObservableList
ObservableArrayList
复制代码
  • addOnListChangedCallback 添加对observableDataList的监听OnListChangedCallback,而后在数据刷新的时候分别调用onItemRangeChanged、onItemRangeInserted、onItemRangeMoved、onItemRangeRemoved,当你修改observableDataList集合的元素的时候,对应的就会回调到这里,是否是也很简单

  • getItem 、 getItemCount、add、removeAt、set 对observableDataList的常规操做,这里很少解释了。

  • ArrayListViewModel 忘了说这个,先看下代码

abstract class ArrayListViewModel<M> : ViewModel<M, DefaultViewHolder>() {
    override fun onBindView(adapter: RecyclerView.Adapter<*>?) {
        onBindAdapter(adapter = adapter as ArrayListAdapter<M>)
    }
    abstract fun onBindAdapter(adapter: ArrayListAdapter<M>)
}
复制代码

这里是为了让ArrayListAdapter对象传递给ArrayListViewModel的onBindView,让对应的ViewModel,来看个实现就知道了,下面就是个例子,这里能够直接拿到ArrayListAdapter对象,这样就能够作对应Adapter的操做,不然你就要用ListAdapter,用的时候可能会须要强转,可你强转的对不对呢?增长了不肯定因素,因此这里在抽象类实现,你要明白,抽象的目的就是为了肯定性,是吧。

class ReportEditorViewModel : ArrayListViewModel<ReportEditorBean>(){

    override fun onBindAdapter(adapter: ArrayListAdapter<ReportEditorBean>) {

    }

    override fun getLayoutRes(): Int {
        return R.layout.item_report_editor_house
    }

}
复制代码

RecyclerView 扩展

因为kotlin的便利,咱们还须要扩展一下RecyclerView,如代码:

fun <VM : ViewModel<*,*>> RecyclerView.bindListAdapter(listAdapter: ListAdapter<VM>,layoutManager: RecyclerView.LayoutManager? = null){
    this.layoutManager = layoutManager?: LinearLayoutManager(context)
    this.adapter = listAdapter
}
复制代码

给如今的RecyclerView扩展bindListAdapter,并传入咱们本身的抽象ListAdapter,最终绑定到一块儿。并提供layoutManager的默认配置,减小模版代码的生成。

页面使用效果

val adapter = ArrayListAdapter<ReportEditorBean>()

  override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_report_editor)
        rv_house_list.bindListAdapter(adapter)
        adapter.add(ReportEditorViewModel())
        adapter.add(ReportEditorViewModel())
    }
复制代码

一个Adapter、一个RecyclerView,而后就是Adapter负责增删改。就这么简单。

有人说点击事件怎么办?

颠覆你认知的时候又到了,请你忘记对Adapter的扩展实现一个onItemClickCallBack,太愚蠢了。答案就在咱们的ViewModel里,看代码实现

class ReportEditorViewModel : ArrayListViewModel<ReportEditorBean>(){

    override fun onBindAdapter(adapter: ArrayListAdapter<ReportEditorBean>) {
        viewHolder.view.setOnClickListener { 
            
        }
    }

    override fun getLayoutRes(): Int {
        return R.layout.item_report_editor_house
    }

}
复制代码

在ViewModel的实现里,用viewHolder不就能够自子加点击事件吗?并且不一样的ViewModel,点击事件处理也均可以不同,你还用在onItemClickCallBack里判断点击是什么怎么处理吗?那种愚蠢的设计就抛弃吧。

总结

今天带你实现了一个超级的Adapter,行吗?以为Ok,麻烦辛苦下你的小手,点个赞哦亲。

相关文章
相关标签/搜索