Android Kotlin嵌套RecyclerView子项onCreateViewHolder未调用问题
问题排查:嵌套RecyclerView子列表不渲染
问题描述
基于MVVM架构+Room数据库实现嵌套RecyclerView,父RecyclerView可正常加载显示,但子RecyclerView完全不渲染。日志显示数据已传入适配器,但onBindViewHolder和onCreateViewHolder均未触发。
相关代码
Fragment代码
class ControlPanelFragment : Fragment() { private var _binding: FragmentControlPanelBinding? = null private val binding: FragmentControlPanelBinding get() = _binding!! private lateinit var controlPanelViewModel: ControlPanelViewModel //floor adapter private lateinit var adapter: FloorsAdapter override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View? { _binding = FragmentControlPanelBinding.inflate(layoutInflater) controlPanelViewModel = ViewModelProvider(this)[ControlPanelViewModel::class.java] adapter = FloorsAdapter(controlPanelViewModel) return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.rvFloors.adapter = adapter binding.rvFloors.layoutManager = LinearLayoutManager(requireContext()) controlPanelViewModel.getAllFloors.observe(viewLifecycleOwner) { Timber.d("原始楼层列表: $it") //去重并排序 val distinctFloors = it.toSet().toList().sorted() Timber.d("去重排序后的楼层列表: $distinctFloors") adapter.updateFloors(distinctFloors) } } override fun onDestroyView() { super.onDestroyView() _binding = null } }
父RecyclerView适配器(修改后)
class FloorsAdapter(private val viewModel: ControlPanelViewModel) : RecyclerView.Adapter<FloorsAdapter.FloorViewHolder>() { private var floorList = emptyList<String>() //为每个楼层保存对应的房间数据和子适配器 private val roomAdapters = mutableMapOf<String, RoomsAdapter>() inner class FloorViewHolder(private val binding: ListItemControlPanelFloorsBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(floor: String) { binding.tvFloor.text = "Floor : $floor" //初始化子RecyclerView if (binding.rvRoomControlPanel.adapter == null) { binding.rvRoomControlPanel.layoutManager = LinearLayoutManager(itemView.context) val adapter = RoomsAdapter() roomAdapters[floor] = adapter binding.rvRoomControlPanel.adapter = adapter } //观察当前楼层的房间数据 viewModel.getAllRooms(floor).observeForever { rooms -> Timber.d("楼层: $floor, 房间列表: $rooms") val distinctRooms = rooms.toSet().toList() roomAdapters[floor]?.updateRooms(distinctRooms) } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): FloorViewHolder { val layoutInflater = LayoutInflater.from(parent.context) val binding = ListItemControlPanelFloorsBinding.inflate(layoutInflater, parent, false) return FloorViewHolder(binding) } override fun onBindViewHolder(holder: FloorViewHolder, position: Int) { val floor = floorList[position] holder.bind(floor) } fun updateFloors(floors: List<String>) { this.floorList = floors notifyDataSetChanged() } override fun getItemCount(): Int { return floorList.size } }
子RecyclerView适配器(修改后)
class RoomsAdapter() : RecyclerView.Adapter<RoomsAdapter.RoomsViewHolder>() { private var roomList = emptyList<String>() inner class RoomsViewHolder(private val binding: ListItemControlPanelRoomsBinding) : RecyclerView.ViewHolder(binding.root) { fun bind(room: String) { binding.tvRoom.text = room } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RoomsViewHolder { val inflater = LayoutInflater.from(parent.context) val binding = ListItemControlPanelRoomsBinding.inflate(inflater, parent, false) return RoomsViewHolder(binding) } override fun onBindViewHolder(holder: RoomsViewHolder, position: Int) { val room = roomList[position] holder.bind(room) } override fun getItemCount(): Int { return roomList.size } fun updateRooms(rooms: List<String>) { this.roomList = rooms notifyDataSetChanged() } }
布局文件(保持原结构不变)
Fragment布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" tools:context=".ui.controlPanel.ui.ControlPanelFragment"> <androidx.constraintlayout.widget.ConstraintLayout android:layout_width="match_parent" android:layout_height="wrap_content" android:elevation="10dp"> <com.google.android.material.switchmaterial.SwitchMaterial android:id="@+id/switchFactory" android:layout_width="wrap_content" android:layout_height="wrap_content" android:layout_marginStart="16dp" android:layout_marginTop="60dp" android:checked="false" android:text="Factory" android:textOff="OFF" android:textOn="ON" android:textSize="16sp" app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" /> <Spinner android:id="@+id/spnChooseFloor" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginStart="200dp" android:layout_marginEnd="16dp" app:layout_constraintBottom_toBottomOf="@+id/switchFactory" app:layout_constraintEnd_toEndOf="parent" app:layout_constraintStart_toEndOf="@+id/switchFactory" app:layout_constraintTop_toTopOf="@+id/switchFactory" /> </androidx.constraintlayout.widget.ConstraintLayout> <androidx.cardview.widget.CardView android:layout_width="match_parent" android:layout_height="match_parent" android:layout_marginStart="16dp" android:layout_marginTop="10dp" android:layout_marginEnd="16dp" android:layout_marginBottom="10dp" android:elevation="8dp" app:cardCornerRadius="8dp"> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rvFloors" android:layout_width="match_parent" android:layout_height="match_parent" tools:listitem="@layout/list_item_control_panel_floors" /> </androidx.cardview.widget.CardView> </LinearLayout>
父RecyclerView列表项布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="200dp" android:orientation="vertical"> <TextView android:id="@+id/tvFloor" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_margin="4dp" android:text="Floor 1" android:textColor="@color/black" android:textSize="20sp" /> <androidx.recyclerview.widget.RecyclerView android:id="@+id/rvRoomControlPanel" android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_marginTop="10dp" tools:listitem="@layout/list_item_control_panel_rooms" /> </LinearLayout>
子RecyclerView列表项布局
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent" android:layout_height="40dp"> <TextView android:id="@+id/tvRoom" android:layout_width="match_parent" android:layout_height="match_parent" android:padding="8dp" android:text="Conference Room" android:textSize="20sp" /> </LinearLayout>
补充代码(Entity/DAO/Repository/ViewModel)
Entity
data class AddedDevicesInformation( @ColumnInfo(name = "floor_name") var floorName: String = "", @ColumnInfo(name = "room_name") var roomName: String = "", @ColumnInfo(name = "machine_name") var machineName: String = "", @ColumnInfo(name = "device_name") var deviceName: String = "", @ColumnInfo(name = "factory_status") var factoryStatus: Boolean? = false, @PrimaryKey(autoGenerate = true) var id: Int? = null, )
DAO查询
//返回指定楼层下的所有房间 @Query("SELECT room_name from added_device_information where floor_name =:floor") fun readAllRoomsOnAFloor(floor: String): LiveData<List<String>>
Repository
class ControlPanelRepository(private val devicesInformationDao: DevicesInformationDao) { fun getAllRooms(floor: String): LiveData<List<String>> = devicesInformationDao.readAllRoomsOnAFloor(floor) }
ViewModel
class ControlPanelViewModel(application: Application) : AndroidViewModel(application) { //Repository实例 val repository: ControlPanelRepository //获取所有楼层的LiveData val getAllFloors: LiveData<List<String>> init { val database = PowerManagementDatabase.getDatabase(application) val dao = database.getAddedDevicesInformationDao() repository = ControlPanelRepository(dao) getAllFloors = repository.getAllFloors } fun getAllRooms(floor: String): LiveData<List<String>> { return repository.getAllRooms(floor) } }
问题原因及修复说明
- 无效的子RecyclerView初始化:原Fragment中单独Inflate了
ListItemControlPanelFloorsBinding并设置适配器,这部分代码完全没用,因为这个Binding并没有关联到父RecyclerView的实际列表项上,直接删除即可。 - 共用子适配器导致数据覆盖:原代码中所有父项的子RecyclerView共用一个
roomAdapter,最后只会显示最后一组房间数据,改为每个楼层对应独立的子适配器,避免数据冲突。 - 子RecyclerView未正确初始化:父适配器的
onBindViewHolder中才是初始化子RecyclerView布局管理器和适配器的正确位置,确保每个列表项的子RecyclerView都被正确配置。 - LiveData观察优化:将房间数据的观察逻辑移到父适配器的ViewHolder绑定方法中,确保每个楼层只观察自己对应的房间数据,同时避免Fragment中循环观察导致的内存泄漏问题。
内容的提问来源于stack exchange,提问作者oyeraghib
相关产品推荐
相关产品推荐

