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

在Kotlin的Fragment中实现可展开列表视图的问题求助

问题描述

在Kotlin开发Android应用时,尝试在Fragment中实现FAQ可展开列表(而非MainActivity),但遇到两个问题:

  1. FAQFragment中初始化FAQAdapter时传入this报错,相同逻辑在MainActivity中可正常运行。
  2. 需要实现**主题(父级)-问题(子级)-答案(孙级)**的三级可展开结构,目前无可行实现思路。

FAQFragment 代码

package com.app

import android.os.Bundle
import android.view.*
import android.content.Context
import androidx.fragment.app.Fragment
import kotlinx.android.synthetic.main.activity_main.*
import kotlinx.android.synthetic.main.fragment_f_a_q.*

class FAQFragment : Fragment() {

    private lateinit var listViewAdapter: FAQAdapter
    private lateinit var questionList : List<String>
    private lateinit var answerList : HashMap<String, List<String>>


    override fun onCreateView(
        inflater: LayoutInflater,
        container: ViewGroup?,
        savedInstanceState: Bundle?
    ): View? {

        showList()

        listViewAdapter = FAQAdapter(this,questionList, answerList)
        FAQList.setAdapter(listViewAdapter)


        return inflater.inflate(R.layout.fragment_f_a_q, container, false)
    }

    private fun showList() {
        questionList = ArrayList()
        answerList = HashMap()

        (questionList as ArrayList<String>).add("Question 1")
        (questionList as ArrayList<String>).add("Question 2")
        (questionList as ArrayList<String>).add("Question 3")
        (questionList as ArrayList<String>).add("Question 4")
        (questionList as ArrayList<String>).add("Question 5")

        val answer1: MutableList<String> = ArrayList()
        answer1.add("Answer 1")
        answer1.add("Answer 2")
        answer1.add("Answer 3")

        val answer2: MutableList<String> = ArrayList()
        answer2.add("Answer 1")
        answer2.add("Answer 2")
        answer2.add("Answer 3")

        val answer3: MutableList<String> = ArrayList()
        answer3.add("Answer 1")
        answer3.add("Answer 2")
        answer3.add("Answer 3")

        val answer4: MutableList<String> = ArrayList()
        answer4.add("Answer 1")
        answer4.add("Answer 2")
        answer4.add("Answer 3")

        val answer5: MutableList<String> = ArrayList()
        answer5.add("Answer 1")
        answer5.add("Answer 2")
        answer5.add("Answer 3")

        answerList[questionList[0]] = answer1
        answerList[questionList[1]] = answer2
        answerList[questionList[2]] = answer3
        answerList[questionList[3]] = answer4
        answerList[questionList[4]] = answer5

    }
}

FAQAdapter 代码

package com.app

import android.content.Context
import android.view.*
import android.widget.*

class FAQAdapter internal constructor(private val context: Context, private val questionList: List<String>, private val answerList: HashMap<String, List<String>>): BaseExpandableListAdapter() {

    override fun getGroupCount(): Int {
        return questionList.size
    }

    override fun getChildrenCount(groupPosition: Int): Int {
        return this.answerList[this.questionList[groupPosition]]!!.size
    }

    override fun getGroup(groupPosition: Int): Any {
        return questionList[groupPosition]
    }

    override fun getChild(groupPosition: Int, childPosition: Int): Any {
        return this.answerList[this.questionList[groupPosition]]!![childPosition]
    }

    override fun getGroupId(groupPosition: Int): Long {
        return groupPosition.toLong()
    }

    override fun getChildId(groupPosition: Int, childPosition: Int): Long {
        return childPosition.toLong()
    }

    override fun hasStableIds(): Boolean {
        return false
    }

    override fun getGroupView(
        groupPosition: Int,
        isExpanded: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {

        var convertView = convertView
        val questionTitle = getGroup(groupPosition) as String
        if(convertView == null){
            val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
            convertView = inflater.inflate(R.layout.faq_questions, null)
        }

        val questionTv = convertView!!.findViewById<TextView>(R.id.question_list_tv)
        questionTv.setText(questionTitle)

        return convertView

    }

    override fun getChildView(
        groupPosition: Int,
        childPosition: Int,
        isLastChild: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {

        var convertView = convertView
        val answerTitle = getChild(groupPosition, childPosition) as String
        if(convertView == null){
            val inflater = context.getSystemService(Context.LAYOUT_INFLATER_SERVICE) as LayoutInflater
            convertView = inflater.inflate(R.layout.faq_answers, null)
        }

        val answerTv = convertView!!.findViewById<TextView>(R.id.question_list_tv)
        answerTv.setText(answerTitle)

        return convertView
    }

    override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean {
        return true
    }
}

解决方案

1. 修复Fragment中传入this报错的问题

问题根源

FAQAdapter构造函数要求Context类型参数,但Fragment本身并非Context子类(Activity才是),因此直接传this会类型不匹配。此外当前代码中onCreateView的执行顺序错误:先初始化Adapter再加载视图,会导致FAQList空指针。

修复代码

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    // 先加载视图,确保控件已初始化
    val view = inflater.inflate(R.layout.fragment_f_a_q, container, false)
    showList()
    // 使用requireContext()获取Fragment关联的Context(非空)
    listViewAdapter = FAQAdapter(requireContext(), questionList, answerList)
    // 从加载的视图中获取ExpandableListView
    val faqList = view.findViewById<ExpandableListView>(R.id.FAQList)
    faqList.setAdapter(listViewAdapter)
    return view
}

额外优化:简化数据初始化

避免强制转换,改用Kotlin集合API直接初始化:

private fun showList() {
    questionList = listOf(
        "Question 1",
        "Question 2",
        "Question 3",
        "Question 4",
        "Question 5"
    )
    answerList = hashMapOf(
        questionList[0] to listOf("Answer 1", "Answer 2", "Answer 3"),
        questionList[1] to listOf("Answer 1", "Answer 2", "Answer 3"),
        questionList[2] to listOf("Answer 1", "Answer 2", "Answer 3"),
        questionList[3] to listOf("Answer 1", "Answer 2", "Answer 3"),
        questionList[4] to listOf("Answer 1", "Answer 2", "Answer 3")
    )
}

2. 实现三级可展开结构(主题-问题-答案)

原生BaseExpandableListAdapter仅支持两级,推荐通过嵌套ExpandableListView实现三级结构,代码实现更直观。

步骤1:定义三级数据类

// 孙级:答案
data class Answer(val content: String)

// 子级:问题(包含多个答案)
data class Question(val title: String, val answers: List<Answer>)

// 父级:主题(包含多个问题)
data class Topic(val title: String, val questions: List<Question>)

步骤2:创建三级布局文件

  • 主题布局(item_topic.xml):
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_topic"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:padding="16dp"
    android:textSize="18sp"
    android:textStyle="bold"/>
  • 问题布局(item_question.xml):嵌套ExpandableListView显示答案
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="vertical">

    <TextView
        android:id="@+id/tv_question"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingHorizontal="16dp"
        android:paddingVertical="12dp"
        android:textSize="16sp"/>

    <ExpandableListView
        android:id="@+id/elv_answers"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:paddingLeft="32dp"/>
</LinearLayout>
  • 答案布局(item_answer.xml):
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
    android:id="@+id/tv_answer"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:paddingHorizontal="16dp"
    android:paddingVertical="8dp"
    android:textSize="14sp"/>

步骤3:实现二级Adapter(答案列表)

class AnswerAdapter(
    private val context: Context,
    private val answers: List<Answer>
) : BaseExpandableListAdapter() {
    override fun getGroupCount(): Int = answers.size
    override fun getChildrenCount(groupPosition: Int): Int = 0
    override fun getGroup(groupPosition: Int): Any = answers[groupPosition]
    override fun getChild(groupPosition: Int, childPosition: Int): Any = ""
    override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()
    override fun getChildId(groupPosition: Int, childPosition: Int): Long = 0
    override fun hasStableIds(): Boolean = false

    override fun getGroupView(
        groupPosition: Int,
        isExpanded: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {
        val view = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.item_answer, parent, false)
        view.findViewById<TextView>(R.id.tv_answer).text = answers[groupPosition].content
        return view
    }

    override fun getChildView(
        groupPosition: Int,
        childPosition: Int,
        isLastChild: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View = View(context)

    override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = false
}

步骤4:实现一级Adapter(主题-问题列表)

class QuestionAdapter(
    private val context: Context,
    private val topics: List<Topic>
) : BaseExpandableListAdapter() {
    override fun getGroupCount(): Int = topics.size
    override fun getChildrenCount(groupPosition: Int): Int = topics[groupPosition].questions.size
    override fun getGroup(groupPosition: Int): Any = topics[groupPosition]
    override fun getChild(groupPosition: Int, childPosition: Int): Any = topics[groupPosition].questions[childPosition]
    override fun getGroupId(groupPosition: Int): Long = groupPosition.toLong()
    override fun getChildId(groupPosition: Int, childPosition: Int): Long = childPosition.toLong()
    override fun hasStableIds(): Boolean = false

    override fun getGroupView(
        groupPosition: Int,
        isExpanded: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {
        val view = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.item_topic, parent, false)
        view.findViewById<TextView>(R.id.tv_topic).text = topics[groupPosition].title
        return view
    }

    override fun getChildView(
        groupPosition: Int,
        childPosition: Int,
        isLastChild: Boolean,
        convertView: View?,
        parent: ViewGroup?
    ): View {
        val view = convertView ?: LayoutInflater.from(context)
            .inflate(R.layout.item_question, parent, false)
        val question = topics[groupPosition].questions[childPosition]
        view.findViewById<TextView>(R.id.tv_question).text = question.title
        
        // 初始化答案列表的Adapter
        val elvAnswers = view.findViewById<ExpandableListView>(R.id.elv_answers)
        elvAnswers.setAdapter(AnswerAdapter(context, question.answers))
        
        // 解决嵌套列表高度截断问题
        val elvParams = elvAnswers.layoutParams
        elvAnswers.setOnGroupExpandListener {
            elvParams.height = elvAnswers.getChildAt(it).height * question.answers.size
            elvAnswers.layoutParams = elvParams
        }
        elvAnswers.setOnGroupCollapseListener {
            elvParams.height = ViewGroup.LayoutParams.WRAP_CONTENT
            elvAnswers.layoutParams = elvParams
        }
        return view
    }

    override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean = true
}

步骤5:在Fragment中初始化三级数据

private fun initTopicData(): List<Topic> {
    val answerGroup1 = listOf(Answer("答案1-1"), Answer("答案1-2"))
    val questionGroup1 = listOf(Question("问题1", answerGroup1), Question("问题2", answerGroup1))
    val topic1 = Topic("主题1", questionGroup1)

    val answerGroup2 = listOf(Answer("答案2-1"), Answer("答案2-2"), Answer("答案2-3"))
    val questionGroup2 = listOf(Question("问题3", answerGroup2), Question("问题4", answerGroup2))
    val topic2 = Topic("主题2", questionGroup2)

    return listOf(topic1, topic2)
}

override fun onCreateView(
    inflater: LayoutInflater,
    container: ViewGroup?,
    savedInstanceState: Bundle?
): View? {
    val view = inflater.inflate(R.layout.fragment_f_a_q, container, false)
    val elvTopics = view.findViewById<ExpandableListView>(R.id.elv_topics)
    elvTopics.setAdapter(QuestionAdapter(requireContext(), initTopicData()))
    return view
}

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 22:27:19