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 }
问题修复方案
核心问题分析
- 对象匹配失败:调用
favWordList.remove(MatchDataHolder(...))时,由于MatchDataHolder未重写equals()和hashCode(),ArrayList无法识别新建对象与列表中原有对象为同一数据,导致移除操作无效。 - 索引处理错误:移除第一个元素后,列表长度缩短,第二个元素的索引会自动偏移,直接使用原始
index2会导致移除错误的元素或索引越界。 - 冗余刷新:
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
相关产品推荐
相关产品推荐

