Android Kotlin:如何从RecyclerView Adapter监听获取选中项列表?
问题:RecyclerView Adapter选中列表外部访问为空
在Android Kotlin开发中,尝试从RecyclerView的FloorProfileDialogAdapter获取选中的String列表并在Fragment中使用,但日志显示列表仅在onBindViewHolder方法内更新,外部调用returnList()时拿到的是空列表。考虑过存入Room数据库,但想知道其他可行方案。
相关代码
Adapter代码
class FloorProfileDialogAdapter() : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() { var floors = emptyList<String>() inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false) return MyViewHolder(binding) } private val checkedFloors: MutableList<String> = mutableListOf() //List of uniquely selected checkbox to be observed from New Schedule Floor Fragment var unique: List<String> = mutableListOf() @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val currentFloor = floors[position] Timber.d("Current floor: $currentFloor") holder.binding.floorCheckBox.text = "Floor $currentFloor" //Checks the checked boxes and updates the list holder.binding.floorCheckBox.setOnCheckedChangeListener { buttonView, isChecked -> if (buttonView.isChecked) { Timber.d("${buttonView.text} checked") checkedFloors.add(buttonView.text.toString()) } else if (!buttonView.isChecked) { Timber.d("${buttonView.text} unchecked") checkedFloors.remove(buttonView.text) } unique = checkedFloors.distinct().sorted() Timber.d("List: $unique") } } fun returnList(): List<String> { Timber.d("$unique") return unique } override fun getItemCount(): Int { return floors.size } @SuppressLint("NotifyDataSetChanged") fun getAllFloors(floorsReceived: List<String>) { Timber.d("Floors received : $floorsReceived") this.floors = floorsReceived notifyDataSetChanged() } }
Fragment读取列表代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) //Chosen Floors val chosenFloors = floorProfileDialogAdapter.returnList() Timber.d("Chosen floors : $chosenFloors") }
Adapter填充数据代码
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) val floorList: MutableList<String> = mutableListOf() var profileName: String? = "" profileName = args.profileName //Profile name received Timber.d("Profile name : $profileName") //Getting list of all floors createProfileViewModel.totalFloors.observe(viewLifecycleOwner) { Timber.d("List of floors received : $it") val intList = it.map(String::toInt) val maxFloorValue = intList.last() var count = 0 try { while (count <= maxFloorValue) { floorList.add(count.toString()) count++ } } catch (e: Exception) { Timber.d("Exception: $e") } floorProfileDialogAdapter.getAllFloors(floorList) Timber.d("Floor List : $floorList") }
问题原因
- 调用时机过早:Fragment在
onViewCreated中直接调用returnList()时,Adapter数据还未初始化完成,用户也未操作Checkbox,自然拿到空列表。 - ViewHolder复用导致状态丢失:
onBindViewHolder会随列表滚动重复调用,但未恢复之前的选中状态,同时checkedFloors会出现重复添加/移除的错误。 - 无数据通知机制:Adapter内部
unique列表更新时,没有通知Fragment,Fragment无法感知数据变化,只能拿到初始空值。
可行解决方案
方案1:使用LiveData实现实时观察
在Adapter中用LiveData存储选中列表,Fragment通过观察LiveData获取最新数据,同时解决ViewHolder复用问题。
修改后的Adapter:
class FloorProfileDialogAdapter() : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() { var floors = emptyList<String>() private val _checkedFloors = MutableLiveData<MutableList<String>>(mutableListOf()) val checkedFloors: LiveData<List<String>> get() = _checkedFloors.map { it.distinct().sorted() } inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false) return MyViewHolder(binding) } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val currentFloor = floors[position] val floorText = "Floor $currentFloor" holder.binding.floorCheckBox.text = floorText // 恢复之前的选中状态 val currentCheckedList = _checkedFloors.value ?: mutableListOf() holder.binding.floorCheckBox.isChecked = currentCheckedList.contains(floorText) holder.binding.floorCheckBox.setOnCheckedChangeListener { buttonView, isChecked -> val text = buttonView.text.toString() val updatedList = currentCheckedList.toMutableList() if (isChecked) { if (!updatedList.contains(text)) updatedList.add(text) } else { updatedList.remove(text) } _checkedFloors.postValue(updatedList) } } override fun getItemCount(): Int = floors.size @SuppressLint("NotifyDataSetChanged") fun getAllFloors(floorsReceived: List<String>) { this.floors = floorsReceived notifyDataSetChanged() } }
Fragment中观察数据:
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 实时观察选中列表变化 floorProfileDialogAdapter.checkedFloors.observe(viewLifecycleOwner) { selectedFloors -> Timber.d("Chosen floors : $selectedFloors") // 在这里处理最新选中数据 } // 原有填充数据代码保持不变... }
方案2:通过回调接口通知Fragment
定义回调接口,Adapter在选中状态变化时主动通知Fragment更新数据。
定义回调接口:
interface OnFloorSelectedListener { fun onFloorSelected(selectedFloors: List<String>) }
修改后的Adapter:
class FloorProfileDialogAdapter(private val listener: OnFloorSelectedListener) : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() { var floors = emptyList<String>() private val checkedFloors = mutableListOf<String>() inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false) return MyViewHolder(binding) } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val currentFloor = floors[position] val floorText = "Floor $currentFloor" holder.binding.floorCheckBox.text = floorText // 恢复选中状态 holder.binding.floorCheckBox.isChecked = checkedFloors.contains(floorText) holder.binding.floorCheckBox.setOnCheckedChangeListener { buttonView, isChecked -> val text = buttonView.text.toString() if (isChecked) { if (!checkedFloors.contains(text)) checkedFloors.add(text) } else { checkedFloors.remove(text) } listener.onFloorSelected(checkedFloors.distinct().sorted()) } } // 主动获取选中列表方法(比如点击确认按钮时调用) fun getSelectedFloors(): List<String> = checkedFloors.distinct().sorted() override fun getItemCount(): Int = floors.size @SuppressLint("NotifyDataSetChanged") fun getAllFloors(floorsReceived: List<String>) { this.floors = floorsReceived notifyDataSetChanged() } }
Fragment实现接口:
class YourFragment : Fragment(), OnFloorSelectedListener { private lateinit var floorProfileDialogAdapter: FloorProfileDialogAdapter override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) // 初始化Adapter时传入回调 floorProfileDialogAdapter = FloorProfileDialogAdapter(this) // 原有填充数据代码保持不变... // 主动获取选中列表示例(比如点击确认按钮) binding.confirmBtn.setOnClickListener { val selectedFloors = floorProfileDialogAdapter.getSelectedFloors() Timber.d("Chosen floors : $selectedFloors") } } override fun onFloorSelected(selectedFloors: List<String>) { Timber.d("实时选中楼层: $selectedFloors") // 实时处理选中数据 } }
方案3:将选中状态绑定到数据源
修改数据源结构,给每个楼层添加选中状态字段,直接操作数据源,Adapter根据数据源渲染状态,Fragment直接读取数据源。
定义数据类:
data class FloorItem(val number: String, var isSelected: Boolean = false)
修改后的Adapter:
class FloorProfileDialogAdapter() : RecyclerView.Adapter<FloorProfileDialogAdapter.MyViewHolder>() { var floorItems = emptyList<FloorItem>() inner class MyViewHolder(val binding: ScheduleFloorDialogItemBinding) : RecyclerView.ViewHolder(binding.root) override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ScheduleFloorDialogItemBinding.inflate(inflater, parent, false) return MyViewHolder(binding) } @SuppressLint("SetTextI18n") override fun onBindViewHolder(holder: MyViewHolder, position: Int) { val currentItem = floorItems[position] holder.binding.floorCheckBox.text = "Floor ${currentItem.number}" holder.binding.floorCheckBox.isChecked = currentItem.isSelected holder.binding.floorCheckBox.setOnCheckedChangeListener { _, isChecked -> currentItem.isSelected = isChecked } } fun getSelectedFloors(): List<String> { return floorItems.filter { it.isSelected }.map { "Floor ${it.number}" }.sorted() } override fun getItemCount(): Int = floorItems.size @SuppressLint("NotifyDataSetChanged") fun getAllFloors(floorsReceived: List<String>) { this.floorItems = floorsReceived.map { FloorItem(it) } notifyDataSetChanged() } }
Fragment中获取数据:
// 示例:点击确认按钮时获取选中列表 binding.confirmBtn.setOnClickListener { val selectedFloors = floorProfileDialogAdapter.getSelectedFloors() Timber.d("Chosen floors : $selectedFloors") }
内容的提问来源于stack exchange,提问作者oyeraghib
相关产品推荐
相关产品推荐

