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

Android翻译匹配应用RecyclerView正确匹配项无法删除问题

Android语言翻译匹配应用:条目移除功能故障

我正在开发一款Android语言翻译匹配应用,功能如下:

  • 用户匹配两种语言的条目,匹配正确时点击项显示正确图标,错误则显示错误图标
  • 正确匹配的两个条目需从列表中移除,但尝试多种方法后始终无法完美实现该移除功能

匹配逻辑基于count值:同一组的两种语言文本(例如英文"How are u"与法语"Comment vas-tu")对应相同的count值,以此判定匹配正确。count值在Activity中通过循环赋值给MatchDataHolder,同一组的两个条目count相同但列表索引不同。


Activity类

package com.blablacards.activity

import android.os.Bundle
import android.view.View
import android.widget.Toast
import androidx.recyclerview.widget.GridLayoutManager
import com.blablacards.R
import com.blablacards.adapter.MatchAdapter
import com.blablacards.app.retrofit.ApiCall
import com.blablacards.model.FavListItem
import com.blablacards.model.MatchDataHolder
import com.blablacards.model.MatchList
import com.blablacards.model.ResponseFavList
import com.blablacards.utils.AppPref
import com.blablacards.utils.Constants
import kotlinx.android.synthetic.main.activity_match.*
import retrofit2.Call
import retrofit2.Callback
import retrofit2.Response

class MatchActivity : BaseActivity(), View.OnClickListener {
    var matchDataHolder: ArrayList<MatchDataHolder>? = ArrayList<MatchDataHolder>()
    var favWordList = ArrayList<FavListItem>()
    var matchList = ArrayList<MatchList>()
    var categoryId: Int? = null
    var categoryName: String? = null

    lateinit var matchAdapter: MatchAdapter

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_match)

        img_back.setOnClickListener(this)

        getBundleData()

        tv_header.text = categoryName

        favWordList.clear()
        calFavCategoryListApi("DESC")
        // setAdapter()

    }

    private fun setAdapter(favWordList: ArrayList<MatchDataHolder>) {
        Toast.makeText(this@MatchActivity, "Adapter已调用", Toast.LENGTH_SHORT).show()
        matchAdapter = MatchAdapter(
            this@MatchActivity,
            favWordList
        )
        println("FAV LIST SIZE ->" + favWordList.size)
        rv_match.layoutManager = GridLayoutManager(this@MatchActivity, 2)
        rv_match.adapter = matchAdapter
    }

    private fun getBundleData() {
        val extras = intent.extras
        if (extras != null) {
            categoryId = extras.getInt("CategoryId")
            categoryName = extras.getString("CategoryName")
            println("!!!categoryId = ${categoryId}")
        }
    }

    private fun calFavCategoryListApi(sortBy: String) {
        ApiCall.initApiCall(Constants.BASE_URL).getFavcategoryList(
            "Bearer" + AppPref.getValue(AppPref.TOKEN, ""),
            AppPref.getValue(AppPref.LOGIN_ID, "")!!, categoryId.toString(), "", "", sortBy
        ).enqueue(object : Callback<ResponseFavList> {
            override fun onResponse(
                call: Call<ResponseFavList>,
                response: Response<ResponseFavList>
            ) {
                if (response.isSuccessful) {
                    if (response.body()!!.status!!) {
                        favWordList.addAll(response.body()!!.data!!)
                        //
                        for (i in 0 until favWordList.size) {
                            matchDataHolder!!.add(MatchDataHolder(favWordList[i].sended_text.toString(),i))
                            matchDataHolder!!.add(MatchDataHolder(favWordList[i].return_text.toString(),i))

                        }
                        for(i in 0 until matchDataHolder!!.size){
                         println("DATA HOLDER ->"+ matchDataHolder!![i].text)
                            println("DATA HOLDER  Count->"+ matchDataHolder!![i].count)
                        }
                        //
                        setAdapter(matchDataHolder!!)

                    } else {
                        showSnackBar(response.message())
                    }
                }
            }

            override fun onFailure(call: Call<ResponseFavList>, t: Throwable) {
                println("!!!t.message = ${t.message}")
            }
        })
    }

    override fun onClick(v: View?) {
        when (v!!.id) {
            R.id.img_back -> {
                onBackPressed()
            }
        }
    }
}

Adapter类

package com.blablacards.adapter

import android.content.Context
import android.os.Handler

import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import android.widget.Toast
import androidx.recyclerview.widget.RecyclerView
import com.blablacards.R
import com.blablacards.model.MatchDataHolder
import kotlinx.android.synthetic.main.row_match.view.*

class MatchAdapter(
    var context: Context,
    var favWordList: ArrayList<MatchDataHolder>,
) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {
    var index1 = -1
    var index2 = -1
    var enabledFlag = false
    var oldPos = -1
    var newPos = -1
    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        return ViewHolder(
            LayoutInflater.from(context)
                .inflate(R.layout.row_match, parent, false)
        )
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        (holder as MatchAdapter.ViewHolder).bind(position)

        holder.itemView.rl_match.setBackgroundColor(context.resources.getColor(R.color.white))
        holder.itemView.tv_match.visibility = View.VISIBLE

        holder.itemView.rl_match.setOnClickListener(View.OnClickListener {
            if (!enabledFlag) {
                println(enabledFlag)
                println("IF块")
                it.rl_match.setBackgroundColor(context.resources.getColor(R.color.orange_light3))
                holder.itemView.tv_match.visibility = View.INVISIBLE
                oldPos = favWordList[position].count
                println("OLD POS ->" + oldPos)
                index1 = position
                println("index 1 = " + index1)
                enabledFlag = true
            } else {

                // 显示正确/错误状态 //
                println("ELSE块")
                enabledFlag = false
                newPos = favWordList[position].count
                println("NEW POS ->" + newPos)
                index2 = position
                println("index 2 = " + index2)
                showCorrectOrFalse(holder.itemView, favWordList[position].count)

            }
        })

    }

    override fun getItemCount(): Int {
        return favWordList.size
    }
    
    private fun timeHandler(itemView: View, status: String, position: Int) {
        Handler().postDelayed({
            if (status == "right") {
                println("进入正确逻辑")
                itemView.tv_match.visibility = View.VISIBLE
                itemView.rl_match.setBackgroundColor(context.resources.getColor(R.color.white))
                itemView.img_right_or_wrong.visibility = View.INVISIBLE
//                favWordList.drop(index1)
//                favWordList.drop(index2)
/// 问题就在这里,无法完美移除条目 //
                favWordList.remove(MatchDataHolder(favWordList[index1].text,newPos))
                favWordList.remove(MatchDataHolder(favWordList[index2].text,oldPos))
                notifyItemRemoved(index1)
                notifyItemRemoved(index2)
                notifyItemRangeChanged(index1,favWordList.size)
                notifyItemRangeChanged(index2,favWordList.size)
                notifyDataSetChanged()

            }
            if (status == "wrong") {
                println("进入错误逻辑")
                itemView.tv_match.visibility = View.VISIBLE
                itemView.rl_match.setBackgroundColor(context.resources.getColor(R.color.white))
                itemView.img_right_or_wrong.visibility = View.INVISIBLE
                notifyDataSetChanged()
            }
        }, 2000)
    }

    private fun showCorrectOrFalse(itemView: View, position: Int) {
        println("调用显示正确/错误方法")
        // 点击后调用handler //
        println("OLD POS " + oldPos + " New POS " + newPos)
        if (oldPos == newPos) {
            itemView.img_right_or_wrong.setImageDrawable(context.resources.getDrawable(R.drawable.right_game))
            Toast.makeText(context, "正确", Toast.LENGTH_SHORT).show()
            timeHandler(itemView, "right", position)
        } else {
            itemView.img_right_or_wrong.setImageDrawable(context.resources.getDrawable(R.drawable.wrong_game))
            Toast.makeText(context, "错误", Toast.LENGTH_SHORT).show()
            timeHandler(itemView, "wrong", position)
        }
    }

    internal inner class ViewHolder(view: View) : RecyclerView.ViewHolder(view) {
        fun bind(position: Int) {
            itemView.findViewById<TextView>(R.id.tv_match).text =
                favWordList[position].text

        }
    }
}

数据模型类(MatchDataHolder)

class MatchDataHolder(text:String,num:Int) {
    var count = num
    var text =text
}

问题修复方案

核心问题分析

  1. 对象匹配失败:调用favWordList.remove(MatchDataHolder(...))时,由于MatchDataHolder未重写equals()和hashCode(),ArrayList无法识别新建对象与列表中原有对象为同一数据,导致移除操作无效。
  2. 索引处理错误:移除第一个元素后,列表长度缩短,第二个元素的索引会自动偏移,直接使用原始index2会导致移除错误的元素或索引越界。
  3. 冗余刷新:notifyDataSetChanged()会强制刷新整个列表,效率低下,且会覆盖局部更新的状态。

修复步骤

1. 重写MatchDataHolder的equals和hashCode方法

确保ArrayList能正确匹配相同数据的对象:

class MatchDataHolder(text: String, num: Int) {
    var count = num
    var text = text

    override fun equals(other: Any?): Boolean {
        if (this === other) return true
        if (javaClass != other?.javaClass) return false
        other as MatchDataHolder
        if (count != other.count) return false
        if (text != other.text) return false
        return true
    }

    override fun hashCode(): Int {
        var result = count
        result = 31 * result + text.hashCode()
        return result
    }
}

2. 修改Adapter中的条目移除逻辑

优先移除索引较大的元素,避免索引偏移问题,直接操作列表索引而非新建对象:

private fun timeHandler(itemView: View, status: String, position: Int) {
    Handler().postDelayed({
        if (status == "right") {
            println("进入正确逻辑")
            itemView.tv_match.visibility = View.VISIBLE
            itemView.rl_match.setBackgroundColor(context.resources.getColor(R.color.white))
            itemView.img_right_or_wrong.visibility = View.INVISIBLE
            
            // 先移除索引大的元素,防止小索引移除后大索引偏移
            val firstIdx = minOf(index1, index2)
            val secondIdx = maxOf(index1, index2)
            
            // 直接通过索引移除元素
            favWordList.removeAt(secondIdx)
            favWordList.removeAt(firstIdx)
            
            // 通知RecyclerView高效更新
            notifyItemRemoved(secondIdx)
            notifyItemRemoved(firstIdx)
            notifyItemRangeChanged(firstIdx, favWordList.size - firstIdx)
            
            // 重置选中状态
            index1 = -1
            index2 = -1
        }
        if (status == "wrong") {
            println("进入错误逻辑")
            itemView.tv_match.visibility = View.VISIBLE
            itemView.rl_match.setBackgroundColor(context.resources.getColor(R.color.white))
            itemView.img_right_or_wrong.visibility = View.INVISIBLE
            
            // 重置选中状态并刷新之前选中的条目
            notifyItemChanged(index1)
            index1 = -1
            index2 = -1
        }
    }, 2000)
}

3. 优化匹配结果展示逻辑

匹配成功时同步刷新第一个选中条目的UI状态:

private fun showCorrectOrFalse(itemView: View, position: Int) {
    println("调用显示正确/错误方法")
    println("OLD POS " + oldPos + " New POS " + newPos)
    if (oldPos == newPos) {
        itemView.img_right_or_wrong.setImageDrawable(context.resources.getDrawable(R.drawable.right_game))
        Toast.makeText(context, "匹配正确", Toast.LENGTH_SHORT).show()
        // 刷新第一个选中条目的UI
        notifyItemChanged(index1)
        timeHandler(itemView, "right", position)
    } else {
        itemView.img_right_or_wrong.setImageDrawable(context.resources.getDrawable(R.drawable.wrong_game))
        Toast.makeText(context, "匹配错误", Toast.LENGTH_SHORT).show()
        // 刷新第一个选中条目的UI
        notifyItemChanged(index1)
        timeHandler(itemView, "wrong", position)
    }
}

内容的提问来源于stack exchange,提问作者Shivam Sharma

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.19 01:50:36