前言
一晃五六年,岁月蹉跎,不禁感叹:曾几何时,沉迷于框架不能自拔,不管做什么需求都要找一个框架出来,然后用了一段时间后,发现诸多问题,很多时候又不得不将就着用,难道我们就应该被别人左右吗?答案是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,可它的问题又是最多,太难了,一点也不简单。不如自己做一个算了,对了,我们还是选择自己搞一个呗。
原生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也是用的这个实现,这是大家公认的比较靠谱的写法。
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啊,而且还要修改这里的逻辑,这么设计不科学啊,应该永远或者说尽量不要动底层逻辑才对,因为你动了底层逻辑就要面临的全面测试。
在我看来,最好的设计是永远不要关心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定义特殊的常量,我能有什么办法,有人肯定反驳,你这没法自定义啊,设计的好垃圾,哈哈,随他去。
- 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,麻烦辛苦下你的小手,点个赞哦亲。