前言
RecyclerView是一款功能十分強(qiáng)大的widget顷牌,涉及到列表呈現(xiàn)時(shí)帜平,通常都會(huì)使用到RecyclerView瀑焦,功能如此強(qiáng)大的widget不做一下記錄實(shí)在可惜蔫浆。
Android官方指導(dǎo)文檔有詳細(xì)的介紹如何使用RecyclerView創(chuàng)建動(dòng)態(tài)列表碑宴,具體參考如下地址
https://developer.android.com/guide/topics/ui/layout/recyclerview?hl=zh-cn思考任何問(wèn)題時(shí)软啼,我們都需要回到問(wèn)題的本質(zhì)。通過(guò)RecyclerView 的源碼我們可以知道RecyclerView是一個(gè)容器延柠,用于顯示列表或網(wǎng)格形式的數(shù)據(jù)祸挪,比如文本或照片。
1. 實(shí)現(xiàn)RecyclerView
1.1 RecyclerView布局
- 布局
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent">
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
<com.google.android.material.floatingactionbutton.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_margin="16dp"
android:contentDescription="@string/fab_content_description"
android:src="@drawable/ic_add_black_24dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>
在Activity
中的設(shè)置
- Activity
//初始使用
mRecyclerView = findViewById(R.id.recycler_view)
mRecyclerView.layoutManager = LinearLayoutManager(this)
mRecyclerView.adapter = mAdapter
1.2 規(guī)劃布局
組成RecyclerView
的表項(xiàng)可能有多種類型贞间,不同的類型會(huì)有不同的布局贿条。根據(jù)列表或網(wǎng)格的外觀,確定布局管理器增热,RecyclerView中的列表項(xiàng)由LayoutManager類負(fù)責(zé)排列整以。
- 可以擴(kuò)展
RecyclerView.LayoutManager
抽象類來(lái)創(chuàng)建自己的布局管理器 - 可以擴(kuò)展
RecyclerView.ItemAnimator
來(lái)定義自己的animator對(duì)象 - 可以擴(kuò)展
RecyclerView.ItemDecoration
來(lái)定義自己的分割線
同一個(gè)RecyclerView
可以綁定不同ViewType
類型的Item,可以定義接口如下峻仇,其他Item實(shí)現(xiàn)該接口
interface IWallpaperListItem {
val type: Int
}
class SourceItem : IWallpaperListItem {
override val type: Int
get() = ViewType.SOURCE
}
class RecommendItem(
val imageList: List<Image>? = null
) : IWallpaperListItem {
override val type: Int
get() =ViewType.WALLPAPER
}
1.3 實(shí)現(xiàn)Adapter和ViewHolder
-
a. 創(chuàng)建Adapter
只定義名字 -
b. 創(chuàng)建ViewHolder
創(chuàng)建ViewHolder
的內(nèi)部類公黑,并且它可以接收一個(gè)itemView
作為參數(shù)。然后創(chuàng)建bind
函數(shù)础浮,將數(shù)據(jù)和攜帶數(shù)據(jù)的UI關(guān)聯(lián)起來(lái)帆调。
abstract class AbsViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
abstract fun bind(wallpaperListItem: IWallpaperListItem)
}
class ***TitleViewHolder(itemView: View) : AbsViewHolder(itemView) {
override fun bind(wallpaperListItem: IWallpaperListItem) {
//綁定內(nèi)容
}
}
class ***SourceViewHolder(itemView: View) : AbsViewHolder(itemView) {
private val ***CardView = itemView.findViewById<CardView>(R.id.***_card_view)
private val ***CardView = itemView.findViewById<CardView>(R.id.***_card_view)
init {
//設(shè)置背景
//CardView.background =
***CardView.setOnClickListener {
Log.d(TAG, "onClick")
//點(diǎn)擊
}
//***View.background =
***CardView.setOnClickListener {
Log.d(TAG, "onClick")
//點(diǎn)擊
}
}
override fun bind(wallpaperListItem: IWallpaperListItem) {
//綁定
}
}
-
c. 繼承RecyclerView.Adapter
更新Adapter
類的定義,使其繼承RecyclerView.Adapter
類豆同,并且將ViewHolder
作為參數(shù)傳入
這里也可以改為繼承ListAdapter
class ***Adapter(
private val retryLoad: () -> Unit
) : ListAdapter<IWallpaperListItem, ***.AbsViewHolder>(***DiffCallback) {
-
d. 重寫(xiě)onCreateViewHolder()
當(dāng)ViewHolder
創(chuàng)建的時(shí)候會(huì)調(diào)用該方法番刊。在該方法里進(jìn)行初始化和填充RecyclerView
中的表項(xiàng)視圖。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): AbsViewHolder {
Log.d(TAG, "onBindViewHolder viewType=$viewType")
return when (viewType) {
ViewType.SOURCE -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.1_item, parent, false)
***SourceViewHolder(view)
}
ViewType.TITLE -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.2_title_item, parent, false)
***TitleViewHolder(view)
}
ViewType.WALLPAPER -> {
val view = LayoutInflater.from(parent.context).inflate(R.layout.3_recommend_item, parent, false)
***WallpaperViewHolder(view) { retryLoad() }
}
else -> {
throw IllegalStateException("view type is error")
}
}
}
-
e. 重寫(xiě)onBindViewHolder()
onBindViewHolder()
被調(diào)用的時(shí)候影锈,會(huì)傳入?yún)?shù)ViewHolder和位置position芹务,該位置可以用于提取表項(xiàng)所需的數(shù)據(jù),并且將數(shù)據(jù)傳遞給ViewHolder來(lái)使數(shù)據(jù)綁定到對(duì)應(yīng)的UI鸭廷。
override fun onBindViewHolder(holder: AbsViewHolder, position: Int) {
Log.d(TAG, "onBindViewHolder position=$position")
val ***ListItem = getItem(position)
holder.bind(***ListItem)
}
-
f. 重寫(xiě)getItemCount()
RecyclerView顯示一個(gè)列表枣抱,所以它需要知道列表里共有多少項(xiàng)。
ListAdapter
可以處理元素的添加和刪除而無(wú)需重繪視圖辆床,甚至可以為變化添加動(dòng)畫(huà)效果佳晶。
DiffUtil 是 ListAdapter 能夠高效改變?cè)氐膴W秘所在。DiffUtil 會(huì)比較新舊列表中增加讼载、移動(dòng)轿秧、刪除了哪些元素中跌,然后輸出更新操作的列表將原列表中的元素高效地轉(zhuǎn)換為新的元素。
為了能夠識(shí)別新的數(shù)據(jù)菇篡,DiffUtil 需要您重寫(xiě) areItemsTheSame() 和 areContentsTheSame()漩符。areItemsTheSame() 檢查兩個(gè)元素是否為同一元素。areContentsTheSame() 檢查兩個(gè)元素是否包含相同的數(shù)據(jù)驱还。
在Adapter類中添加DiffUtil對(duì)象嗜暴,并且復(fù)寫(xiě) areItemsTheSame()和areContentsTheSame()
object FlowerDiffCallback : DiffUtil.ItemCallback<Flower>() {
override fun areItemsTheSame(oldItem: Flower, newItem: Flower): Boolean {
return oldItem == newItem
}
override fun areContentsTheSame(oldItem: Flower, newItem: Flower): Boolean {
return oldItem.id == newItem.id
}
}
將Adapter的父類由RecyclerView.Adapter改為L(zhǎng)istAdapter,并傳入DiffCallback
class FlowersAdapter(private val onClick: (Flower) -> Unit) :
ListAdapter<Flower, FlowersAdapter.FlowerViewHolder>(FlowerDiffCallback) {
更新列表
ListAdapter 通過(guò) submitList() 方法獲取數(shù)據(jù)议蟆,該方法提交了一個(gè)列表來(lái)與當(dāng)前列表進(jìn)行對(duì)比并顯示闷沥。也就是說(shuō)您無(wú)需再重寫(xiě) getItemCount(),因?yàn)?ListAdapter 會(huì)負(fù)責(zé)管理列表咪鲜。
在 Activity 類中狐赡,調(diào)用 Adapter 的 submitList() 方法并傳入數(shù)據(jù)列表撞鹉。
此處需要通過(guò)toMutableList轉(zhuǎn)換生成一個(gè)新的list疟丙,否則不會(huì)更新,不同的item鸟雏,viewType不一樣享郊。
private fun updateListData(imageList: List<ImageData>?) {
Log.d(TAG, "updateListData imageList.size=${imageList?.size}")
mItemList.clear()
mItemList.add(SourceItem())
mItemList.add(TitleItem())
mItemList.add(RecommendItem(imageList))
mAddAdapter.submitList(mItemList.toMutableList())
}
1.4 連接
我們已經(jīng)創(chuàng)建了布局、數(shù)據(jù)列表和adapter孝鹊,可以直接將adapter賦給RecyclerView
2 響應(yīng)點(diǎn)擊事件
2.1 定義點(diǎn)擊動(dòng)作
在創(chuàng)建監(jiān)聽(tīng)器之前炊琉,在 Activity 類中添加一個(gè)函數(shù)用于處理點(diǎn)擊之后的響應(yīng)操作。
private fun adapterOnClick() {
val intent = Intent(this,DetailActivity()::class.java)
this.startActivity(intent)
}
接下來(lái)又活,修改 Adapter 的構(gòu)造函數(shù)來(lái)傳入 onClick() 函數(shù)苔咪。
class **Adapter(private val onClick: (Flower) -> Unit) :
ListAdapter<T, RecyclerView.ViewHolder>(DiffCallback())
在 Activity 類中,在初始化 Adapter 的時(shí)候傳入剛剛創(chuàng)建的點(diǎn)擊事件函數(shù)柳骄。
2.2 添加onClickHandler()
現(xiàn)在響應(yīng)處理已經(jīng)定義好了团赏,可以將它關(guān)聯(lián)到 Adapter 的 ViewHolder 了。
修改 ViewHolder耐薯,將 onClick() 作為參數(shù)傳入舔清。
class ViewHolder(itemView: View, val onClick: (Source) -> Unit) :
RecyclerView.ViewHolder(itemView)
在初始化的代碼中,調(diào)用 itemView 的 setOnClickListener{}
init {
itemView.setOnClickListener {
currentItem?.let {
onClick(it)
}
}
}
現(xiàn)在就可以響應(yīng)點(diǎn)擊事件了
3 ConcatAdapter
使用ConcatAdapter可以將多個(gè)adapter添加到RecyclerView曲初,ConcatAdapter會(huì)依次顯示多個(gè)Adapter的內(nèi)容体谒,有興趣可以了解
- ConcatAdapter
https://developer.android.google.cn/reference/androidx/recyclerview/widget/ConcatAdapter