Recyclerview 数据回收

huangapple go评论64阅读模式
英文:

Recyclerview data recycling

问题

我正在开发一个Android的电子商务应用程序,我使用RecyclerView来显示产品。在每张卡片上都有一个计数器来增加/减少数量。

我的适配器代码如下:

class TestAdapter(
    private val context: Context,
    private val todayList: ArrayList<TodayDealItem>
) :
    RecyclerView.Adapter<TestAdapter.MyView>() {

    class MyView(itemView: View) : RecyclerView.ViewHolder(itemView) {
        var prodId: TextView = itemView.findViewById(R.id.prodid)
        var qty: EditText = itemView.findViewById(R.id.qty)
        var sub: Button = itemView.findViewById(R.id.sub)
        var add: Button = itemView.findViewById(R.id.add)
    }

    private val quantityMap: HashMap<String, Int> = HashMap()

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyView {
        val itemView = LayoutInflater.from(parent.context)
            .inflate(R.layout.test_layout, parent, false)
        return MyView(itemView)
    }

    override fun onBindViewHolder(holder: MyView, position: Int) {
        val currentItem = todayList[position]
        val resources = context.resources
        val productUrl =
            resources.getString(R.string.domain_url) + "api/v2/products/" + currentItem.id
        holder.prodId.text = currentItem.id
        val quantity = quantityMap[currentItem.id] ?: 1
        holder.qty.setText(quantity.toString())

        holder.add.setOnClickListener {
            val currCount = holder.qty.text.toString()
            val currCountAsNum: Int? = currCount.toIntOrNull()
            val stringRequest = StringRequest(
                Request.Method.GET,
                productUrl,
                { s: String? ->
                    try {
                        val jsonObject = JSONObject(s.toString())
                        val dataArray = jsonObject.getJSONArray("data")

                        if (dataArray.length() > 0) {
                            val dataObject = dataArray.getJSONObject(0)

                            if (dataObject.has("current_stock")) {
                                val currentStock = dataObject.getInt("current_stock")
                                if (currCountAsNum!! < currentStock) {
                                    holder.qty.setText((currCountAsNum + 1).toString())
                                    quantityMap[currentItem.id] = currCountAsNum + 1
                                } else {
                                    Toast.makeText(
                                        context,
                                        "Maximum Stock Reached!",
                                        Toast.LENGTH_LONG
                                    ).show()
                                }
                            }
                        }

                    } catch (e: JSONException) {
                        e.printStackTrace()
                    }
                }
            ) { volleyError: VolleyError ->
                Toast.makeText(context, volleyError.message, Toast.LENGTH_LONG).show()
            }

            val requestQueue = Volley.newRequestQueue(context)
            requestQueue.add(stringRequest)
        }
        holder.sub.setOnClickListener {

            val currCount = holder.qty.text.toString()
            val currCountAsNum: Int? = currCount.toIntOrNull()
            if (currCountAsNum!! > 1) {
                holder.qty.setText((currCountAsNum - 1).toString())
                quantityMap[currentItem.id] = currCountAsNum - 1
            } else {
                holder.qty.setText((currCountAsNum - 1).toString())
                quantityMap[currentItem.id] = currCountAsNum - 1
            }
        }
    }

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

问题是,当我通过增加计数器来添加一些项目时,如果滚动,它会自动返回到0。有没有办法阻止这种情况,保持数量不变?

示例图片在这里

我尝试过没有HashMap。这会保留数据。但是,在这种方法中,甚至没有触摸它们,就会更改多个产品的数量。

英文:

I'm developing an e-commerce app in android and I used Recyclerview to display products. In each card, there is a counter to increase/decrease the quantity.

My adapter code:

class testadapter(
private val context: Context,
private val todaylist: ArrayList&lt;TodaydealItem&gt;
) :
RecyclerView.Adapter&lt;testadapter.MyView&gt;() {
class MyView(itemView: View) : RecyclerView.ViewHolder(itemView) {
var prodid: TextView = itemView.findViewById(R.id.prodid)
var qty: EditText = itemView.findViewById(R.id.qty)
var sub: Button = itemView.findViewById(R.id.sub)
var add: Button = itemView.findViewById(R.id.add)
}
private val quantityMap: HashMap&lt;String, Int&gt; = HashMap()
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): MyView {
val itemView = LayoutInflater.from(parent.context)
.inflate(R.layout.testlayout, parent, false)
return MyView(itemView)
}
override fun onBindViewHolder(holder: MyView, position: Int) {
val currentItem = todaylist[position]
val resources = context.resources
val producturl =
resources.getString(R.string.domain_url) + &quot;api/v2/products/&quot; + currentItem.id
holder.prodid.text = currentItem.id
val quantity = quantityMap[currentItem.id] ?: 1
holder.qty.setText(quantity.toString())
holder.add.setOnClickListener {
val currcount = holder.qty.text.toString()
val currcountasnum: Int? = currcount.toIntOrNull()
val stringRequest = StringRequest(
Request.Method.GET,
producturl,
{ s: String? -&gt;
try {
val jsonObject = JSONObject(s.toString())
val dataArray = jsonObject.getJSONArray(&quot;data&quot;)
if (dataArray.length() &gt; 0) {
val dataObject = dataArray.getJSONObject(0)
if (dataObject.has(&quot;current_stock&quot;)) {
val currentStock = dataObject.getInt(&quot;current_stock&quot;)
if (currcountasnum!! &lt; currentStock) {
holder.qty.setText((currcountasnum + 1).toString())
} else {
Toast.makeText(
context,
&quot;Maximum Stock Reached!&quot;,
Toast.LENGTH_LONG
).show()
}
}
}
} catch (e: JSONException) {
e.printStackTrace()
}
}
) { volleyError: VolleyError -&gt;
Toast.makeText(context, volleyError.message, Toast.LENGTH_LONG).show()
}
val requestQueue = Volley.newRequestQueue(context)
requestQueue.add(stringRequest)
}
holder.sub.setOnClickListener {
val currcount = holder.qty.text.toString()
val currcountasnum: Int? = currcount.toIntOrNull()
if (currcountasnum!! &gt; 1) {
holder.qty.setText((currcountasnum - 1).toString())
} else {
holder.qty.setText((currcountasnum - 1).toString())
}
}
}
override fun getItemCount(): Int {
return todaylist.size
}
}

The issue is, when I add some items by increasing the counter, it automatically goes back to 0 if scroll. Is there any way to stop that and keep the quantity still?
sample image here

I tried it without the Hashmap. that keeps the data. But, in that method, more than one product's quantity changes without even touching them.

答案1

得分: 1

我认为您只需通过调用 holder.qty.setText() 在 ViewHoler 上更新数据,因此当滚动时,onBindViewHolder 将从 quantityMap 获取值并重新渲染。

您应该更新 todaylist 并调用 notifyItemChanged 以重新渲染此视图持有者。我认为不需要使用 HashMap 来存储 qty,应该只使用一个集合来加载列表数据,可以在 todayList 的项目中创建字段 quantity。

override fun onBindViewHolder(holder: MyView, position: Int) {
  //...
  holder.add.setOnClickListener {
     if (currcountasnum!! < currentStock) {
         todaylist[position].quantity = (currcountasnum + 1).toString()
         notifyItemChanged(position)
     }
   }
}
英文:

I think you just update data on ViewHoler by call holder.qty.setText(), so when scrolling the onBindViewHolder will get value from quantityMap and re-render

You should update todaylist and call notifyItemChanged to re-render this view holder. I think no need to use HashMap to store qty, should use only one collection to load list data, can create field quantity into item of todayList

override fun onBindViewHolder(holder: MyView, position: Int) {
//...
holder.add.setOnClickListener {
if (currcountasnum!! &lt; currentStock) {
todaylist[position].quantity = (currcountasnum + 1).toString()
notifyItemChanged(position)
}
}
}

答案2

得分: 0

因为MVC的法则,我不在适配器内添加任何实际的点击监听器。我拥有在适配器内调用的回调函数,而调用函数则监听这些回调。

所以在适配器内部,我有类似以下的内容:

class SomeAdapter(val itemLst: MutableList<ItemModel>, val ctx: FragmentActivity) : RecyclerView.Adapter<SomeAdapter.ViewHolder>(){
    private lateinit var binding: AdapterItemBinding
    var onItemClick: ((ItemModel) -> Unit)? = null
    var onCloseClick: ((ItemModel, Int) -> Unit)? = null

    // 常规的模板代码

    override fun onBindViewHolder(holder: SomeAdapter.ViewHolder, position: Int) {
        val item = itemLst[position]
        holder.bind(item)
    }

    inner class ViewHolder(private val binding: AdapterSocketNotifBinding) :
        RecyclerView.ViewHolder(binding.root) {

        fun bind(item: ItemModel){
            binding.item = item

            // 这是这个问题的重要部分
            binding.someView.setOnClickListener{
                onCloseClick?.invoke(itemLst[adapterPosition], adapterPosition)
            }
        }

        // 还有这个
        init {
            itemView.setOnClickListener {
                onItemClick?.invoke(itemLst[adapterPosition])
            }
        }
    }
}

然后在调用方的地方,无论在何处实例化此适配器:

itemAdapter.onCloseClick = { it, pos ->
    run {
        // 在这里做你想做的事情
    }
}

对于单参数的点击监听器,要简单得多:

itemAdapter.onClick = {
    // 变量 "it" 会自动等于 SomeItem
}

我知道这个答案看起来相当复杂,但我想展示的是一种让你能够轻松访问你的 FragmentActivity 变量的方法,这样你就不必费心如何更新适配器可能无法轻松访问的变量。

英文:

Because of the laws of MVC, I don't add any actual click listeners inside an adapter. What I have are callbacks that gets invoked inside the adapter and the calling function listens to.

So inside the adapter, I have something like:

class SomeAdapter(val itemLst: MutableList&lt;ItemModel&gt;, val ctx: FragmentActivity) : RecyclerView.Adapter&lt;SomeAdapter.ViewHolder&gt;(){
private lateinit var binding: AdapterItemBinding
var onItemClick: ((ItemModel) -&gt; Unit)? = null
var onCloseClick: ((ItemModel, Int) -&gt; Unit)? = null
...usual boiler plate
override fun onBindViewHolder(holder: SomeAdapter.ViewHolder, position: Int) {
val item = itemLst[position]
holder.bind(item)
}
inner class ViewHolder(private val binding: AdapterSocketNotifBinding) :
RecyclerView.ViewHolder(binding.root) {
fun bind(item: ItemModel){
binding.item = item
//the important part for this question
binding.someView.setOnClickListener{
onCloseClick?.invoke(itemLst[adapterPosition], adapterPosition)
}
}
//and this
init {
itemView.setOnClickListener {
onItemClick?.invoke(itemLst[adapterPosition])
}
}
}
}

Then on the calling side, wherever you instantiate this adapter:

itemAdapter.onCloseClick = { it, pos -&gt;
run {
//do whatever you want here
}
}

For the single argument click listeners, it's much simpler

itemAdapter.onClick = {
//the variable it would automatically be equal to SomeItem
}

I know this answer seems quite convoluted, but what I wanted to show is a way for you to have easy access to your Fragment or Activity variables so you don't have to wrap your head around how you'll update variables that your adapter probably don't have have an easy access to.

答案3

得分: 0

你需要将计数器的值存储在你的列表模型类中,这样每当你在 onBindViewHolder() 中获取值时,它将获得正确的值,而不会重置计数器。是的,当你在点击时更改计数器时,你需要执行 notifyItemChanged(position)

英文:

You'll have to store the counter value in model class of your list, so whenever you get value from that in onBindViewHolder() it'll get the proper value and not reset the counter. And Yes, you'll have to do notifyItemChanged(position) when you change the counter on click.

huangapple
  • 本文由 发表于 2023年7月20日 14:40:03
  • 转载请务必保留本文链接:https://go.coder-hub.com/76727277.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定