---
tags: Android, RecyclerView
---
# 簡單筆記RecyclerView
其實一直不想做RecyclerView的筆記,因為網路上資源太多了,但每次要用的時候都還要花個幾分鐘回憶一下這東西到底在幹嘛,實在是浪費時間,所以這篇從程市結構上簡單講一下,讓下次要用到RecyclerView的時候可以快速恢復記憶。
## 程式結構
以下重點式地說一下哪些地方要寫東西:
- Activity/Fragment的class attribute要新增
- 一個RecyclerView:
用來接收recyclerview的UI widget,並用它來設定好recyclerview背後的程式邏輯。
- 一個RecyclerView.Adapter:
這就是我們主要要寫的一個class,就是recyclerview背後的程式邏輯,主要負責接收資料、顯示item並處理點擊item後的反應。
- 一個RecyclerView.LayoutManager:
這是用來做recyclerview的layout管理的。
- Activity/Fragment的onCreateView():
裡面要設定inflat相關的東西,要設定recyclerview的layout xml file,以及把RecyclerView.Adapter和RecyclerView.LayoutManager放進RecyclerView。
- 整個RecyclerView.Adapter要寫好,以下幾個重要部分:
- 寫一個繼承自RecyclerView.ViewHolder(itemView)的class:
這裡會取得item中的UI widget們,以便待會使用。
- onCreateViewHolder():
這裡的重點就是要設定item的layout xml file。
- onBindViewHolder():
這裡負責資料的顯示,會把recyclerview的資料用在item的UI widget們身上,並設定item的UI widget們的onClick listener。
重點大概就是以上這些,至於用observer pattern來做Activity/Fragment跟recyclerview之間的溝通因為比較簡單我這邊就沒有特別寫,但那也是要有的,才可以把使用者對recyclerview的操作結果回傳。
## 程式範例
這個範例是一個掃描BLE並列出所有BLE裝置列表的fragment的.kt檔,透過ViewModel跟Activity拿到recyclerview需要的資料,並在item被click的時候顯示dialog。
```
import ...
class BleSettingFragment : Fragment(),
BleDeviceRecyclerviewAdaptor.OnClickListener,
ConnectDialog.OnClickListener {
private val model: SharedViewModel by activityViewModels()
private lateinit var recyclerView: RecyclerView
private lateinit var viewAdapter: BleDeviceRecyclerviewAdaptor
private lateinit var viewManager: RecyclerView.LayoutManager
private var myDataset = mutableListOf<String>()
private val connectDialog = ConnectDialog()
override fun onStart() {
super.onStart()
btnBleCancel.setOnClickListener {
model.msg.value = Uri.parse("msg://ble_setting/stop_scan")
findNavController().navigate(
BleSettingFragmentDirections
.actionBleSettingFragmentToMainPageFragment()
)
}
model.msg.value = Uri.parse("msg://ble_setting/start_scan")
model.msg.observe(viewLifecycleOwner, Observer {
when (it.authority) {
"ble_service" -> {
when (it.path) {
"/start_scan" -> {
txtBleScanStatus.text = "掃描中..."
}
"/stop_scan" -> {
txtBleScanStatus.text = "掃描完成"
}
"/scan_result" -> {
// it.fragment = "${name},${addr},${rssi}"
// myDataset[i] = "${name},${addr},${rssi}"
val newAddr = it.fragment!!.split(",")[1]
var alreadyExist = false
for (i in 0 until myDataset.size) {
val existsAddr = myDataset[i].split(",")[1]
if (newAddr == existsAddr) {
alreadyExist = true
myDataset[i] = it.fragment!!
}
}
if (!alreadyExist) {
myDataset.add(it.fragment!!)
}
viewAdapter.notifyDataSetChanged()
}
}
}
}
})
}
override fun onCreateView(
inflater: LayoutInflater, container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
// Inflate the layout for this fragment
viewManager = LinearLayoutManager(activity)
viewAdapter = BleDeviceRecyclerviewAdaptor(myDataset)
viewAdapter.setListener(this)
var rootView = inflater
.inflate(R.layout.fragment_ble_setting, container, false) as View
recyclerView = rootView.findViewById<RecyclerView>(R.id.recyclerView_ble_device)
.apply {
// use this setting to improve performance if you know that changes
// in content do not change the layout size of the RecyclerView
setHasFixedSize(true)
// use a linear layout manager
layoutManager = viewManager
// specify an viewAdapter (see also next example)
adapter = viewAdapter
}
return rootView
}
override fun recyclerviewClick(data: String) {
val nameAddrRssi = data.split(",")
connectDialog.setListener(this)
connectDialog.name = nameAddrRssi[0]
connectDialog.addr = nameAddrRssi[1]
connectDialog.signalPower = nameAddrRssi[2]
connectDialog.width =
(0.9 * Resources.getSystem().displayMetrics.widthPixels).roundToInt()
connectDialog.height =
(0.4 * Resources.getSystem().displayMetrics.heightPixels).roundToInt()
connectDialog.show(activity?.supportFragmentManager!!, "connection dialog")
}
override fun connectDialogClick(data: String) {
model.msg.value = Uri.parse("msg://ble_setting/connect#$data")
connectDialog.dismiss()
findNavController().navigate(
BleSettingFragmentDirections
.actionBleSettingFragmentToSystemSettingFragment()
)
}
}
class BleDeviceRecyclerviewAdaptor(private val myDataset: MutableList<String>) :
RecyclerView.Adapter<BleDeviceRecyclerviewAdaptor.MyViewHolder>() {
// onclick listener interface
private var listener: OnClickListener? = null
interface OnClickListener {
fun recyclerviewClick(data: String)
}
fun setListener(parentFragment: OnClickListener) {
listener = parentFragment
}
// Provide a reference to the views for each data item
// Complex data items may need more than one view per item, and
// you provide access to all the views for a data item in a view holder.
// Each data item is just a string in this case that is shown in a TextView.
class MyViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
val device = itemView.btnDevice
}
// Create new views (invoked by the layout manager)
override fun onCreateViewHolder(
parent: ViewGroup,
viewType: Int
): BleDeviceRecyclerviewAdaptor.MyViewHolder {
// create a new view
val deviceItemLayout = LayoutInflater.from(parent.context)
.inflate(R.layout.item_ble_device, parent, false) as View
// set the view's size, margins, paddings and layout parameters
return MyViewHolder(deviceItemLayout)
}
// Replace the contents of a view (invoked by the layout manager)
override fun onBindViewHolder(holder: MyViewHolder, position: Int) {
// - get element from your dataset at this position
// - replace the contents of the view with that element
val name = myDataset[position].split(",")[0]
val addr = myDataset[position].split(",")[1]
if (name == "null") {
holder.device.text = addr
} else {
holder.device.text = name
}
holder.device.setOnClickListener {
listener?.recyclerviewClick(myDataset[position])
}
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
super.onDetachedFromRecyclerView(recyclerView)
listener = null
}
// Return the size of your dataset (invoked by the layout manager)
override fun getItemCount() = myDataset.size
}
class ConnectDialog : DialogFragment() {
var name = ""
var addr = ""
var signalPower = ""
var width = 0
var height = 0
// onclick listener interface
private var listener: OnClickListener? = null
interface OnClickListener {
fun connectDialogClick(data: String)
}
fun setListener(parentFragment: OnClickListener) {
listener = parentFragment
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedInstanceState: Bundle?
): View? {
return LayoutInflater.from(context)
.inflate(R.layout.dialog_connection, container, false)
}
override fun onStart() {
super.onStart()
txtDeviceName.text = name
txtDeviceMac.text = addr
txtDeviceSignalPower.text = signalPower
btnConnect.setOnClickListener {
listener?.connectDialogClick(addr)
}
dialog?.window?.setLayout(width, height)
dialog?.window?.setBackgroundDrawable(ColorDrawable(Color.TRANSPARENT))
}
}
```