前言
RecyclerView出來很久了文判,可以說一出來就將ListView給比下去了苞七,當(dāng)然,Recyclerview有它的好浪藻,ListView的好,并不是說一定要用Recyclerview乾翔,最適用自己項(xiàng)目的才是最好的爱葵。
在這里我們將用Kotlin來實(shí)現(xiàn)RecyclerView的多種item布局,和單個(gè)item布局反浓,同時(shí)寫一個(gè)通用的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())
}
}
效果圖
梳理
總體流程是這樣的,首先創(chuàng)建itemView雷则,在里面設(shè)置layoutId和數(shù)據(jù)處理辆雾,然后創(chuàng)建一個(gè)類繼承DelegateItemAdapter
,并在主構(gòu)造方法里面添加不同的itemView月劈,然后Adapter通過DelegateManager
類來管理對應(yīng)的itemView進(jìn)行操作度迂。
ItemView
我們的itemView是實(shí)現(xiàn)DelegateType
接口,然后在里面設(shè)置相對應(yīng)的layoutId猜揪,對數(shù)據(jù)進(jìn)行操作處理:
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的時(shí)候惭墓,必須有ViewHolder,通常情況下而姐,我們需要寫一個(gè)通用的ViewHolder腊凶,但是在Kotlin中的話,就不需要那樣寫拴念,因?yàn)镵otlin可以直接將布局的id來當(dāng)成變量使用钧萍,所以我們需要寫一個(gè)ViewHolder類來繼承Recyclerview里面的Viewholder類。
使用id當(dāng)變量的話丈莺,要在app
下面的build.gradle
里面加個(gè)plugin: 'kotlin-android-extensions'
class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
companion object {
/**
* 創(chuàng)建ViewHolder
*
* @param itemView itemView
* @return ViewHolder
*/
fun createViewHolder(itemView: View): ViewHolder {
val holder = ViewHolder(itemView)
return holder
}
/**
* 創(chuàng)建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就搞定了划煮,不需要在寫那些設(shè)置text、image缔俄、綁定事件相關(guān)的代碼了弛秋。
Adapter
那么我們改如何區(qū)分多種itemView呢器躏,在這里我們引用了一個(gè)接口:
/**
* itemView屬性
*/
interface DelegateType<in T> { // 由于T只是作為參數(shù),所以T是contravariant逆變蟹略,加in
/**
* 獲取layoutId
*/
val itemViewLayoutId: Int
/**
* 判斷類型
*
* @param item data數(shù)據(jù)
* @param position 當(dāng)前position
* @return true顯示數(shù)據(jù)
*/
fun isItemViewType(item: T, position: Int): Boolean
/**
* 顯示數(shù)據(jù)
*
* @param context Context
* @param holder ViewHolder
* @param item data數(shù)據(jù)
* @param position 當(dāng)前position
*/
fun convert(context: Context, holder: ViewHolder, item: T, position: Int)
}
根據(jù)layoutId來創(chuàng)建對應(yīng)的ViewHolder登失,并且通過isItemViewType
方法來匹配是否是當(dāng)前itemView類型,然后通過convert來顯示數(shù)據(jù)挖炬。
Adapter是繼承Recyclerview里面的Adapter揽浙,傳入ViewHolder,傳入泛型數(shù)據(jù)集意敛,那么我們可以通過傳入的數(shù)據(jù)集里面的類型來判斷不同的itemView馅巷,在這里需要重寫幾個(gè)方法:
getItemViewType方法
根據(jù)數(shù)據(jù)來返回不同類型:
/**
* 獲取itemView類型
*
* @param position 當(dāng)前position
*/
override fun getItemViewType(position: Int): Int = if (!useItemViewDelegateManager())
super.getItemViewType(position)
else
mDelegateManager.getItemViewType(mDatas[position], position)
onCreateViewHolder方法
根據(jù)mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId
返回的layoutId來生成對應(yīng)的ViewHolder
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder = ViewHolder.createViewHolder(mContext, parent, mDelegateManager.getItemViewDelegate(viewType).itemViewLayoutId)
onBindViewHolder方法
用于數(shù)據(jù)處理和顯示:
override fun onBindViewHolder(holder: ViewHolder, position: Int) = convert(holder, mDatas[position])
上面的mDelegateManager
是管理itemView的類,可以添加刪除itemView草姻,并且顯示數(shù)據(jù):
/**
* 獲取itemView的類型
*
* @param item data數(shù)據(jù)
* @param position 當(dāng)前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");
}
/**
* 顯示數(shù)據(jù)
*
* @param context Context
* @param holder ViewHolder
* @param item data數(shù)據(jù)
* @param position 當(dāng)前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的下標(biāo)
* @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的下標(biāo)
* @return DelegateType
*/
fun getItemViewDelegate(viewType: Int): DelegateType<T> = mDelegates.get(viewType)
/**
* 獲取itemView的LayoutId
*
* @param viewType 代表itemView的下標(biāo)
* @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
方法來進(jìn)行數(shù)據(jù)操作和顯示撩独,這樣子我們的Adapter就寫出來了敞曹。
總結(jié)
每次寫Recyclerview的時(shí)候都要重復(fù)寫這些Adapter,ViewHolder的代碼综膀,這里將它寫成通用的澳迫,可以省去很多的時(shí)間。
注意:項(xiàng)目是在3.0版本的Android Studio上運(yùn)行剧劝。
源碼:Github橄登,歡迎star。