RecyclerView无限滚动随机项重复添加问题的解决咨询
解决RecyclerView滚动到底部重复添加随机园艺贴士的问题
问题描述
开发展示园艺贴士的全屏RecyclerView,需求是滚动到底部加载一条随机贴士,但目前每次滚动到底部会重复添加同一随机项,出现连续重复的贴士。
原代码实现
Adapter
class GardeningTipAdapter(val dataSource:ArrayList<GardeningTipsModel>) : RecyclerView.Adapter<GardeningTipAdapter.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view: View = LayoutInflater.from(parent.context).inflate(R.layout.gardening_tips_row, parent, false) return ViewHolder(view) } override fun getItemCount(): Int { return dataSource.size } override fun onBindViewHolder(holder: ViewHolder, position: Int) { val text = dataSource[position].gardeningTipFull holder.label.text = text } class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) { val label: TextView = itemView.findViewById(R.id.gardening_tips_row) } }
Model
object GardeningTipsData { fun allGardeningTips() : ArrayList<TipsModel>{ return arrayListOf( TipsModel("Gardening Tip 1"), TipsModel("Gardening Tip 2"), TipsModel("Gardening Tip 3"), TipsModel("Gardening Tip 4"), TipsModel("Gardening Tip 5"), TipsModel("Gardening Tip 6"), ) } fun getRandomTip(currentTips: ArrayList<TipsModel>): ArrayList<TipsModel> { val allTipsList = allGardeningTips() val randomIndex = (0 until allTipsList.size).random() val randomTip = allTipsList[randomIndex] currentTips.add(randomTip) return currentTips } }
Scroll Listener
private val existingTipsList = ArrayList<TipsModel>() // Initial setup with a single random tip existingTipsList.add(GardeningTipsData.getRandomTip(existingTipsList).last()) binding.tipRecyclerView.adapter = GardeningTipAdapter(existingTipsList) var loading = true var pastVisiblesItems: Int = 0 var visibleItemCount: Int = 0 var totalItemCount: Int = 0 binding.tipRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (dy > 0) { // check for scroll down visibleItemCount = mLayoutManager.childCount totalItemCount = mLayoutManager.itemCount pastVisiblesItems = mLayoutManager.findFirstVisibleItemPosition() if (loading) { if (visibleItemCount + pastVisiblesItems >= totalItemCount && !lastItemAdded) { loading = false // Get a new random item and append it to the list val newRandomTip = GardeningTipsData.getRandomTip(existingTipsList).last() existingTipsList.add(newRandomTip) println("ADDED NEW!") // Notify the adapter that the data has changed binding.tipRecyclerView.adapter?.notifyItemInserted(existingTipsList.size - 1) lastItemAdded = true loading = true } else { lastItemAdded = false } } } } })
问题根源
- 重复添加逻辑:
getRandomTip方法内部已经执行了currentTips.add(randomTip),但滚动监听里又再次调用existingTipsList.add(newRandomTip),导致同一个随机项被添加两次。 - 状态控制混乱:
loading和lastItemAdded的切换逻辑不合理,导致滚动到底部时多次触发添加操作。 - 无连续去重处理:原逻辑没有避免连续出现相同贴士的机制,即使只添加一次也可能随机到和上一条相同的内容。
解决方案
1. 重构GardeningTipsData,分离数据生成与列表操作
让getRandomTip只负责生成符合要求的随机贴士,不直接修改传入的列表,同时添加连续去重逻辑:
object GardeningTipsData { // 提前初始化所有贴士,避免每次调用都创建新列表 private val allTipsList = arrayListOf( TipsModel("Gardening Tip 1"), TipsModel("Gardening Tip 2"), TipsModel("Gardening Tip 3"), TipsModel("Gardening Tip 4"), TipsModel("Gardening Tip 5"), TipsModel("Gardening Tip 6"), ) // 可选传入上一条贴士,确保新贴士和上一条不同 fun getRandomTip(excludedTip: TipsModel? = null): TipsModel { val availableTips = excludedTip?.let { tip -> allTipsList.filter { it != tip } } ?: allTipsList // 如果所有贴士都被排除(比如只剩最后一条),直接返回原列表随机项 return if (availableTips.isEmpty()) { allTipsList.random() } else { availableTips.random() } } }
2. 简化滚动监听的状态控制
去掉冗余的lastItemAdded变量,只用loading控制加载状态,确保每次滚动到底部只触发一次添加:
private val existingTipsList = ArrayList<TipsModel>() // 初始化第一条随机贴士 val firstTip = GardeningTipsData.getRandomTip() existingTipsList.add(firstTip) binding.tipRecyclerView.adapter = GardeningTipAdapter(existingTipsList) var loading = false binding.tipRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { if (dy <= 0) return // 只处理向下滚动 val layoutManager = recyclerView.layoutManager as LinearLayoutManager val visibleItemCount = layoutManager.childCount val totalItemCount = layoutManager.itemCount val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() // 判断是否滚动到底部且不在加载中 if (!loading && (visibleItemCount + firstVisibleItemPosition) >= totalItemCount) { loading = true // 获取和最后一条不同的随机贴士 val lastTip = existingTipsList.last() val newRandomTip = GardeningTipsData.getRandomTip(lastTip) existingTipsList.add(newRandomTip) // 通知适配器插入新项(比notifyDataSetChanged更高效) binding.tipRecyclerView.adapter?.notifyItemInserted(existingTipsList.size - 1) // 恢复加载状态,允许下次触发 loading = false } } })
3. 统一Adapter泛型
原Adapter泛型与实际使用的TipsModel不匹配,修正后确保类型一致:
class GardeningTipAdapter(val dataSource: ArrayList<TipsModel>) : RecyclerView.Adapter<GardeningTipAdapter.ViewHolder>() { override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder { val view = LayoutInflater.from(parent.context).inflate(R.layout.gardening_tips_row, parent, false) return ViewHolder(view) } override fun getItemCount(): Int = dataSource.size override fun onBindViewHolder(holder: ViewHolder, position: Int) { holder.label.text = dataSource[position].gardeningTipFull } class ViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { val label: TextView = itemView.findViewById(R.id.gardening_tips_row) } }
内容的提问来源于stack exchange,提问作者Joe
相关产品推荐
相关产品推荐

