在項目開發中,總離不開列表,說到列表,就會有無窮無盡的 Adapter 須要你去實現。從而出現了不少優秀的 adapter 庫。 不過不少庫功能都很全面了,有不少個類,不少功能,可是實際上只用其中的一兩個我感受是不少人的現狀。因此本身實現一個使用起來順手的是一個不錯的選擇,特別是用在一些不是很複雜的列表時。java
先看看使用效果,激發一下你爲數很少繼續閱讀的激情:git
//單類型列表,默認 LinearLayoutManager
recycleView.setup<NumberInfo> {
dataSource(initData())
adapter {
addItem(R.layout.layout_item) {
bindViewHolder { data, _, _ ->
setText(R.id.number, data?.number.toString())
}
}
}
}
//多類型列表
recycleView.setup<Any> {
withLayoutManager { LinearLayoutManager(context) }
dataSource(data)
adapter {
addItem(R.layout.item_setion_header) {
isForViewType { data, _ -> data is SectionHeader }
bindViewHolder { data, _, _ ->
val header = data as SectionHeader
setText(R.id.section_title, header.title)
}
}
addItem(R.layout.item_user) {
isForViewType { data, _ -> data is User }
bindViewHolder { data, _, _ ->
val user = data as User
setText(R.id.name, user.name)
setImageResource(R.id.avatar, user.avatarRes)
//若是你的控件找不到方便賦值的方法,能夠經過 findViewById 去查找
val phone = findViewById<TextView>(R.id.phone)
phone.text = user.phone
}
}
}
}
複製代碼
嗯....,感受還能夠,最少的狀況下能夠把一個列表代碼用 10 幾行就完成了。github
先貼完整代碼地址,沒地址的文章是沒靈魂的:EfficientAdapter
我把它命名爲 EfficientAdapter ,意爲高效的意思,事實上它只有 3 個文件。
至於如何使用,在地址上已經描述了,因此這篇文章主要是講一下實現的思路。app
對 Adapter 的封裝,其實無非就是對 Adapter 裏面的幾個回調方法進行封裝罷了,最經常使用的方法是先定義好一個存放 ViewHolder 的列表,而後在各個回調中獲取這些 ViewHolder,而後實現邏輯。框架
那麼其中最操蛋的是哪一個回調方法的封裝呢?我認爲是 getItemViewType。事實上你能夠在不少框架中看到讓你實現獲取 ViewType 的回調方法。ide
一步一步來,先說 ViewHolder 的封裝函數
在 EfficientAdapter 裏面,我把 ViewHolder 的封裝寫成了 BaseViewHolder:ui
class BaseViewHolder(parent: ViewGroup, resource: Int) : RecyclerView.ViewHolder(
LayoutInflater.from(parent.context).inflate(resource, parent, false)
)
複製代碼
這就是個人封裝,夠簡單吧。this
想什麼呢,固然沒這麼簡單,想要在上面使用效果代碼中那樣實現 ViewHolder 的具體邏輯,還須要有 isForViewType,bindViewHolder 等方法。因此我要定義一個類,去提供這些方法:spa
abstract class ViewHolderCreator<T> {
abstract fun isForViewType(data: T?, position: Int): Boolean
abstract fun getResourceId(): Int
abstract fun onBindViewHolder( data: T?, items: MutableList<T>?, position: Int, holder: ViewHolderCreator<T> )
var itemView: View? = null
fun registerItemView(itemView: View?) {
this.itemView = itemView
}
fun <V : View> findViewById(viewId: Int): V {
checkItemView()
return itemView!!.findViewById(viewId)
}
private fun checkItemView() {
if (itemView == null) {
throw NullPointerException("itemView is null")
}
}
}
複製代碼
在 ViewHolderCreator 中,getResourceId 和 onBindViewHolder 方法相信都知道是幹嗎的,而 isForViewType 方法是用來判斷 ViewType 的,注意它返回類型是 Boolean,這個方法會在下面講到。由於我想在 onBindViewHolder 中能方便的拿到 view,因此有了 registerItemView 和 findViewById 等其餘方法。
以上就是 ViewHolder 的全部封裝,接下來就對 Adapter 的封裝。
open class EfficientAdapter<T> : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
var items: MutableList<T>? = mutableListOf()
private val typeHolders: SparseArrayCompat<ViewHolderCreator<T>> = SparseArrayCompat()
}
複製代碼
Adapter 首先須要一個泛型用來表示傳入的實體類類型,定義了一個 item 列表用來作數據源。ViewHolder 的集合使用一個 SparseArrayCompat 去存儲。之因此用 SparseArray ,是由於我想把 ViewType 作爲 key。
因此,在 onCreateViewHolder 回調方法中,須要根據 viewType 參數在 typeHolders 中取到具體的 ViewHolderCreator:
private fun getHolderForViewType(viewType: Int): ViewHolderCreator<T>? {
return typeHolders.get(viewType)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
val holder = getHolderForViewType(viewType)
?: throw NullPointerException("No Holder added for ViewType $viewType")
return BaseViewHolder(parent, holder.getResourceId())
}
複製代碼
這樣,就能夠經過 getHolderForViewType 方法,在 typeHolders 中獲取到對應的 ViewHolderCreator,而後根據 ViewHolderCreator 中的信息去建立一個新的 ViewHolder。若是找不到,就拋一個空指針異常。
一樣道理,onBindViewHolder 回調方法也能夠這麼作:
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int) {
onBindViewHolder(viewHolder, position, mutableListOf())
}
override fun onBindViewHolder(viewHolder: RecyclerView.ViewHolder, position: Int, payloads:MutableList<Any>) {
val holder = getHolderForViewType(viewHolder.itemViewType)
?: throw NullPointerException("No Holder added for ViewType " + viewHolder.itemViewType)
holder.registerItemView(viewHolder.itemView)
holder.onBindViewHolder(items?.get(position), items, position, holder)
}
複製代碼
注意的是 onBindViewHolder 回調方法有兩個,他們的區別就不說了,這裏兩個都實現了邏輯,固然你也能夠只實現一個。
還剩下 getItemCount 和 getItemViewType 回調方法了,getItemCount 其實沒什麼好說的:
override fun getItemCount(): Int = items?.size ?: 0
複製代碼
先不說如何實現 getItemViewType ,先說說怎麼添加數據到 typeHolders 中:
fun register(holder: ViewHolderCreator<T>) = apply {
var viewType: Int = typeHolders.size()
while (typeHolders.get(viewType) != null) {
viewType++
}
typeHolders.put(viewType, holder)
}
複製代碼
typeHolders 的類型是 SparseArrayCompat,這裏我用 ViewType 做爲 key,register 方法中,能夠看到沒每註冊一次,viewType 就自動加一(由於 typeHolders 的長度會變長),達到了不會重複的效果,到時候在實現 getItemViewType 的時候,就直接取出來便可。避免了具體業務的干擾。
最後看看 getItemViewType 的實現:
override fun getItemViewType(position: Int): Int {
if (items == null) {
throw NullPointerException("adapter data source is null")
}
for (i in 0 until typeHolders.size()) {
val holder = typeHolders.valueAt(i)
val data = items?.getOrNull(position)
if (holder.isForViewType(data, position)) {
return typeHolders.keyAt(i)
}
}
//找不到匹配的 viewType
throw NullPointerException(
"No holder added that matches at position=$position in data source")
}
複製代碼
該方法的思路是經過遍歷 typeHolders,經過 ViewHolderCreator 的 isForViewType 方法來判斷是否符合條件,若是符合,則在 typeHolders 中取出 viewType 出來返回。
由於 typeHolders 中的 viewType 是自增的,因此 getItemViewType 的返回值會是 0,1,2,3...
isForViewType 在實際中如何實現?
舉個例子:
若是你的數據源由多個實體類組成,好比:
private List<Object> data = new ArrayList<>();
data.add(new User("Marry", 17, R.drawable.icon2, "123456789XX"));
data.add(new SectionHeader("My Images"));
data.add(new Image(R.drawable.cover1));
複製代碼
那麼在構建 EfficientAdapter 時,泛型傳入的天然是 Object,而後在 isForViewType 方法中你能夠這樣區分類型:
// 表明這是 User 類型
public boolean isForViewType(Object data, int position) {
return data instanceof User;
}
// 表明這是 SectionHeader 類型
public boolean isForViewType(Object data, int position) {
return data instanceof SectionHeader;
}
// 表明這是 Image 類型
public boolean isForViewType(Object data, int position) {
return data instanceof Image;
}
複製代碼
若是你的數據源只有一個實體類,可是實體類裏面有某個字段能夠區分類型,你能夠這樣:
// 表明這是 User 類型
public boolean isForViewType(ListInfo data, int position) {
return data.type = ListInfo.USER
}
// 表明這是 SectionHeader 類型
public boolean isForViewType(ListInfo data, int position) {
return data.type = ListInfo.HEADER
}
// 表明這是 Image 類型
public boolean isForViewType(ListInfo data, int position) {
return data.type = ListInfo.IMAGE
}
複製代碼
其餘狀況能夠根據具體的狀況而定。
到這裏,已經完成 Adapter 的封裝了,接下來能夠定義一些數據源的增刪查改的方法,好比:
//綁定 RecyclerView
fun attach(recyclerView: RecyclerView) = apply { recyclerView.adapter = this }
//提交數據
fun submitList(list: MutableList<T>) {
this.items?.clear()
this.items?.addAll(list)
notifyDataSetChanged()
}
複製代碼
到這裏,已經能夠簡單粗暴的使用了:
adapter = EfficientAdapter<SectionHeader>()
.register(object : ViewHolderCreator<SectionHeader>() {
override fun isForViewType(data: SectionHeader?, position: Int) = data != null
override fun getResourceId() = R.layout.item_setion_header
override fun onBindViewHolder( data: SectionHeader?, items: MutableList<SectionHeader>?, position: Int, holder: ViewHolderCreator<SectionHeader> ) {
setText(R.id.section_title, data.title)
}
}).attach(recycle_view)
adapter?.submitList(data)
複製代碼
但和使用效果差異有點大啊。因此,接下來就是 kotlin 發揮的時候了。
相信學過 kotlin 的都知道這兩個東西,他們能夠爲咱們的代碼提供更多的可能。
因爲 ViewHolderCreator 是一個抽象類,對它進行 DSL 封裝須要一個默認的實現類(也許能夠直接封裝,可是我只能想到這種方法):
class ViewHolderDsl<T>(private val resourceId: Int) : ViewHolderCreator<T>() {
private var viewType: ((data: T?, position: Int) -> Boolean)? = null
private var viewHolder: ((data: T?, position: Int, holder: ViewHolderCreator<T>) -> Unit)? = null
fun isForViewType(viewType: (data: T?, position: Int) -> Boolean) {
this.viewType = viewType
}
fun bindViewHolder(holder: (data: T?, position: Int, holder: ViewHolderCreator<T>) -> Unit) {
viewHolder = holder
}
override fun isForViewType(data: T?, position: Int): Boolean {
return viewType?.invoke(data) ?: (data != null)
}
override fun getResourceId() = resourceId
override fun onBindViewHolder( data: T?, items: MutableList<T>?, position: Int, holder: ViewHolderCreator<T>, payloads: MutableList<Any> ) {
viewHolder?.invoke(data, position, holder)
}
}
複製代碼
代碼比較清晰,就是對三個抽象方法的實現。因爲 getResourceId 比較簡單,因此直接放在構造方法中傳值就好。
實現好了 ViewHolderDsl,咱們給 EfficientAdapter 定義一個擴展函數,用 DSL 的方式去調用 register 方法:
fun <T : Any> EfficientAdapter<T>.addItem(resourceId: Int, init: ViewHolderDsl<T>.() -> Unit) {
val holder = ViewHolderDsl<T>(resourceId)
holder.init()
register(holder)
}
複製代碼
比較簡單,就是建立好 ViewHolderDsl 後,調用 register 方法便可。
到這裏,其實已經能夠用了,只要咱們再寫一個函數,用 DSL 的方式建立 Adapter 便可:
fun <T : Any> efficientAdapter(init: EfficientAdapter<T>.() -> Unit): EfficientAdapter<T> {
val adapter = EfficientAdapter<T>()
adapter.init()
return adapter
}
複製代碼
因此上面那個簡單粗暴的示例代碼就能夠變成這樣:
adapter = efficientAdapter<Any> {
addItem(R.layout.item_setion_header) {
isForViewType { it != null }
bindViewHolder { data, _, _ ->
setText(R.id.section_title, data.title)
}
}
}.attach(recycle_view)
adapter?.submitList(data)
複製代碼
代碼又清晰和簡單了不少。因爲在 ViewHolderDsl 中,isForViewType 的默認實現是 data!=null,因此若是是單類型列表,這個方法能夠直接不寫。
雖然代碼簡單了不少,但這樣總要定義 adapter 對象和綁定 RecycleView,因此更加優雅的方式就是給 RecycleView 定義一個擴展函數,把這些操做都包裝起來。
首先咱們實現一個叫 RecycleSetup 的類,在這個類裏面,把 RecycleView 的配置以及 Adapter 操做,數據源操做等統統包裝起來:
class RecycleSetup<T> internal constructor(private val recyclerView: RecyclerView) {
var items = mutableListOf<T>()
var adapter: EfficientAdapter<T>? = null
var context = recyclerView.context
fun dataSource(items: MutableList<T>) {
this.items.clear()
this.items = items
}
fun withLayoutManager(init: RecycleSetup<T>.() -> RecyclerView.LayoutManager) =
apply { recyclerView.layoutManager = init() }
fun adapter(init: EfficientAdapter<T>.() -> Unit) {
this.adapter = EfficientAdapter()
init.invoke(adapter!!)
recyclerView.adapter = adapter
adapter?.submitList(this.items)
}
fun submitList(list: MutableList<T>) {
this.items.clear()
this.items = list
adapter?.submitList(this.items)
}
fun getItem(position: Int): T = items[position]
}
複製代碼
代碼簡單,相信你們都能看懂。
有了這個類,最後,就能夠給 RecycleView 實現擴展函數了:
fun <T> RecyclerView.setup(block: RecycleSetup<T>.() -> Unit): RecycleSetup<T> {
val setup = RecycleSetup<T>(this).apply(block)
if (layoutManager == null) {
layoutManager = LinearLayoutManager(context)
}
return setup
}
fun <T> RecyclerView.submitList(items: MutableList<T>) {
if (adapter != null && adapter is EfficientAdapter<*>) {
(adapter as EfficientAdapter<T>).submitList(items)
}
}
複製代碼
layoutManager 爲空就默認實現 LinearLayoutManager。最後,上面那個簡單粗暴的代碼就能夠寫成跟一開始說那個效果同樣了:
recycleView.setup<SectionHeader> {
adapter {
addItem(R.layout.item_setion_header) {
bindViewHolder { data, _, _ ->
setText(R.id.section_title, data.title)
}
}
}
}
recycleView.submitList(data)
複製代碼
完整代碼和例子都在這裏 EfficientAdapter ,有興趣能夠看看。
總結
其實,整篇文章的代碼思路都比較簡單,其中比較有意思的是 viewType 自加一這裏,在使用的時候用戶只須要實現 isForViewType 便可,固然相比各個大佬們的庫,這個算是比較簡單的,大佬們閉上眼睛都能寫出來,因此寫這篇文章的緣由是分享本身在封裝代碼的時候的一個思路,一步一步從零到有。相信不少人都須要這種東西,比成天搬磚有意思,也會學到一點點知識。
Thank you for 看完整篇文章,完!