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

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

问题根源

  1. 重复添加逻辑:getRandomTip方法内部已经执行了currentTips.add(randomTip),但滚动监听里又再次调用existingTipsList.add(newRandomTip),导致同一个随机项被添加两次。
  2. 状态控制混乱:loading和lastItemAdded的切换逻辑不合理,导致滚动到底部时多次触发添加操作。
  3. 无连续去重处理:原逻辑没有避免连续出现相同贴士的机制,即使只添加一次也可能随机到和上一条相同的内容。

解决方案

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.28 13:35:55