今天的主題是點擊項目後的動作,首先我們要知道點到的是清單中的哪個項目,因此要擴充一下 groupRecycleAdapter
。
首先在類別建構子加上第三個參數,命名為 itemClickEvent
,型態是接收 FoodGroup
的 lambda 表示式 (新上車的讀者可以參考 Day 11 的主題),將 val itemClickEvent: (FoodGroup) -> Unit
,加到先前的程式碼之中,類別上方其他部分原封不動。
class GroupRecycleAdapter(val context: Context, val groups: List<FoodGroup>, val itemClickEvent: (FoodGroup) -> Unit)
: RecyclerView.Adapter<GroupRecycleAdapter.Holder>() { ... }
接續到內部類別 Holder
的 bindGroup()
方法,加入熟悉的 setOnClickListener { }
,大括號中使用上面的 itemClickEvent
物件並傳入 group
項目。
itemView.setOnClickListener { itemClickEvent(group) }
這裡提供截圖讓讀者方便確認有調整到的部分:
下一步打開 MainActivity,在 generateListView
中,將原本的 adapter = GroupRecycleAdapter
... 後方加上 { }
,使用 lambda。可在此處加入 Log.v
執行模擬機查看效果,當點擊清單項目時,就會觸發印出 Log,並且程式都能知道是哪一個項目被點擊。
adapter = GroupRecycleAdapter(this, DataService.groups) {
Log.v("Test", it.name)
}
是時候移動到第二個頁面了,讓我們新增一個 ItemsActivity,接著要進行頁面的切換與傳值,切換回 MainActivity 剛剛設計的 lambda 中,移除 Log.v
,並加入先前章節常用的換頁語法:
var itemIntent = Intent(this, ItemsActivity::class.java)
startActivity(itemIntent)
現在執行程式就能成功跳至第二頁面,緊接著進行傳值前的準備工作,到 Models 資料夾內建立一個 FoodItem
的資料類別,主要存放第二層的資料,同時也需要增加實作 Parcelable
。
接著到 Models > FoodGroup,先加入第三參數 list
用來存放 FoodItem
型態資料,下一步一樣增加實作 Parcelable
介面:
data class FoodGroup(val name: String, val image: String, val list: List<FoodItem>?): Parcelable
檢查一下建構子與 writeToParcel
這兩段程式是否有如下列完整產生,特別是各自的第三行。
constructor(parcel: Parcel) : this(
parcel.readString(),
parcel.readString(),
parcel.createTypedArrayList(FoodItem) // 檢查
)
override fun writeToParcel(parcel: Parcel, flags: Int) {
parcel.writeString(name)
parcel.writeString(image)
parcel.writeTypedList(list) // 檢查
}
檢查沒問題後接著切換至 Services > DataService,在原本的 FoodGroup
加入第三個參數,這裡作者拿水果當作示範,其餘的指定為空值 null
。
var fruits = listOf(
FoodItem("蘋果", "img_apple"),
FoodItem("香蕉", "img_banana"),
FoodItem("鳳梨", "img_pineapple"),
FoodItem("芭樂", "img_guava"),
FoodItem("芒果", "img_mango"),
FoodItem("蓮霧", "img_wax_apple")
)
val groups = listOf(
FoodGroup("五穀", "img_various_grains", null),
FoodGroup("蔬菜", "img_vegetables", null),
FoodGroup("水果", "img_fruits", fruits),
FoodGroup("奶類", "img_dairy", null),
FoodGroup("肉類", "img_meats", null)
)
回到 MainActivity,現在能加入 putExtra
傳送選到的 group
到其它頁面。
fun generateListView() {
adapter = GroupRecycleAdapter(this, DataService.groups) {
var itemIntent = Intent(this, ItemsActivity::class.java)
itemIntent.putExtra(EXTRA_GROUP, it)
startActivity(itemIntent)
}
groupRecyclerView.adapter = adapter
groupRecyclerView.layoutManager = LinearLayoutManager(this)
}
第二個頁面用 getParcelableExtra
將物件取出,可以利用 Log.v
與 toString()
觀察值是否有成功傳遞。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item)
val group = intent.getParcelableExtra<FoodGroup>(EXTRA_GROUP)
Log.v("LogInfo", group.toString())
}
以上範例是為了結合示範使用 Parcelable
中的 list
型態傳遞,可以跟 Day 24 的設計方法比較,一般這種資料讀取情境應該會自 MainActivity 傳遞點選的群組Id,再用此單一識別碼於第二頁面,透過 API Service 從資料庫讀取項目出來,因為資料量龐大的情境下,不會在一開始就將所有階層都讀取出來占用資源。
下一步:於 res > layout 新增 item_list_item.xml,並加入兩個元件:ImageView、TextView,ImageView 命名為 itemImage
,layout
皆設定為 match
,四面邊界設為 8dp
,接著設定 ratio 為 1:1
,注意 ratio 必須多按幾下左上角的三角形,切換到如同下圖的模式:
XML 的部分提供參照:
<ImageView
app:srcCompat="@drawable/img_various_grains"
android:id="@+id/itemImage"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:scaleType="centerCrop"
android:layout_width="0dp"
android:layout_height="0dp"
android:layout_marginTop="8dp"
android:layout_marginStart="8dp"
android:layout_marginEnd="8dp"
android:layout_marginBottom="8dp"
app:layout_constraintVertical_bias="1.0"
app:layout_constraintDimensionRatio="w,1:1"/>
TexeView 設定四邊與 ImageView 的四邊約束,並調整垂直到底部 90% 的部分。
接著來到 Adapters 的部分,可用滑鼠左鍵點一下 group_list_item.xml,再按鍵盤 Ctrl + C、Ctrl + V 複製檔案,命名為 ItemRecycleAdapter。
下一步將所有 FoodGroup 字眼換成 FoodItem,R.layout
換成新建立的樣式表,R.id
修改讀取item 的目標元件,並且將用到 group
關鍵字的地方也一併更改,再將不需要的部分刪除,如:接收 List
參數和 onClick
,以下為修改後的程式碼。
class ItemRecycleAdapter(val context: Context, val items: List<FoodItem>)
: RecyclerView.Adapter<ItemRecycleAdapter.Holder>() {
override fun onCreateViewHolder(parent: ViewGroup, position: Int): Holder {
val view = LayoutInflater
.from(context)
.inflate(R.layout.item_list_item, parent, false)
return Holder(view)
}
override fun getItemCount(): Int {
return items.count()
}
override fun onBindViewHolder(holder: Holder, position: Int) {
holder.bindGroup(items[position], context)
}
inner class Holder(itemView: View): RecyclerView.ViewHolder(itemView) {
val itemImage: ImageView = itemView.findViewById(R.id.itemImage)
val itemName: TextView = itemView.findViewById(R.id.itemName)
fun bindGroup(item: FoodItem, context: Context) {
val resourceId = context.resources.getIdentifier(item.image, "drawable", context.packageName)
itemImage.setImageResource(resourceId)
itemName.text = item.name
}
}
}
最後步驟是到 ItemsActivity,將 lateinit var adapter
參數於上方宣告。onCreate()
中加入 titleText.text = group.name
(Line: 26),讓第二頁標題顯示首頁選擇的群組名稱,產生資料清單的部分多傳入一個清單,這個清單會是 List<FoodItem>
型態,可以透過 Alt + Enter 快速產生函式。
就快完成了,在 generateListView()
中,先判斷一下 list
有值時才執行 Adapter
程式段,再把 list
傳入 ItemRecycleAdapter
,最後我們將 layoutManager
更換使用 GridLayout
,第二個參數可以指定項目要幾個為一排,這裡設定為 2
。
class ItemsActivity : AppCompatActivity() {
lateinit var adapter: ItemRecycleAdapter
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_item)
val group = intent.getParcelableExtra<FoodGroup>(EXTRA_GROUP)
titleText.text = group.name
// 產生資料清單
generateListView(group.list)
}
private fun generateListView(list: List<FoodItem>?) {
if (list != null) {
adapter = ItemRecycleAdapter(this, list)
groupRecyclerView.adapter = adapter
groupRecyclerView.layoutManager = GridLayoutManager(this, 2)
}
}
}
執行成果:
最後留給讀者練習:如何改善 ItemName 顯示對比?如何調整顯示項目的排列形狀?讀者可以自行探索一下主版頁面與清單頁面之間的約束影響。RecyclerView 將在此告一段落,接下來會介紹一些獨立的小主題,是作者在之前主題想延伸介紹但與主軸沒有相關,因此就留在後面介紹,我們明天見!
資料參考
Kotlin for Android: Beginner to Advanced | Udemy
https://www.udemy.com/devslopes-android-kotlin/
Create a List with RecyclerView-Android Developers
https://developer.android.com/guide/topics/ui/layout/recyclerview