基于Kotlin的问卷模块开发:多题型处理与答案提取方案问询
问卷模块解决方案
一、动态视图答案提取方案
1. 统一答案提取接口
定义一个通用接口,让所有题型视图实现该接口,统一答案获取逻辑,避免类型判断混乱:
interface QuestionAnswerProvider { fun getQuestionId(): String // 返回对应问题ID fun getAnswer(): Any? // 根据题型返回对应类型:文本/String、单选/Int、多选/List<Int>、绘图/Bitmap等 }
改造现有视图创建逻辑,让每个题型视图通过tag挂载该接口实现:
开放式问题改造:
private fun inflateTextQuestion( inflater: LayoutInflater, parent: ViewGroup, question: EvaluationObject, ): View { val view = inflater.inflate(R.layout.item_text_evaluation, parent, false) val answerEditText = view.findViewById<EditText>(R.id.answerEditText) answerEditText.hint = question.evaluationQuestion view.tag = object : QuestionAnswerProvider { override fun getQuestionId(): String = question.id override fun getAnswer(): Any? = answerEditText.text.toString().takeIf { it.isNotEmpty() } } return view }
选择题改造:
private fun inflateMultipleChoiceQuestion( inflater: LayoutInflater, parent: ViewGroup, question: EvaluationObject, options: List<EvaluationValue> ): View { val view = inflater.inflate(R.layout.item_evaluation_select, parent, false) val questionsRecyclerView = view.findViewById<RecyclerView>(R.id.select_evaluation_recycler) val optionsString = options.map { it.value } val adapter = CardChoiceAdapter( items = optionsString, multipleChoice = false ) { selectedItems -> } questionsRecyclerView.layoutManager = LinearLayoutManager(parent.context) questionsRecyclerView.adapter = adapter view.tag = object : QuestionAnswerProvider { override fun getQuestionId(): String = question.id override fun getAnswer(): Any? = adapter.getSelectedItems().firstOrNull() } return view }
2. 批量提取并同步到ViewModel
在提交/保存操作时,遍历容器内所有子View,通过接口统一收集答案:
fun collectAndSaveAnswers() { val answerMap = mutableMapOf<String, Any?>() dynamic_evaluation_container.forEach { childView -> (childView.tag as? QuestionAnswerProvider)?.let { provider -> answerMap[provider.getQuestionId()] = provider.getAnswer() } } viewModel.updateAllAnswers(answerMap) }
3. 实时同步优化(可选)
如果需要实时保存用户输入,可在视图创建时直接绑定ViewModel:
- 开放式问题添加文本监听:
answerEditText.addTextChangedListener(object : TextWatcher { override fun afterTextChanged(s: Editable?) { viewModel.updateSingleAnswer(question.id, s.toString().takeIf { it.isNotEmpty() }) } override fun beforeTextChanged(s: CharSequence?, start: Int, count: Int, after: Int) {} override fun onTextChanged(s: CharSequence?, start: Int, before: Int, count: Int) {} })
- 选择题在选中回调中直接更新:
val adapter = CardChoiceAdapter( items = optionsString, multipleChoice = false ) { selectedItems -> viewModel.updateSingleAnswer(question.id, selectedItems.firstOrNull()) }
二、多题型布局优化方案
1. 先对问题数据分组
按evaluationScreen和evaluationOrder分层分组,保证布局逻辑清晰:
// 假设questions为服务器返回的问题列表 val screenGroups = questions.groupBy { it.evaluationScreen } screenGroups.forEach { (_, screenQuestions) -> // 按order分组并排序,保证布局顺序正确 val sortedOrderGroups = screenQuestions.groupBy { it.evaluationOrder } .toSortedMap() buildLayoutForScreen(sortedOrderGroups) }
2. 动态构建嵌套布局
根据每组问题数量决定横向/纵向布局:
private fun buildLayoutForScreen(orderGroups: SortedMap<Int, List<EvaluationObject>>) { orderGroups.forEach { (_, questions) -> if (questions.size == 1) { // 单个问题直接添加到垂直容器 val questionView = inflateQuestionByType(questions.first()) dynamic_evaluation_container.addView(questionView) } else { // 创建横向布局承载多个同order问题 val horizontalLayout = LinearLayout(requireContext()).apply { layoutParams = LinearLayout.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT ).apply { orientation = LinearLayout.HORIZONTAL setPadding(0, 16, 0, 16) } } questions.forEach { question -> val questionView = inflateQuestionByType(question) questionView.layoutParams = LinearLayout.LayoutParams( 0, ViewGroup.LayoutParams.WRAP_CONTENT, 1f // 平分横向宽度 ) horizontalLayout.addView(questionView) } dynamic_evaluation_container.addView(horizontalLayout) } } } // 封装的题型视图创建入口 private fun inflateQuestionByType(question: EvaluationObject): View { // 调用QuestionViewFactory对应方法创建视图 return when(question.type) { TEXT -> inflateTextQuestion(...) CHOICE -> inflateMultipleChoiceQuestion(...) // 其他题型分支 else -> throw IllegalArgumentException("Unsupported question type") } }
三、替代实现思路
1. 用RecyclerView统一管理
将整个问卷作为RecyclerView列表,不同题型对应不同ViewHolder:
- 定义
BaseQuestionViewHolder基类,每个题型实现子类(如TextQuestionVH、ChoiceQuestionVH) - Adapter根据题型类型创建对应ViewHolder,ViewHolder内部实现答案提取逻辑
- 同order的问题可作为复合Item处理,或通过Decoration实现分组样式
这种方式更符合Android列表性能优化逻辑,避免大量动态View导致的卡顿。
2. Data Binding双向绑定
通过Data Binding将视图直接绑定ViewModel数据,用户输入实时同步:
- 开放式问题EditText绑定:
@={viewModel.answers[question.id]} - 选择题Adapter绑定选中状态到ViewModel的对应字段
这种方式消除手动提取答案的逻辑,降低视图与ViewModel的耦合。
内容的提问来源于stack exchange,提问作者Klutz
相关产品推荐
相关产品推荐

