清單的排版會受到主版與子版兩個 xml 的排版影響,在 group_list_item.xml 中可以設計成左右滿版貼齊邊緣,並將高度固定,此外若需要各項目之間留有間隔,可以設定 MargnBottom,如下圖所示:
主版的設計也提供參考圖一,另外 ListView 預設會有外框線,如果不需要外框可以在 ListView 設定屬性 divider
,指定 @Null
值,如圖二。
搞定!執行模擬查看辛苦的成果吧,作者有將資料來源多增加幾筆,超出高度後會有捲軸出現。
雖然達成了目標,但是回頭看看程式其實隱藏著一個問題,回到 GroupAdapter
可以看到這一段程式被 IDE 提醒,建議要使用 Holder 設計模式,否則每次呼叫 getView()
方法的時候都要重複執行不必要的部分,會影響到介面操作順暢度,讀者應該也可以感受到在滾動捲軸時會卡頓,可以試著在 Line: 35 加入 Log
查看程式如何運作。
從記錄中發現每次滾動捲軸都會重複進入 getView()
,接著我們著手將程式碼調整一下,看是否能減少效能負荷,在 GroupAdapter
下方加入 ViewHolder
類別,並宣告可為空值的屬性承接兩個元件。
private class ViewHolder{
var groupImage: ImageView? = null
var groupText: TextView? = null
}
在 getView()
一開始的地方,將原本的 groupView
物件變數抽離出來,同時也新增一個 holder
的物件變數,型態為剛剛新增的 ViewHolder
,如下所示。
val groupView: View
val holder: ViewHolder
接著使用 getView()
函式上接收的 convertView
物件參數,進行重複利用判斷,該參數初始值會是 null
,因此透過這個判斷條件,可加上條件判斷後是否要產生 groupView
物件實作。
到這邊順手將 .inflate()
的參數調整成傳入三個變數,第一個不變,第二個由於原本使用 null
,IDE 會建議不要這樣使用,因此改傳入第二及第三 parent, false
兩個參數)。
再來輪到實作 holder = ViewHolder()
,將原本的 .findViewById()
結果交給 hodler
的屬性,接著將 holder
交給 groupView.tag
屬性。另一方面設計 else
程式段將 holder
及 groupView
進行重複利用 (Recycle)。
if (convertView == null) {
groupView = LayoutInflater
.from(context)
.inflate(R.layout.group_list_item, parent, false)
holder = ViewHolder()
holder.groupImage = groupView.findViewById(R.id.groupImage)
holder.groupText = groupView.findViewById(R.id.groupName)
groupView.tag = holder
} else {
holder = convertView.tag as ViewHolder
groupView = convertView
}
接續在下方的程式碼,原本的 groupImage
、groupText
前方加入 hodler.
(因為我們已經將兩個元件移到 holder
物件內),再來處理 Nullable 安全性問題,後方需多加上 ?
符號,完成如下:
// 取得資料
var group = getItem(position)
// 變更圖片
var imgRid = context.resources
.getIdentifier(group.image, "drawable", context.packageName)
holder.groupImage?.setImageResource(imgRid)
// 變更文字
holder.groupText?.text = group.name
return groupView
完整的程式段:
最後就可以執行程式看看結果,順暢程度有改善嗎?讀者可以加入三段 Log
,分別在 if、else 及 return 之前,這時回到 Studio 上觀察 Logcat,會發現雖然 if...else
這段有效能上的改善,但程式還是不斷的在 setImage
與更新 text
,應該有更好的方法來處理,下一章節要示範使用 RecyclerView
來解決這個問題,我們明天見!