如何在BaseExpandableListAdapter中集成可配置的独立Google Ads?
解决BaseExpandableListAdapter中插入独立Google Ads组项的问题
问题背景
- 需求:在BaseExpandableListAdapter中实现独立的广告视图作为组项,支持在任意偶数位置插入
- 已遇问题:此前在父视图XML中嵌入广告视图后,广告所在组项仍可展开,不符合需求
解决方案步骤
核心思路是将广告项与普通记录项拆分为独立类型,通过合并数据源区分处理,让广告组项无对应子项,从根源避免展开问题。
1. 定义数据类型密封类
创建密封类区分普通记录和广告两种组项:
sealed class ListItemCllRcc { data class RecordItem(val record: RecordCllRcc) : ListItemCllRcc() object AdItem : ListItemCllRcc() }
2. 修改适配器数据源
替换原单一记录列表为包含两种类型的合并列表,并实现数据合并逻辑:
var combinedList: MutableList<ListItemCllRcc> = mutableListOf() set(value) { field = value notifyDataSetChanged() } // 合并记录与广告项的方法,可自定义插入位置规则 fun updateRecords(newRecords: List<RecordCllRcc>) { combinedList.clear() newRecords.forEachIndexed { index, record -> combinedList.add(ListItemCllRcc.RecordItem(record)) // 示例:每4条记录后插入广告,如需偶数位置可改为 (index + 1) % 2 == 0 if ((index + 1) % 4 == 0) { combinedList.add(ListItemCllRcc.AdItem) } } notifyDataSetChanged() }
3. 重写Adapter核心方法
根据组项类型返回对应参数:
override fun getGroupCount(): Int { return combinedList.size } override fun getChildrenCount(groupPosition: Int): Int { // 广告项无对应子项,返回0;普通记录项返回1 return when (combinedList[groupPosition]) { is ListItemCllRcc.RecordItem -> 1 is ListItemCllRcc.AdItem -> 0 } } override fun getGroup(groupPosition: Int): ListItemCllRcc { return combinedList[groupPosition] } override fun getChild(groupPosition: Int, childPosition: Int): RecordCllRcc { val recordItem = combinedList[groupPosition] as ListItemCllRcc.RecordItem return recordItem.record } override fun getGroupId(groupPosition: Int): Long { return groupPosition.toLong() } override fun isChildSelectable(groupPosition: Int, childPosition: Int): Boolean { // 仅普通记录项的子项可选中 return combinedList[groupPosition] is ListItemCllRcc.RecordItem }
4. 区分加载组项视图
根据组项类型加载对应布局,广告项使用独立的广告布局:
override fun getGroupView( groupPosition: Int, isExpanded: Boolean, convertView: View?, parent: ViewGroup? ): View { return when (val item = combinedList[groupPosition]) { is ListItemCllRcc.RecordItem -> { // 加载普通记录项布局 val binding: ListItemRecordCllRccBinding = if (convertView == null || convertView.tag != "record") { DataBindingUtil.inflate( LayoutInflater.from(parent!!.context), R.layout.list_item_record_cll_rcc, parent, false ).apply { tag = "record" } } else { DataBindingUtil.getBinding(convertView)!! } bindRecordItem(binding, item.record, groupPosition) binding.root } is ListItemCllRcc.AdItem -> { // 加载独立广告布局(需提前创建包含NativeAd模板的XML) val adBinding: ListItemAdCllRccBinding = if (convertView == null || convertView.tag != "ad") { DataBindingUtil.inflate( LayoutInflater.from(parent!!.context), R.layout.list_item_ad_cll_rcc, parent, false ).apply { tag = "ad" } } else { DataBindingUtil.getBinding(convertView)!! } loadNativeAd(adBinding) adBinding.root } } }
5. 实现视图绑定与广告加载
- 普通记录项绑定逻辑(剥离原广告代码):
private fun bindRecordItem( binding: ListItemRecordCllRccBinding, record: RecordCllRcc, groupPosition: Int ) { binding.record = record binding.isFirstOrLastItem = groupPosition == 0 }
- 独立广告加载逻辑:
private fun loadNativeAd(binding: ListItemAdCllRccBinding) { val adLoader = AdLoader.Builder(mContext, mContext.getString(R.string.native_ad_id)) .forNativeAd { ad: NativeAd -> binding.templateID.setNativeAd(ad) } .withAdListener(object : AdListener() { override fun onAdFailedToLoad(adError: LoadAdError) { Log.e("AdError", adError.message) } override fun onAdLoaded() { Log.e("AdStatus", "广告加载成功") super.onAdLoaded() } }) .withNativeAdOptions(NativeAdOptions.Builder().build()) .build() adLoader.loadAd(AdRequest.Builder().build()) }
6. 调整子视图加载逻辑
仅处理普通记录项的子视图加载:
override fun getChildView( groupPosition: Int, childPosition: Int, isLastChild: Boolean, convertView: View?, parent: ViewGroup ): View { val recordItem = combinedList[groupPosition] as ListItemCllRcc.RecordItem val binding: ListItemExpandedRecordBinding = if (convertView == null) { DataBindingUtil.inflate( LayoutInflater.from(parent.context), R.layout.list_item_expanded_record, parent, false ) } else { DataBindingUtil.getBinding(convertView)!! } bindExpandedViewCllRcc(binding, recordItem.record) return binding.root } // 修改子视图绑定方法,直接接收Record参数 private fun bindExpandedViewCllRcc( binding: ListItemExpandedRecordBinding, record: RecordCllRcc ) { try { val file = Environment.getExternalStorageDirectory() .toString() + "/Call/" + record.nameCllRcc binding.viewModel = viewModelCllRcc if (viewModelCllRcc.currentRecordCllRcc.value?.nameCllRcc != record.nameCllRcc) { binding.record = record viewModelCllRcc.currentRecordCllRcc.value = record } binding.wave.setRawDataCllRcc(File(file).readBytes()) { } binding.wave.onProgressListenerCllRcc = object : CustomProgressListenerCllRcc() { override fun onProgressChanged(progress: Float, byUser: Boolean) { if (byUser) { viewModelCllRcc.seekToCllRcc(progress, record) } } } viewModelCllRcc.currentPositionCllRcc.observe(lifecycleOwnerCllRcc) { binding.redLineSeekBar.translationX = (binding.wave.width * it.toFloat()) / 101.5f } viewModelCllRcc.isPlayingCllRcc.observe(lifecycleOwnerCllRcc) { binding.isPlaying = it } } catch (e: Exception) { e.printStackTrace() } }
关键说明
- 广告项作为独立组项存在,通过
getChildrenCount返回0,让ExpandableListView不对其提供展开交互 - 密封类确保类型安全,Adapter可精准区分不同组项的处理逻辑
- 广告插入位置可通过
updateRecords方法中的规则自由调整,满足任意位置插入需求
内容的提问来源于stack exchange,提问作者Himanshu Yadav Anshu
相关产品推荐
相关产品推荐

