在Kotlin的Fragment中实现可展开列表视图的问题求助
问题描述
在Kotlin开发Android应用时,尝试在Fragment中实现FAQ可展开列表(而非MainActivity),但遇到两个问题:
- FAQFragment中初始化
FAQAdapter时传入this报错,相同逻辑在MainActivity中可正常运行。 - 需要实现**主题(父级)-问题(子级)-答案(孙级)**的三级可展开结构,目前无可行实现思路。
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
相关产品推荐
相关产品推荐

