前言
RecyclerView出來很久了,可以說一出來就將ListView給比下去了,當然,Recyclerview有它的好,ListView的好,并不是說一定要用Recyclerview,最適用自己項目的才是最好的。
在這里我們將用Kotlin來實現RecyclerView的多種item布局,和單個item布局,同時寫一個通用的Adapter。
使用
先將寫完的代碼的使用方式展示一下:
一種item布局
class SingleItemAdapter(mContext: Context, mDatas: List<TestBean>)
: DelegateItemAdapter<TestBean>(mContext, mDatas) {
init {
addItemViewDelegate(SingleItemDelegate())
}
}
效果圖
多種item布局
class MultiItemAdapter(mContext: Context, mDatas: List<TestBean>)
: DelegateItemAdapter<TestBean>(mContext, mDatas) {
init {
addItemViewDelegate(LeftDelegate())
addItemViewDelegate(CenterDelegate())
addItemViewDelegate(RightDelegate())
}
}
效果圖
梳理
總體流程是這樣的,首先創建itemView,在里面設置layoutId和數據處理,然后創建一個類繼承DelegateItemAdapter
,并在主構造方法里面添加不同的itemView,然后Adapter通過DelegateManager
類來管理對應的itemView進行操作。
ItemView
我們的itemView是實現DelegateType
接口,然后在里面設置相對應的layoutId,對數據進行操作處理:
class SingleItemDelegate : DelegateType<TestBean> {
override val itemViewLayoutId: Int
get() = R.layout.item_left
override fun isItemViewType(item: TestBean, position: Int): Boolean = true
override fun convert(context: Context, holder: ViewHolder, item: TestBean, position: Int) {
with(holder.itemView) {
item_left_text.text = item.text
setOnClickListener {
context.toast("SingleItemDelegate")
}
}
}
}
ViewHolder
在使用Recyclerview的時候,必須有ViewHolder,通常情況下,我們需要寫一個通用的ViewHolder,但是在Kotlin中的話,就不需要那樣寫,因為Kotlin可以直接將布局的id來當成變量使用,所以我們需要寫一個ViewHolder類來繼承Recyclerview里面的Viewholder類。
使用id當變量的話,要在app
下面的build.gradle
里面加個plugin: 'kotlin-android-extensions'
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
companion object {
/**
* 創建ViewHolder
*
* @param itemView itemView
* @return ViewHolder
*/
fun createViewHolder(itemView: View): ViewHolder {
val holder = ViewHolder(itemView)
return holder
}
/**
* 創建ViewHolder
*
* @param context Context
* @param parent ViewGroup
* @param layoutId layoutId
* @return ViewHolder
*/
fun createViewHolder(context: Context,
parent: ViewGroup, layoutId: Int): ViewHolder {
val itemView = LayoutInflater.from(context).inflate(layoutId, parent,
false)
val holder = ViewHolder(itemView)
return holder
}
}
}
這樣子ViewHolder就搞定了,不需要在寫那些設置text、image、綁定事件相關的代碼了。
Adapter
那么我們改如何區分多種itemView呢,在這里我們引用了一個接口:
/**
* itemView屬性
*/
interface DelegateType<in T> { // 由于T只是作為參數,所以T是contravariant逆變,加in
/**
* 獲取layoutId
*/
val itemViewLayoutId: Int
/**
* 判斷類型
*
* @param item data數據
* @param position 當前position
* @return true顯示數據
*/
fun isItemViewType(item: T, position: Int): Boolean
/**
* 顯示數據
*
* @param context Context
* @param holder ViewHolder
* @param item data數據
* @param position 當前position
*/
fun convert(context: Context, holder: ViewHolder, item: T, position: Int)
}
根據layoutId來創建對應的ViewHolder,并且通過isItemViewType
方法來匹配是否是當前itemView類型,然后通過convert來顯示數據。
Adapter是繼承Recyclerview里面的Adapter,傳入ViewHolder,傳入泛型數據集,那么我們可以通過傳入的數據集里面的類型來判斷不同的itemView,在這里需要重寫幾個方法:
getItemViewType方法
根據數據來返回不同類型:
/**
* 獲取itemView類型
*
* @param position 當前position
*/
override fun getItemViewType(position: Int): Int = if (!useItemViewDelegateManager())
super.getItemViewType(position)
else
mDelegateManager.getItemViewType(mDatas[position], position)
onCreateViewHolder方法
根據mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId
返回的layoutId來生成對應的ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder.createViewHolder(mContext, parent, mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId)
onBindViewHolder方法
用于數據處理和顯示:
override fun onBindViewHolder(holder: ViewHolder, position: Int) = convert(holder, mDatas[position])
上面的mDelegateManager
是管理itemView的類,可以添加刪除itemView,并且顯示數據:
/**
* 獲取itemView的類型
*
* @param item data數據
* @param position 當前position
* @return Int
*/
fun getItemViewType(item: T, position: Int): Int {
(0.. delegateCount - 1)
.filter { mDelegates.valueAt(it).isItemViewType(item, position) }
.forEach { return mDelegates.keyAt(it) }
throw IllegalArgumentException(
"No ItemViewDelegate added that matches position = $position in data = $item source");
}
/**
* 顯示數據
*
* @param context Context
* @param holder ViewHolder
* @param item data數據
* @param position 當前position
*/
fun convert(context: Context, holder: ViewHolder, item: T, position: Int) {
for (i in 0..delegateCount - 1) {
val delegate = mDelegates.valueAt(i)
if (delegate.isItemViewType(item, position)) {
delegate.convert(context, holder, item, position);
return@convert
}
}
throw IllegalArgumentException(
"No ItemViewDelegateManager added that matches position= $position in data = $item source")
}
/**
* 添加itemView
*
* @param delegate itemView
* @throws IllegalArgumentException 已存在不能再添加
* @return DelegateManager
*/
fun addDelegate(delegate: DelegateType<T>): DelegateManager<T> {
for (i in 0..delegateCount - 1) {
if (mDelegates.valueAt(i).itemViewLayoutId == delegate.itemViewLayoutId) {
throw IllegalArgumentException("An ItemViewDelegate is already registered for the delegate = $delegate.")
}
}
mDelegates.put(mDelegates.size(), delegate)
return this
}
/**
* 添加itemView
*
* @param viewType 代表itemView的下標
* @param delegate itemView
* @throws IllegalArgumentException 已存在不能再添加
* @return DelegateManager
*/
fun addDelegate(viewType: Int, delegate: DelegateType<T>): DelegateManager<T> {
if (mDelegates.get(viewType) != null) {
throw IllegalArgumentException("An ItemViewDelegate is already registered for the viewType = $viewType. Already registered ItemViewDelegate is ${mDelegates.get(viewType)}")
}
mDelegates.put(viewType, delegate)
return this
}
/**
* 獲取itemView
*
* @param viewType 代表itemView的下標
* @return DelegateType
*/
fun getItemViewDelegate(viewType: Int): DelegateType<T> = mDelegates.get(viewType)
/**
* 獲取itemView的LayoutId
*
* @param viewType 代表itemView的下標
* @return Int
*/
fun getItemViewLayoutId(viewType: Int): Int = getItemViewDelegate(viewType).itemViewLayoutId
/**
* 獲取itemView的類型
*
* @param delegate itemView
* @return Int
*/
fun getItemViewType(delegate: DelegateType<T>): Int = mDelegates.indexOfValue(delegate)
通過使用addDelegate
方法添加itemView,然后通過getItemViewType
方法來獲取itemView的類型,最后通過convert
方法來進行數據操作和顯示,這樣子我們的Adapter就寫出來了。
總結
每次寫Recyclerview的時候都要重復寫這些Adapter,ViewHolder的代碼,這里將它寫成通用的,可以省去很多的時間。
注意:項目是在3.0版本的Android Studio上運行。
源碼:Github,歡迎star。