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

Kotlin迁移后Android Spinner点击无下拉展示 无报错无崩溃

Kotlin迁移后Spinner下拉列表不展示问题修复

问题现象

  • 应用从Java迁移至Kotlin后,原Java环境运行完全正常的Spinner控件,点击分享图标触发时下拉列表完全不展示
  • 触发过程无异常抛出、无应用崩溃,Logcat无任何相关错误日志

相关实现代码

1. 自定义Spinner适配器CustomShareSpinnerAdapter

class CustomShareSpinnerAdapter(
    applicationContext: Context?,
    private val shareOptions: Array<String>
) : BaseAdapter() {
    private val inflter: LayoutInflater
    override fun getCount(): Int {
        return shareOptions.size
    }

    override fun getItem(i: Int): Any? {
        return null
    }

    override fun getItemId(i: Int): Long {
        return 0
    }

    @SuppressLint("InflateParams")
    override fun getDropDownView(position: Int, _convertView: View?, parent: ViewGroup): View {
        var convertView = _convertView
        if (convertView == null) {
            val vi =
                parent.context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
            convertView = vi.inflate(R.layout.share_spinner_row, null)
        }
        val llLayout = super.getDropDownView(position, convertView, parent)
        if (parent.parent!=null)
            roundCorners(parent)
        return llLayout
    }

    private fun roundCorners(parent: ViewGroup) {
        val container2Round = parent.parent as View
        val gd = GradientDrawable(
            GradientDrawable.Orientation.TOP_BOTTOM, intArrayOf(Color.WHITE, Color.WHITE)
        )
        val radius = AWScreen.dp2px(10)
        gd.cornerRadius = radius.toFloat()
        container2Round.background = gd
    }

    override fun getView(i: Int, _view: View?, viewGroup: ViewGroup): View {

        val view = _view ?:
        inflter.inflate(R.layout.share_spinner_row, viewGroup, false)

        val tvShare = view.findViewById<TextView>(R.id.share)
        tvShare.text = shareOptions[i]
        if (shareOptions[i] == "") tvShare.visibility = View.GONE
        setListItemRoundedCorners(tvShare, i)
        if (i == shareOptions.size - 1) {
            val padding = AWScreen.sp2px(10f, view.context)
            tvShare.setPadding(padding, padding, padding, padding)
        }
        return view
    }

    private fun setListItemRoundedCorners(tvShare: TextView, i: Int) {
        if (i == 1) {
            tvShare.setBackgroundResource(R.drawable.rounded_border_top)
        } else {
            if (i == 2) {
                tvShare.setBackgroundResource(R.drawable.rounded_border_bottom)
            }
        }
    }

    init {
        inflter = LayoutInflater.from(applicationContext)
    }
}

2. Spinner子项布局share_spinner_row.xml

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
              android:id="@+id/shareLayout"
              android:layout_width="fill_parent"
              android:layout_height="wrap_content"
              android:orientation="horizontal">

    <TextView
        android:id="@+id/share"
        android:textSize="20sp"
        android:textColor="#000"
        android:paddingTop="10dp"
        android:paddingLeft="10dp"
        android:paddingRight="10dp"
        android:paddingBottom="10dp"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"/>
</LinearLayout>

3. Spinner初始化方法setShareSpinner

private fun setShareSpinner() {
    val shareOptions = arrayOf(
        "",
        getStringResourceByName("displaythoughtactivity_sharespinner_text0"),
        getStringResourceByName("displaythoughtactivity_sharespinner_text1"),
        getStringResourceByName("displaythoughtactivity_sharespinner_text2")
    )
    val customAdapter = CustomShareSpinnerAdapter(this, shareOptions)
    binding!!.shareSpinner.adapter = customAdapter
    binding!!.shareSpinner.onItemSelectedListener = this
}

4. Spinner选中回调onItemSelected实现

override fun onItemSelected(arg0: AdapterView<*>, arg1: View, position: Int, id: Long) {
    val shareSpinnerId = R.id.shareSpinner
    val saveSpinnerId = R.id.saveSpinner
    when (arg0.id) {
        shareSpinnerId -> {
            onShareSpinnerItemSelected(position)
            binding!!.shareSpinner.setSelection(0)
        }
        saveSpinnerId -> {
            onSaveSpinnerItemSelected(position)
            binding!!.saveSpinner.setSelection(0)
        }
        else -> loadAnimSpinnerItem(position)
    }
}

问题根因

  1. getDropDownView实现逻辑错误:自行inflate视图后调用super.getDropDownView(),BaseAdapter默认实现没有有效视图返回逻辑,会覆盖正确渲染的下拉项,导致下拉视图测量尺寸为0,无法弹出。
  2. 适配器基础方法实现不符合规范:getItem()固定返回null、getItemId()固定返回0,Spinner内部计算弹窗位置、校验数据有效性时拿不到正确参数,会中断下拉流程。
  3. 圆角处理逻辑无执行控制:每次渲染下拉子项都会重复修改下拉父容器背景,打断Spinner的弹窗布局、测量流程。
  4. 选中回调逻辑冲突:选中有效项后立即在主线程同步调用setSelection(0)回到空占位项,会直接关闭刚触发弹出的下拉列表。

生命周期疑问解答

  • 初始化阶段getView调用2次为系统正常行为:Spinner绑定适配器后,会第一次调用getView测量默认选中项的尺寸用于布局闭合状态的Spinner控件,第二次调用getView将选中项视图实际绘制到界面上,不属于异常。
  • 点击时getDropDownView调用20次左右为系统正常调度:Spinner下拉弹窗展示过程中,框架会多次调用该方法完成视图复用、尺寸预测量、动画参数计算、滚动边界校验,4个选项调用十到二十次均为正常现象,不是代码错误。

修复方案

  1. 修正getDropDownView实现,删除无效的super调用,增加圆角处理的执行次数控制,保证下拉项视图正确渲染:
// 增加标记位控制圆角只设置一次
private var hasSetDropDownCorner = false

@SuppressLint("InflateParams")
override fun getDropDownView(position: Int, _convertView: View?, parent: ViewGroup): View {
    val convertView = _convertView ?: LayoutInflater.from(parent.context).inflate(R.layout.share_spinner_row, null)
    val tvShare = convertView.findViewById<TextView>(R.id.share)
    tvShare.text = shareOptions[position]
    if (shareOptions[position].isEmpty()) {
        tvShare.visibility = View.GONE
    } else {
        tvShare.visibility = View.VISIBLE
        setListItemRoundedCorners(tvShare, position)
    }
    if (!hasSetDropDownCorner && parent.parent != null) {
        roundCorners(parent)
        hasSetDropDownCorner = true
    }
    return convertView
}
  1. 修正适配器基础方法,返回正确的数据项和id:
override fun getItem(i: Int): Any = shareOptions[i]

override fun getItemId(i: Int): Long = i.toLong()
  1. 修正选中回调逻辑,过滤空占位项的选中事件,异步重置选中位置避免打断下拉流程:
override fun onItemSelected(arg0: AdapterView<*>, arg1: View, position: Int, id: Long) {
    val shareSpinnerId = R.id.shareSpinner
    val saveSpinnerId = R.id.saveSpinner
    when (arg0.id) {
        shareSpinnerId -> {
            // 首项为空占位,不执行业务逻辑
            if (position == 0) return
            onShareSpinnerItemSelected(position)
            // 等下拉逻辑执行完成后再重置选中项
            binding!!.shareSpinner.post { binding!!.shareSpinner.setSelection(0) }
        }
        saveSpinnerId -> {
            if (position == 0) return
            onSaveSpinnerItemSelected(position)
            binding!!.saveSpinner.post { binding!!.saveSpinner.setSelection(0) }
        }
        else -> loadAnimSpinnerItem(position)
    }
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.29 09:12:19