其實一直不想做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))
}
}
IEEE 754有一個半精度的浮點數,叫Half-precision floating-point format 通訊的時候數值的部分常會用到,因為他是最小的浮點數,好用 Go的float16 package 我有一個專案有用到這個 https://github.com/x448/float16 不錯用,推推 online float16 calculator 要做確認的時候還是會需要計算一下,查到這個網站
Sep 2, 2022有時候我們會需要查看一個檔案(尤其是binary檔案的時候)的各個byte到底寫了什麼,這時後就可以用od這個指令來把檔案一個一個byte地顯示出來 最常用的指令(今天就只記錄這招XD) od -c filename.bmp >> od.txt 上面是將一個bmp檔案經過od的解析後,將結果寫進od.txt中。寫進txt檔是因為通常這個output會很大,如果terminal不允許顯示這麼長的output的話,會無法一次看完全部。 參數c在man page的解釋是"select printable characters or backslash escapes",已經講解得很清楚了,如果是ASCII的話會顯示字元或是帶有反斜線的跳脫字元,但一個byte換成十進位的話可以代表0到255,而ASCII只用了0到127而已,所以剩下的無法部分,od會將它轉換成八進位的三位數,例如212代表的就是2x8^2+1x8+2=138,也就是十進位的138,又例如012代表的就是十進位的10。所以我們可以知道這邊這個三位數最多只到377,也就是十進位的255 而od.txt每一行的左邊會有該行的行數,更正確的說是該行的offset,也就是該行在這份檔案中,它的前面有幾個byte的意思。而他的單位是八進位的數字,例如0000040代表的就是這一行的前面有4x8=32個byte。
Sep 6, 2020先來講一點廢話,講一下為啥我會開始用ViewModel。 以前我寫single activity application還沒有用navigation的時候,都是自己寫個listener的interface用observer pattern來實現activity和fragment的溝通。我記得這其實是原本官方建議的方式,但我現在找不到了,記得好像蠻硬性地規定要有個callback method叫做onFragmentInteraction(uri: Uri)來做activity跟fragment的溝通。observer pattern的方式我用了很久,直到後來因為覺得各個fragment之間的轉換用寫的實在是有點亂,就用了navigation,沒想到一試成主顧,真的太好用而且很簡單(我就不特別筆記navigation了因為官方教學十分清楚,照著做就好),但用了navigation後,整個MainActivity的程式架構變了很多,也讓我重新思考activity、fragment和service之間到底要怎麼溝通比較好,於是讓我有這個機會試著用用看ViewModel和LiveData。然而它們就跟navigation一樣,太好用又太簡單了,用完之後回不去了。 所以這篇筆記所寫的可能跟一般大家常用ViewModel和LiveData的理由不同,一般是拿來存取UI元件的資料用的,而我這邊想講一下怎麼用ViewModel和LiveData來取代Observer pattern來做activity和fragment之間(fragment和fragment之間也可以)的溝通,而且相較之下是一個更好用更簡單的方案。 基本介紹 ViewModel ViewModel是一個用來把資料跟activity、fragment等view類型的元件分離的東西,而且它的lifecycle比activit和fragment的還要長,是一個很適合放資料的地方。它的存在跟一個叫做MVVM的design pattern有很大的關係。主要就是view跟model之間,有了這個viewmodel來讓資料的取得與更新有一個獨立的地方來專門處理。
Aug 20, 2020App除了使用者看得到可互動的foreground部分之外,很多時候會用到background的運算,例如Google drive透過網路進行檔案的上傳下載、Line的即時訊息等等都是要在背景執行或是待命的。這篇會基本地介紹一下background processing的部分有哪些常用機制可以用,然後主要介紹其中兩個,第一個不外乎就是App四大component之一的Service,另一個是API 28出現的WorkManager。 官方建議的background processing方案 其實官方有文章來講解這個部分,這邊簡單重點整理一下,詳細內容可以去看官方的文章。 官方把背景的工作分成三種 Immediate Deferred Exact
Aug 10, 2020or
By clicking below, you agree to our terms of service.
New to HackMD? Sign up