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() {
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 : ListAdapter<ArrayListViewModel>() {
private val observableDataList = ObservableArrayList<ArrayListViewModel>()
init {
observableDataList.addOnListChangedCallback(object : OnListChangedCallback<ObservableArrayList<ArrayListViewModel>>() {
override fun onChanged(sender: ObservableArrayList<ArrayListViewModel>) {
notifyDataSetChanged()
}
override fun onItemRangeChanged(sender: ObservableArrayList<ArrayListViewModel>, positionStart: Int, itemCount: Int) {
notifyItemRangeChanged(positionStart, itemCount)
}
override fun onItemRangeInserted(sender: ObservableArrayList<ArrayListViewModel>, positionStart: Int, itemCount: Int) {
notifyItemRangeInserted(positionStart, itemCount)
}
override fun onItemRangeMoved(sender: ObservableArrayList<ArrayListViewModel>, fromPosition: Int, toPosition: Int, itemCount: Int) {
notifyItemMoved(fromPosition, toPosition)
}
override fun onItemRangeRemoved(sender: ObservableArrayList<ArrayListViewModel>, positionStart: Int, itemCount: Int) {
notifyItemRangeRemoved(positionStart, itemCount)
}
})
}
override fun getItem(position: Int): ArrayListViewModel {
return observableDataList[position]
}
override fun getItemCount(): Int {
return observableDataList.size
}
fun add(index: Int, element: ArrayListViewModel) {
observableDataList.add(index, element)
}
fun removeAt(index: Int): ArrayListViewModel {
return observableDataList.removeAt(index)
}
fun set(index: Int, element: ArrayListViewModel): ArrayListViewModel {
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 : ViewModel<M, DefaultViewHolder>() {
override fun onBindView(adapter: RecyclerView.Adapter<*>?) {
onBindAdapter(adapter = adapter as ArrayListAdapter)
}
abstract fun onBindAdapter(adapter: ArrayListAdapter)
}
这里是为了让ArrayListAdapter对象传递给ArrayListViewModel的onBindView,让对应的ViewModel,来看个实现就知道了,下面就是个例子,这里可以直接拿到ArrayListAdapter对象,这样就可以做对应Adapter的操作,否则你就要用ListAdapter,用的时候可能会需要强转,可你强转的对不对呢?增加了不确定因素,所以这里在抽象类实现,你要明白,抽象的目的就是为了确定性,是吧。
class ReportEditorViewModel : ArrayListViewModel(){
override fun onBindAdapter(adapter: ArrayListAdapter) {
}
override fun getLayoutRes(): Int {
return R.layout.item_report_editor_house
}
}
RecyclerView 扩展
由于kotlin的便利,我们还需要扩展一下RecyclerView,如代码:
fun <VM : ViewModel<,>> RecyclerView.bindListAdapter(listAdapter: ListAdapter,layoutManager: RecyclerView.LayoutManager? = null){
this.layoutManager = layoutManager?: LinearLayoutManager(context)
this.adapter = listAdapter
}
给现在的RecyclerView扩展bindListAdapter,并传入我们自己的抽象ListAdapter,最终绑定到一起。并提供layoutManager的默认配置,减少模版代码的生成。
尾声
面试成功其实都是必然发生的事情,因为在此之前我做足了充分的准备工作,不单单是纯粹的刷题,更多的还会去刷一些Android核心架构进阶知识点,比如:JVM、高并发、多线程、缓存、热修复设计、插件化框架解读、组件化框架设计、图片加载框架、网络、设计模式、设计思想与代码质量优化、程序性能优化、开发效率优化、设计模式、负载均衡、算法、数据结构、高级UI晋升、Framework内核解析、Android组件内核等。
不仅有学习文档,视频+笔记提高学习效率,还能稳固你的知识,形成良好的系统的知识体系。这里,笔者分享一份从架构哲学的层面来剖析的视频及资料分享给大家梳理了多年的架构经验,筹备近6个月最新录制的,相信这份视频能给你带来不一样的启发、收获。
Android进阶学习资料库
一共十个专题,包括了Android进阶所有学习资料,Android进阶视频,Flutter,java基础,kotlin,NDK模块,计算机网络,数据结构与算法,微信小程序,面试题解析,framework源码!
大厂面试真题
PS:之前因为秋招收集的二十套一二线互联网公司Android面试真题 (含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《2017-2021字节跳动Android面试历年真题解析》
美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-MPDx7YWe-1720112985300)]
《2017-2021字节跳动Android面试历年真题解析》
[外链图片转存中…(img-ef8zQ75B-1720112985300)]