ConcatAdapter 是 recyclerview: 1.2.0-alpha 04 中提供的一個新組件,它能夠幫咱們順序地組合多個 Adapter,並讓它們顯示在同一個 RecyclerView 中。這使您能夠更好地封裝 Adapter。您沒必要再將許多數據源組合到一個 Adapter 中,從而在減小 Adapter 複雜度的同時也讓它們能夠被複用。android
這方面的一個用例,是在列表頭部和底部顯示加載狀態: 當列表從網絡中檢索數據時,咱們想顯示一個加載中的圖標;若是出現錯誤,咱們要顯示錯誤信息和重試按鈕。git
△ 一個帶有底部的 RecyclerView,底部顯示了加載狀態: 加載進度或錯誤信息github
ConcatAdapter 讓咱們能夠順序顯示多個 Adapter 中的內容。例如,假設咱們有下面三個 Adapter:網絡
val firstAdapter: FirstAdapter = … val secondAdapter: SecondAdapter = … val thirdAdapter: ThirdAdapter = … val concatAdapter= ConcatAdapter(firstAdapter, secondAdapter, thirdAdapter) recyclerView.adapter = concatAdapter
RecyclerView
將會按 Adapter 順序顯示全部的項目。ide
使用不一樣的適配器可使您更好地區分列表的每一個部分。例如,若是要顯示一個頭部,能夠將其封裝在它本身的 Adapter 中,而無需把頭部的邏輯與處理列表顯示的 Adapter 混雜在一塊兒。函數
△ RecyclerView 和 Adapter 數據佈局
咱們能夠在頭部或底部顯示一個進度條或錯誤信息。列表成功加載數據後,頭部或底部便不該該再顯示任何信息。這樣一來,它們就能夠用 Adapter 實現有 0 個或 1 個項目的列表:動畫
val concatAdapter = ConcatAdapter(headerAdapter, listAdapter, footerAdapter) recyclerView.adapter = concatAdapter
若是頭部和底部用的是同一佈局、ViewHolder 和 UI 邏輯 (例如: 進度條要什麼時候顯示、怎麼顯示),您能夠只實現一個 Adapter,而後建立兩個實例: 一個做爲頭部、一個做爲底部。google
要得到完整的實現,請查看這裏 拉取請求,它添加了:spa
默認狀況下,每一個 Adapter 維護它們本身的 ViewHolder 池,在 Adapter 之間不會進行復用。但若是多個 Adapter 使用的是同一種 ViewHolder,咱們可能會想要在 Adapter 間複用 ViewHolder 的實例。咱們能夠在構造 ConcatAdapter 時使用一個 ConcatAdapter.Config 對象來實現這樣的效果。只要設置 isolateViewTypes = false,就可讓全部合併進來的 Adapter 使用同一個視圖池。在顯示加載狀態的頭部和底部的例子中,兩種 ViewHolder 事實上使用的是相同的內容,因此咱們能夠複用它們。
⚠️ 若是要支持不一樣的 ViewHolder 類型,您應該實現 Adapter.getItemViewType) 方法。當您複用 ViewHolder 時,確保同一視圖類型沒有對應不一樣的 ViewHodler!防止出現這個問題的最佳實踐之一,即是將佈局 ID 做爲視圖類型返回。
<!-- Copyright 2019 Google LLC. SPDX-License-Identifier: Apache-2.0 --> class HeaderAdapter() : RecyclerView.Adapter<LoadingStateViewHolderHeaderViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LoadingStateViewHolder(parent) } override fun getItemViewType(position: Int): Int { - return 0 + return R.layout.list_loading } } class FooterAdapter() : RecyclerView.Adapter<LoadingStateViewHolderFooterViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { return LoadingStateViewHolder(parent) } override fun getItemViewType(position: Int): Int { - return 0 + return R.layout.list_loading } }
相比於使用 stable id 搭配 notifyDataSetChanged,咱們更建議使用 Adapter 的特定通知事件,該事件能夠爲RecyclerView 提供更多有關數據集更改的信息,從而使 RecyclerView 能夠更有效率地更新 UI,同時也有更好的動畫效果。若是您正在使用 ListAdapter 的話,其內部會使用 DiffUtil 回調幫您處理通知事件。可是若是您須要使用 stable id,ConcatAdapter.Config 爲其提供了三種不一樣的配置: NO_STABLE_IDS、ISOLATED_STABLE_IDS 和 SHARED_STABLE_IDS。其中後面兩種須要您本身處理 Adapter 中的 stable id。您能夠查看 StableIdMode 文檔來得到更多關於其工做原理的信息。
當 ConcatAdapter
中的一個 Adapter 調用了通知函數時,ConcatAdapter
會在更新 RecyclerView
以前計算新的項目位置。
從 RecyclerView
的角度來看,notifyItemRangeChanged 表示更新的項目相同,只是內容有所更改;notifyDataSetChanged 表示先後數據之間沒有任何關係。所以,咱們沒法將 notifyDataSetChanged 映射到 notifyItemRangeChanged
中。
若是一個 Adapter 調用了 Adapter.notifyDataSetChanged
,則 ConcatAdapter
也會調用Adapter.notifyDataSetChanged,而不是 Adapter.notifyItemRangeChanged。與 RecyclerViews 同樣,咱們要選擇更精細的更新操做,通常狀況下避免調用 Adapter.notifyDataSetChanged()。也可使用自動執行此操做的 Adapter 實現,例如 ListAdapter 或 SortedList。
您可能使用過 ViewHolder.getAdapterPosition) 來得到 Adapter 中某個 ViewHolder
的位置。如今,由於咱們合併了多個 Adapter,做爲代替,您須要調用 ViewHolder.getBindingAdapterPosition())。若是您想在共享 ViewHolder
的狀況下得到最後一個綁定某個 ViewHolder
的 Adapter,可使用 ViewHolder.getBindingAdapter())。
以上就是所有了!總結一下: 若是要順序顯示不一樣類型的數據的同時,也但願這些數據可以封裝在它們本身的 Adapter 中,請開始使用 ConcatAdapter
;若是想要更進一步對 ViewHolder
池和 statle id 進行高級控制,則要使用 ConcatAdapter.Config
。