一個資深的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,可它的問題又是最多,太難了,一點也不簡單。不如本身作一個算了,對了,咱們仍是選擇本身搞一個唄。架構

原生Adapter幾個痛點

  • Adapter 不通用,每遇到新的業務都要建立新的Adapter
  • ViewHolder 也不通用,問題同上
  • 集合數據的更新不能主動讓Adapter通知頁面刷新
  • ItemViewType 須要本身維護一套常量控制
  • onBindViewHolder 隨着業務的複雜,變得愈來愈臃腫

從零開始,咱們本身寫一個舒服的Adapter


之前咱們的實現,要分別實現Adapter、ViewHolder、Model,並且它們之間耦合嚴重,遇到一個複雜的列表真是苦不堪言。app

如今咱們要實現以下目標框架

  • 通用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也是用的這個實現,這是你們公認的比較靠譜的寫法。ide

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多行代碼搞定,超級簡單把。this

ListAdapter 抽象類spa

  • 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,麻煩辛苦下你的小手,點個贊哦親。

相關文章
相關標籤/搜索