You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

问题原因

  1. 调用时机过早:Fragment在onViewCreated中直接调用returnList()时,Adapter数据还未初始化完成,用户也未操作Checkbox,自然拿到空列表。
  2. ViewHolder复用导致状态丢失:onBindViewHolder会随列表滚动重复调用,但未恢复之前的选中状态,同时checkedFloors会出现重复添加/移除的错误。
  3. 无数据通知机制: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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.21 05:54:22