Android Kotlin动态填充ExpandableListView问题及图片添加咨询
问题描述
我正在学习Android的Kotlin/UI设计基础,尝试从数据库动态填充ExpandableListView。目前在displayInventory函数中调用elv.addView(txtView)时出现addView(View) is not supported in AdapterView错误。通过相关帖子了解到不能直接向ListView添加项,需向Adapter添加数据再设置到ListView,但仍难以将该概念转化为Kotlin代码实现。
疑问
- 是否可以通过我尝试的这种函数方式动态填充ExpandableListView?
- 如果后续要给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。
正确实现步骤:
- 删除
displayInventory函数,不需要手动创建TextView添加到列表。 - 修改
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 } } - 在
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 } } - 修正
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() } }) } - 在
onCreate中直接调用getDB(),不需要GlobalScope协程。
问题2:给ExpandableListView添加图片的实现
非常容易实现,只需修改布局和Adapter代码:
- 修改子项布局
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> - 更新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 } - 在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
相关产品推荐
相关产品推荐

