一晃五六年,歲月蹉跎,不由感嘆:曾幾什麼時候,沉迷於框架不能自拔,無論作什麼需求都要找一個框架出來,而後用了一段時間後,發現諸多問題,不少時候又不得不將就着用,難道咱們就應該被別人左右嗎?答案是No,仍是得試着提升本身的架構能力,來應對將來更多的挑戰。你越是醒悟的快,你的進步就會越快,閱讀源碼是痛苦的,可愈來愈多的痛苦終將會成就你,不信你跟着我往下看。緩存
名字 | 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
如今咱們要實現以下目標架構
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這層很關鍵,它負責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();
}
複製代碼
這麼設計最大的亮點就是少了ItemViewType的維護,讓你看看別人的設計,下面是別人的代碼,維護ItemViewType,嚇人不,若是之後再多一種EMPTY_VIEW,那我是否是得擴展一個EMPTY_VIEW2啊,並且還要修改這裏的邏輯,這麼設計不科學啊,應該永遠或者說盡可能不要動底層邏輯纔對,由於你動了底層邏輯就要面臨的全面測試。ide
在我看來,最好的設計是永遠不要關心ItemViewType的邏輯,而所謂的頭部View和底部View只是你維護在List頂端和低端的數據,最終根據List的排序綁定到ItemView上,而不是經過ItemViewType去控制,這點你細細品味。而EmptyView更像是一個壓在RecyclerView上面的棧,或者你把List改爲一個Empty的ViewModel並全屏展現的RecyclerView上,當有真實數據的時候將其移除掉,總之咱們操做的就是ViewModel的去和留,保持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
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
}
}
複製代碼
因爲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,麻煩辛苦下你的小手,點個贊哦親。