今天開始的課程為資料清單,建立一個新的專案,專案的選擇與先前課程相同,並新增三類 Package 資料夾,分別命名為:Controllers、Models、Services,在 Models 中建立 Class 檔案 (可命名為 FoodGroup),其中宣告資料類別 FoodGroup 如下,存放群組名稱與圖檔名稱:
data class FoodGroup(val name: String, val image: String)
另外於 Services 新增一個 Object 類型,命名為 DataService,用來當作資料來源,內容如下:
object DataService {
val groups = listOf(
FoodGroup("五穀", "img_various_grains"),
FoodGroup("蔬菜", "img_vegetables"),
FoodGroup("水果", "img_fruits")
)
}
下一步,切換至 activity_main.xml,在 Legacy 中將 ListView 加入到版面上。
回到 MainActivity 首先加入全域變數 adapter
,這裡的 lateinit
關鍵字代表宣告時不先初始化 ,若沒有這個關鍵字就必須要實作或給予預設值。
lateinit var adapter: ArrayAdapter<FoodGroup>
接著實作 ArrayAdapter
,前兩個參數都是固定值,第三個參數用上面設計的 DataService.groups
當作資料來源,再將整個 adapter
指定到版面上的 ListView。
fun generateListView() {
adapter = ArrayAdapter(this,
android.R.layout.simple_list_item_1,
DataService.groups)
groupListView.adapter = adapter
}
最後在 onCreate
呼叫上面設計的函式。
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 產生資料清單
generateListView()
}
實際執行的效果,雖然還不是最終想要的,不過雛型有成功的列出資料清單:
接著處理清單顯示的樣式,先在專案總管 res 中新增一個版面資源檔,命名為 group_list_item.xml,在此設計要顯示在清單的個別項目樣式,由於清單是由多個項目組成,所以只要設計單一樣式讓所有項目共用即可,版面配置使用 ImageView
顯示圖檔、TextView
顯示名稱。
為了後續使用方便,請將 imageView
與 textView
更改獨特的識別 Id,可以命為 groupImage、groupName。緊接著在專案新增 Adapter (轉接器) 資料夾,加入一個 Kotlin Class: GroupAdapter 並且使用 BaseAdapter()
介面,這時將游標移動到紅底部分,按下 Alt + Enter 選擇實作成員。
此時會跳出一個視窗,使用鍵盤 Shift 將所有成員全選 (或直接按 Ctrl + A),再點選 OK 即可。
若操作正確就會顯示以下畫面:
這裡建立一個自訂的轉接器 (Adapter) 目的是在取代剛才使用的 ArrayAdapter
,藉由自訂轉接器可以彈性調整設計清單的呈現方式。轉接器是 Data (DB) - Adapter - View 中,擔任資料與 View 之間的轉接角色。
因為最終我們要取代下面這段程式:
adapter = ArrayAdapter(this,
android.R.layout.simple_list_item_1,
DataService.groups)
所以要在 GroupAdapter
中實作相同工作,須於建構子上加入兩個參數接收,分別是 Context
與 List
。
class GroupAdapter(var context: Context, var groups: List<FoodGroup>): BaseAdapter()
實作成員後會產生四個方法,目前都還是 TODO
尚未實際實作的狀態,讓我們先從簡單的部分 getItem、getItemId、getCount
下手。
getItem()
是在傳入索引位置時,希望回傳清單內的項目,所以很簡單的以下列方式實作,並將原本預設的 Any
回傳型態改成 FoodGroup
。
override fun getItem(position: Int): FoodGroup{
return groups[position]
}
getItemId()
我們用不到,可以直接回傳 0
。
override fun getItemId(position: Int): Long {
return 0
}
getCount()
要取得清單的項目數,以 count()
方式取得並回傳即可。
override fun getCount(): Int {
return groups.count()
}
最複雜的地方在第一個 getView
,這裡要加入大量程式。使用 LayoutInflater
取得先前建立好的個別項目 layout
,記得在下 R
時要選擇自己專案的,才能夠繼續往下找到 group_list_item.xml。
接著完成下列程式碼,為了避免版面雜亂,請參考註解的說明。
// 首先取出剛剛設計的個別項目 layout,參數 null 是為了指定根目錄
val groupView = LayoutInflater
.from(context)
.inflate(R.layout.group_list_item, null)
// 取得項目樣式中的兩個元件,若有紅字可用 Alt + Enter > Import
var groupImage: ImageView = groupView.findViewById(R.id.groupImage)
var groupText: TextView = groupView.findViewById(R.id.groupName)
// 取得項目資料
var group = getItem(position)
// 變更圖片
var imgRid = context.resources
.getIdentifier(group.image, "drawable", context.packageName)
groupImage.setImageResource(imgRid)
// 變更文字
groupText.text = group.name
// 回傳 View
return groupView
完成自訂的轉接器後,就能回到 MainActivity,將有使用到 ArrayAdapter
的部分取代掉:
lateinit var adapter: GroupAdapter
adapter = GroupAdapter(this, DataService.groups)
groupListView.adapter = adapter
搞定一切,可以執行模擬機看看效果,版面的呈現可能會很奇怪,下一章節會來處理排版的設計,我們明天見!