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) } }
问题根因
getDropDownView实现逻辑错误:自行inflate视图后调用super.getDropDownView(),BaseAdapter默认实现没有有效视图返回逻辑,会覆盖正确渲染的下拉项,导致下拉视图测量尺寸为0,无法弹出。- 适配器基础方法实现不符合规范:
getItem()固定返回null、getItemId()固定返回0,Spinner内部计算弹窗位置、校验数据有效性时拿不到正确参数,会中断下拉流程。 - 圆角处理逻辑无执行控制:每次渲染下拉子项都会重复修改下拉父容器背景,打断Spinner的弹窗布局、测量流程。
- 选中回调逻辑冲突:选中有效项后立即在主线程同步调用
setSelection(0)回到空占位项,会直接关闭刚触发弹出的下拉列表。
生命周期疑问解答
- 初始化阶段
getView调用2次为系统正常行为:Spinner绑定适配器后,会第一次调用getView测量默认选中项的尺寸用于布局闭合状态的Spinner控件,第二次调用getView将选中项视图实际绘制到界面上,不属于异常。 - 点击时
getDropDownView调用20次左右为系统正常调度:Spinner下拉弹窗展示过程中,框架会多次调用该方法完成视图复用、尺寸预测量、动画参数计算、滚动边界校验,4个选项调用十到二十次均为正常现象,不是代码错误。
修复方案
- 修正
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 }
- 修正适配器基础方法,返回正确的数据项和id:
override fun getItem(i: Int): Any = shareOptions[i] override fun getItemId(i: Int): Long = i.toLong()
- 修正选中回调逻辑,过滤空占位项的选中事件,异步重置选中位置避免打断下拉流程:
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
相关产品推荐
相关产品推荐

