[Google Course] Android Basics in Kotlin(第6篇) — Display a scrollable list with RecyclerView

Phoebe Huang
18 min readMar 9, 2021

--

這篇文章我將要介紹在我們手機上很常見的列表顯示,想想我們的電話簿、相簿,還有我們常用的社交軟體,都會看到相同類型的資料以列表方式呈現,所以列表的呈現方式是Android UI上很重要的一個工具!!

而Android 提供了一個方便的UI元件 — RecyclerView,為什麼我們要使用這個元件呢??根據Android 文件寫道:

RecyclerView recycles those individual elements. When an item scrolls off the screen, RecyclerView doesn’t destroy its view. Instead, RecyclerView reuses the view for new items that have scrolled onscreen. This reuse vastly improves performance, improving your app’s responsiveness and reducing power consumption. — Android RecyclerView

圖片來源:Google Course

上圖說明當我們滑到底部時,RecyclerView並沒有摧毀這個View的資源,取而代之的是RecyclerView 重複使用了這個View的資源,將新的清單項目加入,如此一來便改善了手機的效能,降低了電力的損耗,所以我們就快快來學習這個方便的模組吧!!

我們在這篇文章會學到以下:

加入 Android Material Design 函式庫
什麼是 Package
加入 model/data Class
RecyclerView

接著是QA:

QA

為了怕有些人沒有看之前的文章,文章中會使用到的編譯環境與參考內容也在這邊列出給大家:
Kotlin 線上編譯環境
Android Studio 安裝
Google Course 的 Android Basics in Kotlin

[加入 Android Material Design 函式庫]

首先我們先新增一個專案,名稱叫做 Affirmations ,接著讓我們加入Material dependencies 到我們的專案內:

  1. 打開專案的Gradle Scripts > build.gradle (Module: app) ,注意你可能會看到兩個 build.gradle,請小心的選擇到app的那一個gradle!!
  1. 找到implementation 'androidx.appcompat:appcompat:1.2.0' ,在下方加入implementation 'com.google.android.material:material:1.3.0'
  2. 完成後按下右上方的Sync 按鍵

接著我們加入要呈現在列表上的激勵人心字句的字串資源(resource),打開 app > res > values > strings.xml ,用以下的程式碼取代掉原本在strings.xml 的內容

<resources>
<string name="app_name">Affirmations</string>
<string name="affirmation1">I am strong.</string>
<string name="affirmation2">I believe in myself.</string>
<string name="affirmation3">Each day is a new opportunity to grow and be a better version of myself.</string>
<string name="affirmation4">Every challenge in my life is an opportunity to learn from.</string>
<string name="affirmation5">I have so much to be grateful for.</string>
<string name="affirmation6">Good things are always coming into my life.</string>
<string name="affirmation7">New opportunities await me at every turn.</string>
<string name="affirmation8">I have the courage to follow my heart.</string>
<string name="affirmation9">Things will unfold at precisely the right time.</string>
<string name="affirmation10">I will be present in all the moments that this day brings.</string>
</resources>

如此一來,我們就可以在其他程式碼中,以R.string.affirmation1 or R.string.affirmation2 來使用這些字句!!

[什麼是Package]

Package 是用來方便我們管理我們的程式碼,與其他開發人員進行更有效率的溝通,在Preject 視窗中選擇Android,我們會看到java資料夾內有三個Package,一個為我們的主要程式,兩個用來測試使用的

android package

可以注意到package 的名稱以 “.” 來做分割,並且開頭為小寫!!

我們在開始一個專案時,可以利用package 完善的設計我們的程式架構,我們可以依據程式碼的不同功能來分類他們應該是屬於哪一個package
,分別加入我們的邏輯程式,不僅以後自己要trace code 時可以節省很多時間,其他開發人員也容易理解程式架構~

因為我們的app只有資料(圖片、字串)、介面元件,所以我們只需要新增modeldataadapter (清單列表內的單一項目介面設計),現在我們就來新增package吧!

com.phoebe.affirmations 這個 package 點選右鍵-> New -> Package :

打上在對應路徑下要新增的package 名稱,記得有三個要新增:

目前專案應架構看起來應該會是這樣:

[加入 model/data class]

接著我們再來新增一個名稱為 Affirmation 的 model class ,這個類別是用來定義我們的數據長的樣子,並不實際儲存我們的數據,因此我們需要將它歸類在model這個package 中,在model 這個package 點選右鍵->New->Kotlin File/Class,輸入 Affirmation,並選擇Class:

將程式內容修改成以下程式碼:

data class Affirmation(val stringResourceId: Int)

接著我們就來準備我們的資料類別吧!!在data 這個package 點選右鍵->New->Kotlin File/Class

新增一個名為Datasource的Kotlin 類別,並將下面粗體字加入到類別中:

class Datasource() {
fun loadAffirmations(): List<Affirmation> {
return listOf<Affirmation>(
Affirmation(R.string.affirmation1),
Affirmation(R.string.affirmation2),
Affirmation(R.string.affirmation3),
Affirmation(R.string.affirmation4),
Affirmation(R.string.affirmation5),
Affirmation(R.string.affirmation6),
Affirmation(R.string.affirmation7),
Affirmation(R.string.affirmation8),
Affirmation(R.string.affirmation9),
Affirmation(R.string.affirmation10))
}

}

這邊在類別裡建立了一個函式名稱為 loadAffirmations ,並回傳List這個清單給呼叫此函式的人,List 後方的<> 內初始化這個清單內放置的是剛剛建立好的 model 物件類別 — Affirmation

[RecyclerView]

終於來到我們的重頭戲 RecyclerView ,RecyclerView 包含幾個部分:

  • item — 清單內所呈現的單一項目內容,是剛剛我們創建的 Affirmation 類別
  • Adapter — 獲取資料以及將資料呈現到對應的UI元件上
  • ViewHolders — 可以讓RecyclerView 所使用的一堆 affirmations
  • RecyclerView — 畫面的呈現
圖片來源:Google Course

[加入RecyclerView]

現在我們先來重新設定我們的UI介面:

  1. 打開res->layout->activity_main.xml
  2. 刪除 TextView
  3. 目前的介面主要是用ConstraintLayout來控制多個子元件,但是因為我們現在只需要單一個子元件,因此我們用 FrameLayout 取代 ConstraintLayout
  4. 加入recyclerview元件程式碼到.xml檔案,注意到我們將設定id、layout_width、layout_height、scollbars、layoutManager這些屬性以利後續設計:
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">

<androidx.recyclerview.widget.RecyclerView
android:id="@+id/recycler_view"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
app:layoutManager="LinearLayoutManager" />

</FrameLayout>

到目前為止,我們可以執行我們的程式,但是我們會發現畫面只有一個白色背景,因為我們還缺少了一些將資料塞到畫面元件裡的程式碼,那我們就繼續下去吧!

我們現在要做的就是將DataSource裡的Affirmation類型資料,顯示到RecyclerView上面

[Adapter]

那要如何將清單型資料顯示到我們的RecycleView,會使用到我們的Adapter 適應器,你可以把它想像成是一個用來將我們的資料在RecycleView做一個相對應的呈現的工具

而一個Adapter 又分為許多部分,因此接下來的程式碼相較這系列之前的文章會較為困難,不過沒關係,完成這篇文章的內容後,將來我們可以重複使用今天的程式碼來創建一個 RecyclerView,我們將Adapter分為四個部分:

設計RecyclerView 單一 Item 的Layout
新增 ItemAdapter
繼承及實做Adapter 的抽象介面
RecyclerView 設定 Adapter

[設計RecyclerView 單一 Item 的Layout]

  1. 在res->layout 點右鍵,點選新增 File ,輸入檔名為 list_item.xml
  2. 加入 TextViewlist_item.xml ,如下列程式碼 :
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/item_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />

我們注意到 list_item.xml 並沒有一個ViewGroup包著TextView,這是因為待會我們會將list_item 當成一個子元件 inflated (填充) 到 RecycleView

[新增 ItemAdapter]

接著我們在adapter這個Package 新增一個檔案名稱為 ItemAdapter

  1. 在adapter這個Package 點右鍵,點選新增Kotlin File/Class,輸入檔名為 ItemAdapter
  2. 加入下面粗體字到 ItemAdapter 類別內,如下列程式碼 :
class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>) {

class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
}

這邊我們加入的東西有兩個建構子參數及一個內部類別

a. private val context: Context
Context 儲存了App相關的資訊,因此我們將他傳入到ItemAdapter,而這個參數只有在類別可以使用,我們讓他的屬性為 private

b. private val dataset: List<Affirmation>
dataset 參數傳入ItemAdapter所需要的字串資訊,因為這個參數只有在類別可以使用,我們讓他的屬性為 private

c. class ItemViewHolder
RecyclerView 並不會直接與 view item 互動,而是與 ViewHolders,一個ViewHolder 代表一個能夠被重複使用的單一清單項目,這會使得更新Item的layout更加容易、有效率
因為 ItemViewHolder 只有被 ItemAdapter 使用,所以我們將位置放置在ItemAdapter 裡面,這樣子的類別稱為 Internal Class Nest Class,這並不強迫他一定要是內部類別,只是如果這要子設計我們的資料結構的話,那麼後續的開發人員也會更好理解!

[繼承及實做Adapter 的抽象介面]

接著我們要讓ItemAdapter 這個類別繼承RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() ,這樣我們能夠使用Android 設計好的Adapter 響應的事件!回憶一下之前的文章,在Kotlin 中繼承父類別只需要在類別名稱後方加上冒號再加上欲繼承之父類別名稱即可,在這個範例中,我們在ItemAdapter 參數後方繼承 RecyclerView.Adapter<ItemAdapter.ItemViewHolder>

class ItemAdapter(private val context: Context, private val dataset: List<Affirmation>): RecyclerView.Adapter<ItemAdapter.ItemViewHolder>() {

class ItemViewHolder(private val view: View) : RecyclerView.ViewHolder(view) {
val textView: TextView = view.findViewById(R.id.item_title)
}
}

而當繼承後,你會看到一些紅色毛毛蟲的錯誤訊息(滑鼠移到毛毛蟲後,如下圖),我們可以將父類別定義好的函式給覆寫掉,或是必須實作一些抽象函式:

點選 Implement members ,選擇所有需要實作的抽象函式:

Android Studio會自動幫我們新增所需要實作的抽象函式到類別中:

我們分別針對這三個函式做實作:

override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ItemViewHolder {
val adapterLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.list_item, parent, false)

return ItemViewHolder(adapterLayout)

}

override fun onBindViewHolder(holder: ItemViewHolder, position: Int) {
val item = dataset[position]
holder.textView.text = context.resources.getString(item.stringResourceId)

}

override fun getItemCount(): Int {
return dataset.size
}

a. 在 onCreateViewHolder 裡,我們透過LayoutInflater.from(parent.context) 取得LayoutInflater 的實體,設定這個ItemAdapter 使用 list_item.xml 來填充父元件 當創建這個 ItemAdapter 類別時,會呼叫此函式,我們就能夠取得一個ItemViewHolder 的實體物件!

b. 在 onBindViewHolder 裡,我們設定 textView.text 文字內容,主要是依據他們在dataset 的index 來決定

c. getItemCount 則是較單純的回傳dataset的大小

[RecyclerView 設定 Adapter]

接著就差一步,我們可以在畫面上看到我們的字串資源了!

打開 MainActivity.ktsetContentView(R.layout.activity_main) 下方新增一行空白行,將下列程式碼寫到 MainActivity.kt 內:

val myDataset = Datasource().loadAffirmations()

val recyclerView = findViewById<RecyclerView>(R.id.recycler_view)
recyclerView.adapter = ItemAdapter(this, myDataset)

recyclerView.setHasFixedSize(true)

接著可以執行我們的App,應該會看到如下的文字畫面:

下篇文章我們會繼續加入圖片到我們的RecycleView內,最終成品會是如下圖:

[QA]

● 使用Package有什麼優點?
● 若要使用其他Package內的類別(Class B),需要在Class A 匯入Class B? (O or X)#
● 使用RecycleView有什麼優點?
● 為什麼 RecycleView 需要一個 Adapter?

喜歡我的文章的人也記得幫我按個拍手、分享,覺得很不錯的可以幫我拍個50下!
也要快點追蹤我的 FB粉絲專頁 — 飛比尋常的程式設計世界 ,不會太頻繁出現在你的塗鴉牆騷擾你,好文章生產需要一點時間,有錯誤或想討論的都歡迎留言給我唷!那就下次見拉!

--

--

Phoebe Huang
Phoebe Huang

Written by Phoebe Huang

A software engineer from Taiwan, use free time to learn more about computer science.

No responses yet