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

Kotlin GridView点击与滚动时显示异常问题排查

问题分析与解决方案

嗨,我仔细看了你的代码,你遇到的点击选中两个项、滚动时选中状态乱跳的问题,核心原因是没处理好GridView的View复用机制,加上你直接在点击事件里修改View状态的方式不对,具体来说:

为什么会出现这个问题?

  1. GridView的View复用特性:GridView只会创建当前屏幕可见的item View,当滚动时,离开屏幕的View会被回收,用来显示新进入屏幕的item。你之前修改了某个View的背景/文字颜色,没有通过Adapter统一管理状态,导致复用的时候旧状态被带到新item上,看起来像是“随机选中”。
  2. 重置状态的范围有限:你在点击事件里遍历GvAlphabets.getChildCount()来重置状态,但这个方法只能获取当前屏幕可见的item,那些已经滚出屏幕的View根本没被重置,等它们滚回来的时候,还是保持着之前被修改的状态。
  3. 选中状态没有持久化记录:你没有专门保存当前选中的位置,每次点击只修改了当前View的状态,没有告诉Adapter哪个位置是真正选中的,导致复用View时无法正确恢复状态。

具体修复步骤

1. 在Activity中添加选中位置的记录

首先,在HomeActivity里添加一个变量,用来保存当前选中的item位置:

class HomeActivity : AppCompatActivity() {
    // 新增:保存当前选中的位置,默认-1表示没有选中
    private var selectedPosition = -1
    // ... 其他变量

2. 修改Adapter,让它负责管理item的选中状态

把选中状态的判断逻辑放到Adapter的getView方法里,这样每次复用View时,都会根据selectedPosition来设置正确的状态:

  • 修改Adapter的构造函数,传入selectedPosition的引用
  • 添加方法更新选中位置并通知Adapter刷新
  • 在getView里根据当前position是否等于selectedPosition来设置背景和文字颜色

修改后的Adapter代码:

internal class MainAdapter(
    private val context: Context,
    private val AlphabetList: List<String>,
    private val AlphabetsPronunciationList: List<String>,
    // 新增:接收选中位置的引用
    private var selectedPosition: Int
) : BaseAdapter() {
    private var layoutInflater: LayoutInflater? = null
    private lateinit var TextViewTitle: TextView
    private lateinit var TextViewDescription: TextView

    // 新增:提供方法更新选中位置,并通知Adapter刷新
    fun updateSelectedPosition(newPosition: Int) {
        selectedPosition = newPosition
        notifyDataSetChanged()
    }

    override fun getCount(): Int {
        return AlphabetList.size
    }

    override fun getItem(position: Int): Any? {
        return AlphabetList[position]
    }

    override fun getItemId(position: Int): Long {
        return position.toLong()
    }

    override fun getView(
        position: Int,
        convertView: View?,
        parent: ViewGroup
    ): View? {
        var convertView = convertView
        if (layoutInflater == null) {
            layoutInflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
        }
        if (convertView == null) {
            convertView = layoutInflater!!.inflate(R.layout.row_item, null)
        }

        TextViewTitle = convertView!!.findViewById(R.id.TextViewTitle)
        TextViewTitle.text = AlphabetList[position]
        TextViewDescription = convertView!!.findViewById(R.id.TextViewDescription)
        TextViewDescription.text = AlphabetsPronunciationList[position]

        // 核心:根据当前位置是否是选中位置,设置对应的样式
        if (position == selectedPosition) {
            // 选中状态的样式
            convertView.setBackgroundResource(R.drawable.gridview_gradient_item)
            TextViewTitle.setTextColor(context.resources.getColor(R.color.white))
            TextViewDescription.setTextColor(context.resources.getColor(R.color.ArcGreen))
            TextViewDescription.setBackgroundResource(R.drawable.description_background_white)
        } else {
            // 默认未选中的样式
            convertView.setBackgroundResource(R.drawable.gridview_white_item)
            TextViewTitle.setTextColor(context.resources.getColor(R.color.ArcGreen))
            TextViewDescription.setTextColor(context.resources.getColor(R.color.black))
            TextViewDescription.setBackgroundResource(R.drawable.description_background_color)
        }

        TextViewDescription.setOnClickListener {
            (parent as GridView).performItemClick(it, position, 0)
        }

        return convertView
    }
}

3. 修改Activity中的点击事件,移除无效的状态重置逻辑

把之前遍历子View重置状态的代码删掉,换成更新selectedPosition并通知Adapter刷新:

// 在HomeActivity的onCreate里,初始化Adapter的地方:
val mainAdapter = MainAdapter(this@HomeActivity, AlphabetsList, AlphabetsPronunciationList, selectedPosition)
GvAlphabets.adapter = mainAdapter

GvAlphabets.onItemClickListener = OnItemClickListener { parent, view, position, id ->
    try {
        // 更新选中位置
        selectedPosition = position
        // 通知Adapter刷新所有item的状态
        mainAdapter.updateSelectedPosition(selectedPosition)

        // 处理TextViewHeader的显示逻辑(保留你的原有代码)
        val ViewId = view.id
        if (ViewId == R.id.TextViewDescription) {
            TextViewHeader.visibility = View.VISIBLE
            BtnClose.visibility = View.VISIBLE
            val descTextView = view as TextView
            TextViewHeader.text = descTextView.text
        } else {
            TextViewHeader.visibility = View.GONE
            BtnClose.visibility = View.GONE
        }

        // 优化MediaPlayer使用,避免内存泄漏
        var mp: MediaPlayer? = MediaPlayer()
        mp!!.setDataSource(AudioList[position])
        mp!!.prepare()
        mp!!.start()
        // 播放完成后释放资源
        mp!!.setOnCompletionListener {
            it.release()
            mp = null
        }
    } catch (e: Exception) {
        println(e.message)
    }
}

为什么这样修改就解决问题了?

  • 所有item的状态都由Adapter统一管理,不管View是否被复用,getView方法都会根据selectedPosition设置正确的样式,从根源上避免了复用带来的状态混乱。
  • 不再需要遍历可见View重置状态,因为Adapter会为每个item设置正确的状态,包括滚出屏幕后再滚回来的item。
  • 选中位置被持久化记录,滚动时不会丢失选中状态。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.04.28 10:07:30