嵌套RecyclerView性能问题:日历多日期加笔记后卡顿崩溃
问题分析与解决方案
核心卡顿原因
- 重复创建大量对象:主适配器
CalendarAdapter的onBindViewHolder每次触发时,都会为每个日期的RecyclerView重新创建CalendarDayAdapter和LinearLayoutManager,导致内存占用飙升、GC频繁,最终引发卡顿甚至崩溃。 - ViewPool使用错误:两个适配器的
getItemViewType返回position,导致ViewPool无法跨位置复用ViewHolder;且每个日期RecyclerView未共用统一的复用池,缓存机制失效。
解决方案(最小代码改动)
1. 核心修改思路
- 复用日期RecyclerView的
Adapter和LayoutManager,避免重复创建 - 统一使用全局
RecycledViewPool,修复ViewHolder复用逻辑 - 仅更新Adapter数据源,而非重建Adapter
2. 具体代码修改
修改CalendarAdapter
class CalendarAdapter(val items: MutableList<CalendarClass>,val dayListener: OnDayClickListener, val notificationlistener: CalendarDayAdapter.OnButtonClickListener) : RecyclerView.Adapter<CalendarAdapter.CalendarPickerViewHolder>() { // 全局共用的ViewHolder复用池 private val sharedViewPool = RecyclerView.RecycledViewPool().apply { setMaxRecycledViews(0, 50) // 固定viewType为0,设置足够缓存数量 } inner class CalendarPickerViewHolder(currentItemView: View) : RecyclerView.ViewHolder(currentItemView), View.OnClickListener { // 缓存所有日期的Adapter,避免重复创建 private val dayAdapters = mutableMapOf<View, CalendarDayAdapter>() private val layoutManager = LinearLayoutManager(currentItemView.context) init { // 一次性初始化所有日期的RecyclerView配置 val weeks = listOf( currentItemView.findViewById<LinearLayout>(R.id.week1), currentItemView.findViewById<LinearLayout>(R.id.week2), currentItemView.findViewById<LinearLayout>(R.id.week3), currentItemView.findViewById<LinearLayout>(R.id.week4), currentItemView.findViewById<LinearLayout>(R.id.week5) ) weeks.forEach { weekLayout -> for (i in 0..6) { val dayItem = weekLayout.getChildAt(i) dayItem.setOnClickListener(this) val dayRecyclerView = dayItem.findViewById<RecyclerView>(R.id.notificationView) // 仅设置一次LayoutManager和复用池 dayRecyclerView.layoutManager = layoutManager dayRecyclerView.setRecycledViewPool(sharedViewPool) // 初始化空Adapter并缓存 val emptyAdapter = CalendarDayAdapter(mutableListOf(), 0, 0, 0, notificationlistener, 0) dayRecyclerView.adapter = emptyAdapter dayAdapters[dayItem] = emptyAdapter } } } override fun onClick(p0: View?) { val position: Int = bindingAdapterPosition val currentTodoItem = items[position] if (position != RecyclerView.NO_POSITION) { dayListener.onDayClick(p0, currentTodoItem.month, currentTodoItem.year) } } // 更新日期数据的方法,替代重建Adapter fun updateDayData(dayItem: View, data: MutableList<NotificationData>, day: Int, month: Int, year: Int, datePosition: Int) { val adapter = dayAdapters[dayItem] ?: return adapter.items.clear() adapter.items.addAll(data) adapter.day = day adapter.month = month adapter.year = year adapter.datePosition = datePosition adapter.notifyDataSetChanged() } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarPickerViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.calender_month, parent, false) return CalendarPickerViewHolder(view) } override fun onBindViewHolder(holder: CalendarPickerViewHolder, position: Int) { val currentTodoItem = items[position] holder.itemView.apply { if(currentTodoItem.name.trim().isNotEmpty()) { findViewById<TextView>(R.id.monthYearText).text = currentTodoItem.name } val week1 = findViewById<LinearLayout>(R.id.week1) val week2 = findViewById<LinearLayout>(R.id.week2) val week3 = findViewById<LinearLayout>(R.id.week3) val week4 = findViewById<LinearLayout>(R.id.week4) val week5 = findViewById<LinearLayout>(R.id.week5) fun getMonthLength(year:Int,month:Int): Int { if(year % 4 == 0 && month == 2){ return 29 } return when (month) { 1 -> 31 2 -> 28 3 -> 31 4 -> 30 5 -> 31 6 -> 30 7 -> 31 8 -> 31 9 -> 30 10 -> 31 11 -> 30 12 -> 31 else -> 0 } } var index = 0 fun equalDate(year:Int,month:Int,day:Int): MutableList<NotificationData> { for(i in 0 until currentTodoItem.notificationdata.size){ if (currentTodoItem.notificationdata[i].year == year && currentTodoItem.notificationdata[i].month == month && currentTodoItem.notificationdata[i].day == day) { index = i return currentTodoItem.notificationdata[i].data } } return mutableListOf(NotificationData("",WHITE,"","",Time_hour_minutes(0,0))) } var week = 1 val dayOfLastMonthDate = LocalDate.of(currentTodoItem.year, currentTodoItem.month, 1).dayOfWeek.value val monthLength = getMonthLength(currentTodoItem.year,currentTodoItem.month) var previousMonthLength: Int = if(currentTodoItem.month>1){ getMonthLength(currentTodoItem.year,currentTodoItem.month-1) } else { getMonthLength(currentTodoItem.year-1,12) } // 修改为更新已有Adapter数据,不再创建新Adapter fun updateWeek1(weeks : LinearLayout) { for (i in 1 until dayOfLastMonthDate){ val dayItem = weeks.getChildAt(i-1) val dayText = dayItem.findViewById<TextView>(R.id.date) val dayValue = previousMonthLength - dayOfLastMonthDate + i + 1 dayText.text = dayValue.toString() val data = if(currentTodoItem.month>1){ equalDate(currentTodoItem.year,currentTodoItem.month-1, dayValue) } else { equalDate(currentTodoItem.year-1,12, dayValue) } val monthVal = if(currentTodoItem.month>1) currentTodoItem.month-1 else 12 val yearVal = if(currentTodoItem.month>1) currentTodoItem.year else currentTodoItem.year-1 holder.updateDayData(dayItem, data, dayValue, monthVal, yearVal, index) } for (i in 0..7-dayOfLastMonthDate) { val dayItem = weeks.getChildAt(i+dayOfLastMonthDate-1) val dayText = dayItem.findViewById<TextView>(R.id.date) if(week >= monthLength){ dayText.text = "0" val data = equalDate(currentTodoItem.year,currentTodoItem.month,0) holder.updateDayData(dayItem, data, 0, currentTodoItem.month, currentTodoItem.year, index) } else { dayText.text = week.toString() val data = equalDate(currentTodoItem.year,currentTodoItem.month,week) holder.updateDayData(dayItem, data, week, currentTodoItem.month, currentTodoItem.year, index) week++ } } } fun updateWeek(weeks : LinearLayout) { for (i in 0..6) { val dayItem = weeks.getChildAt(i) val dayText = dayItem.findViewById<TextView>(R.id.date) if(week >= monthLength){ for (j in 1..6-i){ val nextDayItem = weeks.getChildAt(i+j) val nextDayText = nextDayItem.findViewById<TextView>(R.id.date) nextDayText.text = j.toString() val data = if(currentTodoItem.month < 12){ equalDate(currentTodoItem.year,currentTodoItem.month+1,j) } else { equalDate(currentTodoItem.year+1,1,j) } val monthVal = if(currentTodoItem.month <12) currentTodoItem.month+1 else 1 val yearVal = if(currentTodoItem.month <12) currentTodoItem.year else currentTodoItem.year+1 holder.updateDayData(nextDayItem, data, j, monthVal, yearVal, index) } break } else { dayText.text = week.toString() val data = equalDate(currentTodoItem.year,currentTodoItem.month,week) holder.updateDayData(dayItem, data, week, currentTodoItem.month, currentTodoItem.year, index) week++ } } } updateWeek1(week1) updateWeek(week2) updateWeek(week3) updateWeek(week4) updateWeek(week5) } } override fun getItemId(position: Int): Long { return position.toLong() } override fun getItemViewType(position: Int): Int { return 0 // 所有月份项布局一致,返回固定值 } override fun getItemCount(): Int = items.size interface OnDayClickListener{ fun onDayClick(view: View?, month: Int, year: Int) } }
修改CalendarDayAdapter
class CalendarDayAdapter( var items: MutableList<NotificationData>, var day: Int, var month: Int, var year: Int, val listener: OnButtonClickListener, var datePosition: Int ) : RecyclerView.Adapter<CalendarDayAdapter.CalendarPickerViewHolder>() { inner class CalendarPickerViewHolder(currentItemView: View) : RecyclerView.ViewHolder(currentItemView), View.OnClickListener{ private val notificationText = currentItemView.findViewById<TextView>(R.id.notification) init { notificationText.setOnClickListener(this) } override fun onClick(p0: View?) { val position : Int = bindingAdapterPosition if(position != RecyclerView.NO_POSITION){ listener.onButtonClick(day,month,year,p0, position, datePosition) } } // 封装数据绑定逻辑 fun bind(data: NotificationData) { if (data.title.isNotBlank()) { notificationText.text = data.title notificationText.background.setTint(data.color) notificationText.visibility = View.VISIBLE } else { notificationText.visibility = View.INVISIBLE } } } override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): CalendarPickerViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.notification, parent, false) return CalendarPickerViewHolder(view) } override fun onBindViewHolder(holder: CalendarPickerViewHolder, position: Int) { holder.bind(items[position]) } override fun getItemId(position: Int): Long { return position.toLong() } override fun getItemViewType(position: Int): Int { return 0 // 所有笔记项布局一致,返回固定值 } override fun getItemCount(): Int = items.size interface OnButtonClickListener{ fun onButtonClick(day :Int,month :Int,year :Int, view : View?, position : Int, datePosition : Int) } }
3. 修改说明
- ViewHolder初始化优化:在
CalendarPickerViewHolder的init方法中一次性配置所有日期RecyclerView的LayoutManager和复用池,缓存空Adapter。 - 数据更新逻辑调整:将原创建Adapter的逻辑改为调用
updateDayData更新已有Adapter的数据源,避免重复创建对象。 - ViewPool修复:两个适配器的
getItemViewType均返回固定值0,确保全局复用池可以正常跨位置复用ViewHolder。
内容的提问来源于stack exchange,提问作者lolboer9999999
相关产品推荐
相关产品推荐

