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

RecyclerView滚动时Item值重置问题排查及解决方案咨询

解决RecyclerView滚动时Item中EditText值重置的问题

Hey, 我看了你的代码,马上就发现问题所在了——你没把用户输入的EditText值同步到Adapter的数据源里,反而存在ViewHolder的savedStock里了!RecyclerView的ViewHolder是会被复用的,滚动的时候旧的ViewHolder会被拿来绑定新位置的数据,你存在ViewHolder里的临时数据自然就没了,导致值重置。完全不用换成ListView,RecyclerView的复用机制是它的优势,只要改对数据维护的逻辑就行,下面是具体的解决方案:

核心问题分析

你的StockFragmentHolder里维护了一个savedStock对象,填充UI和处理点击时都用这个临时对象,而不是更新Adapter的listStocks数据源。当RecyclerView滚动复用ViewHolder时,这个savedStock会被新的位置数据覆盖,之前用户输入的值就丢失了。

具体修复步骤

1. 移除ViewHolder中的临时savedStock,所有数据操作直接基于Adapter的数据源

ViewHolder只负责UI绑定和事件监听,不应该存储数据,数据统一由Adapter的listStocks管理。

2. 给EditText添加文本监听,实时更新数据源

用户输入时,立刻把输入值同步到listStocks对应的Stock对象里,这样滚动时重新绑定数据就能拿到最新值。

3. 修正populateView方法,直接从数据源获取数据填充UI

不再依赖ViewHolder的临时对象,直接用传入的stock参数(也就是数据源里的对象)来填充EditText和其他控件。

4. 修复Adapter中的几个语法错误

  • onCreateViewHolder没有返回ViewHolder实例
  • onBindViewHolder需要把RecyclerView.ViewHolder强转成你的StockFragmentHolder

修改后的完整代码

StocksAdapter 修正版

class StocksAdapter() : RecyclerView.Adapter<StocksAdapter.StockFragmentHolder>() {

    // 扩展函数可以放在类外面或者companion object里,避免ViewHolder混乱
    fun Double.format(digits: Int) = java.lang.String.format("%,.${digits}f", this)

    inner class StockFragmentHolder(view: View) : RecyclerView.ViewHolder(view) {
        lateinit var numberOfShares: EditText
        lateinit var buyPrice: EditText
        lateinit var btnCompute: Button
        lateinit var btnRemove: Button
        lateinit var stockAveragePrice: TextView
        lateinit var stockTotalAmount: TextView

        init {
            numberOfShares = view.findViewById(R.id.stockNumberOfShares)
            buyPrice = view.findViewById(R.id.stockBuyPrice)
            btnCompute = view.findViewById(R.id.btnCompute)
            btnRemove = view.findViewById(R.id.btnRemove)
            stockAveragePrice = view.findViewById(R.id.stockAveragePrice)
            stockTotalAmount = view.findViewById(R.id.stockTotalAmount)
        }

        fun populateView(stock: Stock, onStocksAdapterListener: OnStocksAdapterListener, position: Int) {
            // 直接用数据源的stock对象填充UI
            buyPrice.setText(if (stock.buyPrice <= 0.0) "" else stock.buyPrice.toString())
            numberOfShares.setText(if (stock.numberOfShares <= 0L) "" else stock.numberOfShares.toString())
            stockAveragePrice.text = stock.averagePrice?.format(2) ?: "0"
            stockTotalAmount.text = stock.totalAmount?.format(2) ?: "0"

            // 监听EditText输入,实时更新数据源
            numberOfShares.addTextChangedListener(object : TextWatcher {
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
                override fun afterTextChanged(s: Editable?) {
                    stock.numberOfShares = s?.toString()?.toLongOrNull() ?: 0L
                }
            })

            buyPrice.addTextChangedListener(object : TextWatcher {
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
                override fun afterTextChanged(s: Editable?) {
                    stock.buyPrice = s?.toString()?.toDoubleOrNull() ?: 0.0
                }
            })

            btnCompute.setOnClickListener {
                val currentStock = stock
                if (currentStock.numberOfShares > 0L) {
                    val buyTotalAmount = StocksCalculator.calculateTotalSharesPrice(
                        currentStock,
                        StocksCalculator.TRANSACTION_FEE_BASE_VALUES,
                        Constants.TRANSACTION_TYPE.TRANSACTION_TYPE_BUY
                    )
                    val averagePricePerShare = buyTotalAmount / currentStock.numberOfShares

                    // 计算后更新数据源的字段,同时更新UI
                    currentStock.averagePrice = averagePricePerShare
                    currentStock.totalAmount = buyTotalAmount

                    stockAveragePrice.text = averagePricePerShare.format(2)
                    stockTotalAmount.text = buyTotalAmount.format(2)

                    onStocksAdapterListener.onStocksComputeButtonClicked(
                        currentStock.numberOfShares,
                        averagePricePerShare,
                        buyTotalAmount,
                        position
                    )
                }
            }

            btnRemove.setOnClickListener {
                onStocksAdapterListener.onStocksRemoveButtonClicked(position)
            }
        }
    }

    lateinit var onStocksAdapterListener: OnStocksAdapterListener
    lateinit var listStocks: ArrayList<Stock>

    constructor(listStocks: ArrayList<Stock>, onStocksAdapterListener: OnStocksAdapterListener) : this() {
        this.listStocks = listStocks
        this.onStocksAdapterListener = onStocksAdapterListener
        setHasStableIds(true)
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): StockFragmentHolder {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(R.layout.fragment_stock, parent, false)
        return StockFragmentHolder(itemView) // 修复:返回ViewHolder实例
    }

    override fun getItemViewType(position: Int): Int {
        return position
    }

    override fun getItemCount(): Int {
        return listStocks.size
    }

    override fun onBindViewHolder(holder: StockFragmentHolder, position: Int) {
        val stock = listStocks[position]
        holder.populateView(stock, this.onStocksAdapterListener, position)
    }

    interface OnStocksAdapterListener {
        fun onStocksAddButtonClicked()
        fun onStocksComputeButtonClicked(
            numberOfShares: Long?,
            averagePricePerShare: Double?,
            averageTotalAmount: Double?,
            position: Int
        )
        fun onStocksRemoveButtonClicked(position: Int)
    }
}

补充说明:Stock类需要添加额外字段

为了保存计算后的均价和总额,你的Stock类需要新增两个字段:

data class Stock(
    var numberOfShares: Long = 0L,
    var buyPrice: Double = 0.0,
    var sellPrice: Double = 0.0,
    var averagePrice: Double? = null, // 新增:保存计算后的均价
    var totalAmount: Double? = null   // 新增:保存计算后的总额
)

为什么禁用setIsRecyclable(false)没用?

这个方法只是禁止ViewHolder被复用,滚动时会创建新的ViewHolder,但你依然没有把用户输入的值保存到数据源里,所以当滚动回来时,还是会从原始的listStocks里拿旧数据填充,自然还是会重置。只有把数据同步到数据源,才能保证滚动时显示的是最新值。

内容的提问来源于stack exchange,提问作者Kenzo-kun

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.27 09:41:41