--- 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)) } } ```