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

Android课程项目:寻求替换嵌套RecyclerView的通用问卷实现方案

优化问卷RecyclerView方案:单RecyclerView + 多类型Item

嘿,我完全懂你嵌套RecyclerView做问卷的那种怪异感——之前做类似项目时也踩过这个坑,滚动时偶尔会有小卡顿,而且代码结构也显得冗余。给你推荐一个更通用、更优雅的方案:单RecyclerView + 多类型Item,完美适配不同问题类型和可变选项的需求,还能避开嵌套的各种问题。

核心思路

把每个不同类型的问题(单选、多选、填空等)看作RecyclerView的一种Item类型,每个Item内部直接承载对应的选项布局,不需要嵌套子RecyclerView。这样既保持了通用性,又能让代码逻辑更清晰。

步骤1:定义统一的问题数据模型

先创建一个问题基类,再为每种问题类型实现子类,子类中包含该类型特有的数据(比如选项列表、选中状态等):

// 所有问题的基类
abstract class Question(val id: String, val title: String)

// 单选问题子类
class SingleChoiceQuestion(
    id: String,
    title: String,
    val options: List<String>,
    var selectedOption: String? = null
) : Question(id, title)

// 多选问题子类
class MultipleChoiceQuestion(
    id: String,
    title: String,
    val options: List<String>,
    val selectedOptions: MutableList<String> = mutableListOf()
) : Question(id, title)

// 填空问题子类
class TextInputQuestion(
    id: String,
    title: String,
    var inputText: String = ""
) : Question(id, title)

步骤2:实现多类型Adapter

在RecyclerView Adapter中,通过getItemViewType()区分不同问题类型,为每种类型创建对应的ViewHolder:

class QuestionnaireAdapter(private val questions: List<Question>) : RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    companion object {
        // 定义不同问题类型对应的ViewType
        private const val VIEW_TYPE_SINGLE_CHOICE = 0
        private const val VIEW_TYPE_MULTIPLE_CHOICE = 1
        private const val VIEW_TYPE_TEXT_INPUT = 2
    }

    override fun getItemViewType(position: Int): Int {
        return when (questions[position]) {
            is SingleChoiceQuestion -> VIEW_TYPE_SINGLE_CHOICE
            is MultipleChoiceQuestion -> VIEW_TYPE_MULTIPLE_CHOICE
            is TextInputQuestion -> VIEW_TYPE_TEXT_INPUT
            else -> throw IllegalArgumentException("未知的问题类型")
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_SINGLE_CHOICE -> {
                val view = inflater.inflate(R.layout.item_single_choice, parent, false)
                SingleChoiceViewHolder(view)
            }
            VIEW_TYPE_MULTIPLE_CHOICE -> {
                val view = inflater.inflate(R.layout.item_multiple_choice, parent, false)
                MultipleChoiceViewHolder(view)
            }
            VIEW_TYPE_TEXT_INPUT -> {
                val view = inflater.inflate(R.layout.item_text_input, parent, false)
                TextInputViewHolder(view)
            }
            else -> throw IllegalArgumentException("未知的ViewType")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val question = questions[position]
        when (holder) {
            is SingleChoiceViewHolder -> holder.bind(question as SingleChoiceQuestion)
            is MultipleChoiceViewHolder -> holder.bind(question as MultipleChoiceQuestion)
            is TextInputViewHolder -> holder.bind(question as TextInputQuestion)
        }
    }

    override fun getItemCount() = questions.size

    // 单选问题的ViewHolder,负责绑定数据和交互
    class SingleChoiceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvTitle: TextView = itemView.findViewById(R.id.tv_question_title)
        private val rgOptions: RadioGroup = itemView.findViewById(R.id.rg_options)

        fun bind(question: SingleChoiceQuestion) {
            tvTitle.text = question.title
            // 清空之前的选项,避免复用问题
            rgOptions.removeAllViews()
            
            question.options.forEach { option ->
                val radioBtn = RadioButton(itemView.context).apply {
                    text = option
                    isChecked = option == question.selectedOption
                    setOnCheckedChangeListener { _, isChecked ->
                        if (isChecked) question.selectedOption = option
                    }
                }
                rgOptions.addView(radioBtn)
            }
        }
    }

    // 多选问题的ViewHolder
    class MultipleChoiceViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvTitle: TextView = itemView.findViewById(R.id.tv_question_title)
        private val llOptions: LinearLayout = itemView.findViewById(R.id.ll_options_container)

        fun bind(question: MultipleChoiceQuestion) {
            tvTitle.text = question.title
            llOptions.removeAllViews()
            
            question.options.forEach { option ->
                val checkBox = CheckBox(itemView.context).apply {
                    text = option
                    isChecked = question.selectedOptions.contains(option)
                    setOnCheckedChangeListener { _, isChecked ->
                        if (isChecked) question.selectedOptions.add(option)
                        else question.selectedOptions.remove(option)
                    }
                }
                llOptions.addView(checkBox)
            }
        }
    }

    // 填空问题的ViewHolder
    class TextInputViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) {
        private val tvTitle: TextView = itemView.findViewById(R.id.tv_question_title)
        private val etAnswer: EditText = itemView.findViewById(R.id.et_answer_input)

        fun bind(question: TextInputQuestion) {
            tvTitle.text = question.title
            etAnswer.setText(question.inputText)
            
            etAnswer.addTextChangedListener(object : TextWatcher {
                override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {}
                override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {}
                override fun afterTextChanged(s: Editable?) {
                    question.inputText = s.toString()
                }
            })
        }
    }
}

步骤3:创建对应布局文件

为每种问题类型创建简单的布局,比如:

  • item_single_choice.xml:包含一个TextView(标题)和RadioGroup(选项容器)
  • item_multiple_choice.xml:包含TextView和LinearLayout(选项容器)
  • item_text_input.xml:包含TextView和EditText

布局可以根据你的UI需求自定义样式,保持统一即可。

方案优势

  • 避免嵌套问题:没有子RecyclerView的滚动冲突、重复测量等性能问题
  • 高通用性:新增问题类型只需要添加对应的子类、ViewHolder和布局,无需修改核心逻辑
  • 逻辑清晰:每种问题的UI和数据绑定都封装在各自的ViewHolder中,代码模块化
  • 易维护:后续修改某类问题的交互或样式,只需要调整对应的ViewHolder和布局

额外优化建议

  • 如果某类问题的选项数量特别多,可以考虑在ViewHolder内部使用小型RecyclerView承载选项,但一般问卷选项不会过多,线性布局足够
  • 可以把选项的View创建逻辑封装成工具方法,避免ViewHolder代码冗余
  • 为所有问题布局添加统一的间距、标题样式,保证UI一致性

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 11:32:49