RecyclerView
也是一個非常特別值得獨立一篇來介紹的 ViewGroup
,幾乎每種 app 都會有列表式的資料,無論是 Facebook 的 feed、Gmail 的郵件清單或是Google Play 的 app 列表都會有這種需求。
這種無窮無盡列表式的畫面如果要用一般 xml 來表示幾乎是不可能的而且也很不切實際。RecyclerView
的存在就是要解決這個問題,就字面上大家或許可以猜到我們將會有某種方式可以重複利用已經存在的 View
,我們就一起來看他是怎麼實作的吧。
開始之前一樣是要加 gradle 的 dependencies。
dependencies {
implementation 'androidx.recyclerview:recyclerview:1.1.0-beta04'
}
整個流程由以下 RecyclerView
、RecyclerView.Adapter
RecyclerView.ViewHolder
三個 class 合力呈現。
RecyclerView
可以說是一個 container 來存放我們所有子 View
的地方,基本使用就跟一般的 ViewGroup
一樣,而且因為所有的子 View
都會動態綁定,所以在 xml 的設定也很單純,我們可以把 activity_main.xml
改成如下的程式碼:
<?xml version="1.0" encoding="utf-8"?>
<androidx.recyclerview.widget.RecyclerView
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
</androidx.recyclerview.widget.RecyclerView>
ViewHolder
就是我們儲存 View
reference 的地方,大家也可以把它當成一個儲存 View
的 class。
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
}
雖然看起來空空的,不過我們之後有需要還可以在這個 class 基礎上加其他的 function。
Adapter
是我們介接 data 跟 View
的地方,幾乎所有的工作都在這裡完成,當我們建立一個空的 Adapter
大概是這樣子:
class MyAdapter : RecyclerView.Adapter<MyViewHolder>() {
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
// 建立 ViewHolder 的地方,如果有同時支援多種 layout 的需求,
// 可以複寫 getItemViewType function,
// 這個 function 就可以拿到不同的 viewType 以供我們識別。
}
override fun getItemCount(): Int {
// 回傳整個 Adapter 包含幾筆資料。
}
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// 因為 ViewHolder 會重複使用,
// 我們要在這個 function 依據 position
// 把正確的資料跟 ViewHolder 綁定在一起。
}
}
因為 Adapter
需要綁定 View
我們先回到 app/src/main/res/layout 目錄下建立 item_myholder.xml
如下:
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="wrap_content">
</TextView>
onCreateViewHolder
是我們建立 MyViewHolder
實體的地方,一般只需要直接回傳一個 ViewHolder
即可,系統會依據螢幕大小來呼叫我們的 function 決定建立幾個 ViewHolder
。
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder {
return MyViewHolder(
LayoutInflater.from(parent.context)
.inflate(
R.layout.item_myholder,
parent,
false
)
)
}
我們先建立一組假資料放在 MyAdapter
如下:
private val data = (1..100).toList()
有了這組資料,getItemCount
跟 onBindViewHolder
就有資料可以綁定了,將來我們可以把資料改成從外部設值。
override fun getItemCount() = data.size
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
holder.setData(data[position])
}
大家有發現
getItemCount
函數結構不見了嗎?這是因為在 kotlin 裡我們可以把 function 當作變數使用,甚至也可以當參數傳到其他地方,這個技術叫做 functional programming。更多資訊可以參考這裡:https://kotlinlang.org/docs/tutorials/kotlin-for-py/functional-programming.html
onBindViewHolder
主要的任務是把 ViewHolder
跟 data 做綁定,因為 ViewHolder
等同於 View
,所以也可以視為把 View
跟 data 做綁定的地方,RecyclerView
會在子 View
滑出螢幕外的時候把 View
回收給其他需要使用的地方,所以當實際要呈現的畫面比目前螢幕大,不斷滑動的時候這個 function 會被呼叫很多次。
眼尖的讀者會發現我們把
onBindViewHolder
綁定資料的工作又丟回去給ViewHolder
了,這是因為ViewHolder
比Adapter
更適合做View
的綁定與設值呦!大家可以慢慢體會。
回頭修改 MyViewHolder
如下:
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
private val textView: TextView = itemView.findViewById(R.id.myTextView)
fun setData(data: Int) {
textView.text = "This is #$data Row"
}
}
最後還有個小設定,我們還需要 LayoutManager
來指定 RecyclerView
排列的方式,常用的有三種:LinearLayoutManager
、GridLayoutManager
、StaggeredGridLayoutManager
,當然你也可以自己客製化自己的版本。
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
回到我們的 MainActivity
,把 onCreate
改成以下的程式碼:
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val recyclerView = findViewById<RecyclerView>(R.id.recyclerView)
recyclerView.adapter = MyAdapter()
recyclerView.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false)
}
最後就是把它 build 起來在手機上看看效果如何囉!沒意外的話畫面會長得像下圖這樣子,下一篇我們會繼續 深入探討 RecyclerView,不見不散囉~~
大家也可以試著 debug 看看我們的
MyAdapter
裡面的onCreateViewHolder
被呼叫幾次以及onBindViewHolder
被呼叫幾次(可以搭配滑動螢幕),非常有助於了解整個RecyclerView
運作的機制。
Android 十全大補已經正式出書上架囉!
有興趣的讀者歡迎參考:
https://www.tenlong.com.tw/products/9789864345786
請問如果
override fun getItemCount() = 3
那麼所呈現的資料永遠只會顯示前3筆嗎?
是的
謝謝