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

使用RecyclerView实现多题型调查问卷的正确方法是什么?

正确使用RecyclerView + ViewHolder实现多题型调查问卷的方案

Great call going with RecyclerView over ListView here—its flexibility with multiple view types is perfect for your survey with different question formats like Integer-Answer-Question and Long-Text-Answer-Question. Let’s break down the step-by-step implementation to make this work smoothly:

1. 先设计统一的问题数据模型

首先你需要一个基类/接口来统一所有题型,让Adapter能无缝处理不同的问题类型:

// 以Kotlin为例,Java实现思路完全一致
sealed class SurveyQuestion {
    data class IntegerQuestion(
        val id: String,
        val title: String,
        var userAnswer: Int? = null // 保存用户输入的整数答案
    ) : SurveyQuestion()

    data class LongTextQuestion(
        val id: String,
        val title: String,
        var userAnswer: String? = null // 保存用户输入的长文本答案
    ) : SurveyQuestion()

    // 后续要加单选、多选等题型?直接新增子类即可
}

每个题型都继承自SurveyQuestion,各自维护自己的答题数据,后续处理用户输入、提交答案都会很方便。

2. 创建对应题型的布局文件

为每种题型单独编写布局,保证UI和题型匹配:

  • item_integer_question.xml:包含TextView(展示题目)+ EditText(限制整数输入,设置inputType="number")
  • item_long_text_question.xml:包含TextView(展示题目)+ EditText(支持多行输入,设置inputType="textMultiLine")

3. 实现多类型的RecyclerView.Adapter和ViewHolder

核心是通过getItemViewType()区分不同题型,再在onCreateViewHolder()中创建对应的ViewHolder,代码示例如下:

class SurveyAdapter(private val questions: MutableList<SurveyQuestion>) :
    RecyclerView.Adapter<RecyclerView.ViewHolder>() {

    // 定义视图类型常量,方便后续判断
    companion object {
        private const val VIEW_TYPE_INTEGER = 0
        private const val VIEW_TYPE_LONG_TEXT = 1
    }

    override fun getItemViewType(position: Int): Int {
        return when (questions[position]) {
            is SurveyQuestion.IntegerQuestion -> VIEW_TYPE_INTEGER
            is SurveyQuestion.LongTextQuestion -> VIEW_TYPE_LONG_TEXT
            else -> throw IllegalArgumentException("Unknown question type")
        }
    }

    override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): RecyclerView.ViewHolder {
        val inflater = LayoutInflater.from(parent.context)
        return when (viewType) {
            VIEW_TYPE_INTEGER -> {
                val view = inflater.inflate(R.layout.item_integer_question, parent, false)
                IntegerQuestionViewHolder(view)
            }
            VIEW_TYPE_LONG_TEXT -> {
                val view = inflater.inflate(R.layout.item_long_text_question, parent, false)
                LongTextQuestionViewHolder(view)
            }
            else -> throw IllegalArgumentException("Unknown view type")
        }
    }

    override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
        val question = questions[position]
        // 匹配对应的ViewHolder绑定数据
        when (holder) {
            is IntegerQuestionViewHolder -> holder.bind(question as SurveyQuestion.IntegerQuestion)
            is LongTextQuestionViewHolder -> holder.bind(question as SurveyQuestion.LongTextQuestion)
        }
    }

    override fun getItemCount() = questions.size

    // 整数题型的ViewHolder,处理输入逻辑
    inner class IntegerQuestionViewHolder(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_integer_answer)

        fun bind(question: SurveyQuestion.IntegerQuestion) {
            tvTitle.text = question.title
            // 回显已输入的答案
            etAnswer.setText(question.userAnswer?.toString() ?: "")
            
            // 实时监听输入,更新数据模型(避免滚动后答案丢失)
            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.userAnswer = s?.toString()?.toIntOrNull()
                }
            })
        }
    }

    // 长文本题型的ViewHolder
    inner class LongTextQuestionViewHolder(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_long_text_answer)

        fun bind(question: SurveyQuestion.LongTextQuestion) {
            tvTitle.text = question.title
            etAnswer.setText(question.userAnswer ?: "")
            
            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.userAnswer = s?.toString()
                }
            })
        }
    }
}

4. 初始化RecyclerView并设置Adapter

在你的Activity/Fragment中完成初始化:

val recyclerView = findViewById<RecyclerView>(R.id.rv_survey)
// 垂直滚动列表用LinearLayoutManager即可
recyclerView.layoutManager = LinearLayoutManager(this)
// 初始化示例问题列表
val surveyQuestions = mutableListOf<SurveyQuestion>(
    SurveyQuestion.IntegerQuestion("1", "请输入你的年龄:"),
    SurveyQuestion.LongTextQuestion("2", "请描述你的使用体验:")
)
val adapter = SurveyAdapter(surveyQuestions)
recyclerView.adapter = adapter
// 可选:添加列表分割线,提升可读性
recyclerView.addItemDecoration(DividerItemDecoration(this, DividerItemDecoration.VERTICAL))

5. 关键优化和注意事项

  • 避免答案丢失:一定要把用户输入实时更新到数据模型中(就像代码里的TextWatcher逻辑),因为RecyclerView会复用ViewHolder,不绑定数据的话滚动后输入内容会消失
  • 软键盘适配:给Activity在AndroidManifest.xml中设置android:windowSoftInputMode="adjustResize",避免输入时遮挡题目
  • 扩展便捷性:后续新增题型时,只需要添加SurveyQuestion子类、对应布局,再在Adapter中补充视图类型判断和ViewHolder即可,扩展性拉满
  • 内存泄漏防范:如果ViewHolder中使用了匿名内部类(比如TextWatcher),在复杂场景下可以考虑在onViewRecycled()中移除监听,避免潜在泄漏

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.20 06:54:53