使用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
相关产品推荐
相关产品推荐

