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

Android Kotlin动态填充ExpandableListView问题及图片添加咨询

问题描述

我正在学习Android的Kotlin/UI设计基础,尝试从数据库动态填充ExpandableListView。目前在displayInventory函数中调用elv.addView(txtView)时出现addView(View) is not supported in AdapterView错误。通过相关帖子了解到不能直接向ListView添加项,需向Adapter添加数据再设置到ListView,但仍难以将该概念转化为Kotlin代码实现。

疑问

  1. 是否可以通过我尝试的这种函数方式动态填充ExpandableListView?
  2. 如果后续要给ExpandableListView添加图片,是否容易实现?

附:相关代码

CustomExpandableListAdapter类

class CustomExpandableListAdapter internal constructor(
    private val context: Context,
    private val titleList: List<String>,
    private val dataList: HashMap<String, List<Inventory>>
) : BaseExpandableListAdapter() {

private val inflater: LayoutInflater = LayoutInflater.from(context)
private lateinit var groupBinding: ListGroupBinding
private lateinit var itemBinding: ListItemBinding

override fun getChild(listPosition: Int, expandedListPosition: Int): Any {
    return this.dataList[this.titleList[listPosition]]!![expandedListPosition]
}

override fun getChildId(listPosition: Int, expandedListPosition: Int): Long {
    return expandedListPosition.toLong()
}

override fun getChildView(
    listPosition: Int,
    expandedListPosition: Int,
    isLastChild: Boolean,
    view: View?,
    parent: ViewGroup
): View {
    var convertView = view
    val holder: ItemViewHolder
    if (convertView == null) {
        itemBinding = ListItemBinding.inflate(inflater)
        convertView = itemBinding.root
        holder = ItemViewHolder()
        holder.label = itemBinding.expandedListItem
        convertView.tag = holder
    } else {
        holder = convertView.tag as ItemViewHolder
    }
    val expandedListText = getChild(listPosition, expandedListPosition) as String
    holder.label!!.text = expandedListText
    return convertView
}

override fun getChildrenCount(listPosition: Int): Int {
    return this.dataList[this.titleList[listPosition]]!!.size
}

override fun getGroup(listPosition: Int): Any {
    return this.titleList[listPosition]
}

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

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

override fun getGroupView(
    listPosition: Int,
    isExpanded: Boolean,
    view: View?,
    parent: ViewGroup
): View {
    var convertView = view
    val holder: GroupViewHolder
    if (convertView == null) {
        groupBinding = ListGroupBinding.inflate(inflater)
        convertView = groupBinding.root
        holder = GroupViewHolder()
        holder.label = groupBinding.listTitle
        convertView.tag = holder
    } else {
        holder = convertView.tag as GroupViewHolder
    }
    val listTitle = getGroup(listPosition) as String
    holder.label!!.text = listTitle
    return convertView
}

override fun hasStableIds(): Boolean {
    return false
}

override fun isChildSelectable(listPosition: Int, expandedListPosition: Int): Boolean {
    return true
}

inner class ItemViewHolder {
    internal var label: TextView? = null
}

inner class GroupViewHolder {
    internal var label: TextView? = null
}
}

ProfileActivity代码

class ProfileActivity : AppCompatActivity() {

private var adapter: ExpandableListAdapter? = null
private lateinit var binding: ActivityProfileBinding

val database = Firebase.database
val ref = database.getReference("Users")
val mainRef = ref.child(Firebase.auth.currentUser!!.uid).child("Inventory")
val userInventory : MutableList<Inventory> = ArrayList()
var listData = HashMap<String, List<Inventory>>()


//check hashmap for functionality and next steps to populate expandable list view
override fun onCreate(savedInstanceState: Bundle?) {

    super.onCreate(savedInstanceState)
    binding = ActivityProfileBinding.inflate(layoutInflater)
    val view = binding.root
    setupExpandableListView()
    setContentView(view)
    val layout : ConstraintLayout = findViewById(R.id.root)

    //profile pic header
    val profilePic : ImageView = findViewById(R.id.profile_pic)
    profilePic.setImageResource(R.drawable.bermanologylogo)

    //Nav Bar items
    var bottomNavView: BottomNavigationView = findViewById(R.id.bottom_nav)
    bottomNavView.selectedItemId = R.id.profile
    bottomNavView.setOnNavigationItemSelectedListener { rock ->
        when (rock.itemId) {
            R.id.profile -> {
                val intent =
                    Intent(this, ProfileActivity::class.java)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                startActivity(intent)
                finish()
            }
            R.id.feed -> {
                val intent =
                    Intent(this, SearchActivity::class.java)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                startActivity(intent)
                finish()

            }
            R.id.add_entry -> {
                val intent =
                    Intent(this, MainActivity::class.java)
                intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
                startActivity(intent)
                finish()
            }
        }
        true
    }


    //coroutines to get database contents and display inventory
    GlobalScope.launch {
        var scrollScreen : NestedScrollView = findViewById(R.id.scrolly)

        withContext(Dispatchers.IO) {
            getDB()
            listData = sortElements(userInventory) as HashMap<String, List<Inventory>>
            print(userInventory.toString())
            for ((key, value) in listData) {
                println("${key.toString()} = ${value.toString()}")
            }
        }
        withContext(Dispatchers.Main){
            displayInventory()
        }
    }

    profilePic.setOnClickListener {

        }


    }


//create and display textviews of current inventory
private fun displayInventory(){

    var elv : ExpandableListView = findViewById(R.id.elv)

    userInventory.forEach { element ->
        var uId = element.Uid.toString()
        var name = element.name.toString()
        var purchDate = element.purchDate.toString()
        var country = element.country.toString()
        var local = element.local.toString()
        var mine = element.mine.toString()
        var weight = element.weight.toString()
        var paid = element.paid.toString()
        var asking = element.asking.toString()
        var description = element.description.toString()
        var dimensions = element.dimensions.toString()
        var filePaths: ArrayList<String> = element.filePaths
        var downloadUrls: ArrayList<String> = element.downloadUrls

        val txtView = TextView(this)
        txtView.text = "Uid: $name\n" +
                "purchase date: $purchDate\n" +
                "country: $country\n" +
                "local: $local\n" +
                "mine: $mine\n" +
                "weight: $weight\n" +
                "paid: $paid\n" +
                "asking: $asking\n" +
                "UId: $uId\n" +
                "dimension: $dimensions\n" +
                "FilePaths:     ${filePaths.toString()}\n" +
                "downloadUrls: ${downloadUrls.toString()}\n"
        txtView.setTextColor(Color.BLUE)
        txtView.textSize = 25F

        elv.addView(txtView)
    }
}


/* program populates array with all of users inventory */
private suspend fun getDB() {
    mainRef.addValueEventListener(object : ValueEventListener {
        override fun onDataChange(snapshot: DataSnapshot) {

            val rockTypes = snapshot!!.children


                // This returns the correct child count...
//                println(" Outer count: " + snapshot.children.count().toString())
                rockTypes.forEach { parent ->
                    var specimens = parent.children
//                    val specName = parent.key
//                    val specCount = parent.children.count()
//                    val
//
////                    println("Inner count: " + parent.children.count().toString())
                specimens.forEach { child ->
                    var rock = child.getValue(Inventory::class.java)!!
                    println(rock.name)
                    userInventory.add(rock)
                }
            }
        }


        override fun onCancelled(error: DatabaseError) {
            TODO("Not yet implemented")
        }
    })
    delay(10000)
}


private fun sortElements(input: List<Inventory>) = input.groupBy { it.name }

private fun setupExpandableListView() {
    val names = ArrayList(listData.keys)
    val expandableListView = binding.elv

    adapter = CustomExpandableListAdapter(this, names, listData)
    expandableListView.setAdapter(adapter)

    expandableListView.setOnGroupExpandListener { groupPosition ->
        Toast.makeText(
            applicationContext,
            (listData as ArrayList<String>)[groupPosition] + " List Expanded.",
            Toast.LENGTH_SHORT
        ).show()
    }

    expandableListView.setOnGroupCollapseListener { groupPosition ->
        Toast.makeText(
            applicationContext,
            (listData as ArrayList<String>)[groupPosition] + " List Collapsed.",
            Toast.LENGTH_SHORT
        ).show()
    }

    expandableListView.setOnChildClickListener { parent, v, groupPosition, childPosition, id ->
        Toast.makeText(
            applicationContext,
            "Clicked: " + (listData as ArrayList<String>)[groupPosition] + " -> " + listData[(listData as ArrayList<String>)[groupPosition]]!!.get(
                childPosition
            ),
            Toast.LENGTH_SHORT
        ).show()
        false
    }
}
}

解决方案

问题1:动态填充ExpandableListView的正确方式

你尝试的直接调用elv.addView()的方式完全不可行,因为ExpandableListView属于AdapterView子类,所有AdapterView都必须通过Adapter管理数据与视图绑定,不能直接添加子View。

正确实现步骤:

  1. 删除displayInventory函数,不需要手动创建TextView添加到列表。
  2. 修改CustomExpandableListAdapter,将数据成员改为可变类型并添加更新方法:
    class CustomExpandableListAdapter internal constructor(
        private val context: Context,
        var titleList: List<String>, // 改为var
        var dataList: HashMap<String, List<Inventory>> // 改为var
    ) : BaseExpandableListAdapter() {
    
        // ... 保留原有其他代码 ...
    
        // 添加数据更新方法
        fun updateData(newTitleList: List<String>, newDataList: HashMap<String, List<Inventory>>) {
            titleList = newTitleList
            dataList = newDataList
            notifyDataSetChanged() // 通知列表刷新视图
        }
    
        // 修正getChildView的类型转换错误,直接使用Inventory对象拼接文本
        override fun getChildView(
            listPosition: Int,
            expandedListPosition: Int,
            isLastChild: Boolean,
            view: View?,
            parent: ViewGroup
        ): View {
            var convertView = view
            val holder: ItemViewHolder
            if (convertView == null) {
                itemBinding = ListItemBinding.inflate(inflater)
                convertView = itemBinding.root
                holder = ItemViewHolder()
                holder.label = itemBinding.expandedListItem
                convertView.tag = holder
            } else {
                holder = convertView.tag as ItemViewHolder
            }
            val inventory = getChild(listPosition, expandedListPosition) as Inventory
            val itemText = "Uid: ${inventory.Uid}\n" +
                    "purchase date: ${inventory.purchDate}\n" +
                    "country: ${inventory.country}\n" +
                    "local: ${inventory.local}\n" +
                    "mine: ${inventory.mine}\n" +
                    "weight: ${inventory.weight}\n" +
                    "paid: ${inventory.paid}\n" +
                    "asking: ${inventory.asking}\n" +
                    "dimension: ${inventory.dimensions}"
            holder.label!!.text = itemText
            holder.label!!.setTextColor(Color.BLUE)
            holder.label!!.textSize = 25F
            return convertView
        }
    }
    
  3. 在ProfileActivity中添加列表更新函数:
    private fun updateExpandableList() {
        val names = ArrayList(listData.keys)
        // 更新已有Adapter或创建新Adapter
        adapter?.let {
            (it as CustomExpandableListAdapter).updateData(names, listData)
        } ?: run {
            adapter = CustomExpandableListAdapter(this, names, listData)
            binding.elv.adapter = adapter
        }
    }
    
  4. 修正getDB函数,移除不必要的协程和延迟,直接在Firebase回调中处理数据并更新列表:
    private fun getDB() {
        mainRef.addValueEventListener(object : ValueEventListener {
            override fun onDataChange(snapshot: DataSnapshot) {
                userInventory.clear()
                snapshot.children.forEach { parent ->
                    parent.children.forEach { child ->
                        val rock = child.getValue(Inventory::class.java)!!
                        userInventory.add(rock)
                    }
                }
                listData = sortElements(userInventory) as HashMap<String, List<Inventory>>
                updateExpandableList() // 主线程直接更新列表
            }
    
            override fun onCancelled(error: DatabaseError) {
                Toast.makeText(this@ProfileActivity, "加载数据失败", Toast.LENGTH_SHORT).show()
            }
        })
    }
    
  5. 在onCreate中直接调用getDB(),不需要GlobalScope协程。

问题2:给ExpandableListView添加图片的实现

非常容易实现,只需修改布局和Adapter代码:

  1. 修改子项布局list_item.xml,添加ImageView:
    <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:orientation="horizontal"
        android:padding="16dp">
    
        <ImageView
            android:id="@+id/item_image"
            android:layout_width="80dp"
            android:layout_height="80dp"
            android:scaleType="centerCrop"
            android:src="@drawable/placeholder"/>
    
        <TextView
            android:id="@+id/expanded_list_item"
            android:layout_width="match_parent"
            android:layout_height="wrap_content"
            android:layout_marginStart="16dp"/>
    </LinearLayout>
    
  2. 更新Adapter的ItemViewHolder和getChildView:
    inner class ItemViewHolder {
        internal var label: TextView? = null
        internal var image: ImageView? = null // 添加ImageView引用
    }
    
    override fun getChildView(
        listPosition: Int,
        expandedListPosition: Int,
        isLastChild: Boolean,
        view: View?,
        parent: ViewGroup
    ): View {
        var convertView = view
        val holder: ItemViewHolder
        if (convertView == null) {
            itemBinding = ListItemBinding.inflate(inflater)
            convertView = itemBinding.root
            holder = ItemViewHolder()
            holder.label = itemBinding.expandedListItem
            holder.image = itemBinding.itemImage // 初始化ImageView
            convertView.tag = holder
        } else {
            holder = convertView.tag as ItemViewHolder
        }
        val inventory = getChild(listPosition, expandedListPosition) as Inventory
        // 拼接文本...
        holder.label!!.text = itemText
        // 加载图片(需引入Glide库)
        if (inventory.downloadUrls.isNotEmpty()) {
            Glide.with(context)
                .load(inventory.downloadUrls[0])
                .placeholder(R.drawable.placeholder)
                .into(holder.image!!)
        } else {
            holder.image!!.setImageResource(R.drawable.no_image)
        }
        return convertView
    }
    
  3. 在app模块的build.gradle中添加Glide依赖:
    dependencies {
        implementation 'com.github.bumptech.glide:glide:4.16.0'
        annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0'
    }
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.15 17:20:48