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

基于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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.13 19:16:00