基于ItemViewType筛选RecyclerView并实现单次埋点追踪
实现方案
一、核心思路拆解
要同时满足标签切换筛选、复用原列表、单次埋点三个需求,核心要做三件事:
- 维护全量原始数据,基于标签动态生成过滤后的展示列表
- 适配器根据当前展示列表的Offer类型返回对应ItemViewType
- 用全局集合记录已上报埋点的Offer ID,确保同一Offer仅上报一次
二、具体实现步骤
1. 定义标签类型枚举
先统一标签的类型标识,避免硬编码:
enum class TabType { ALL, // 全部Offer FAVOURITE, // 收藏Offer PERSONAL // 个人Offer }
2. 数据层改造
在适配器中维护三个核心数据对象:
class OfferAdapter : RecyclerView.Adapter<RecyclerView.ViewHolder>() { // 原始全量Offer列表,始终保留不修改 private val allOffers = mutableListOf<OfferBaseModel>() // 当前选中的标签类型 private var currentTabType: TabType = TabType.ALL // 过滤后的展示列表,随标签切换更新 private var displayedOffers: List<OfferBaseModel> = emptyList() // 记录已上报埋点的Offer ID,确保单次上报 private val trackedOfferIds = mutableSetOf<String>() // 初始化全量数据 fun setAllOffers(offers: List<OfferBaseModel>) { allOffers.clear() allOffers.addAll(offers) // 初始化默认展示全部 updateDisplayedOffers(TabType.ALL) } }
3. 标签切换与数据过滤
实现根据标签类型过滤数据的方法,切换标签时调用:
fun switchTab(tabType: TabType) { currentTabType = tabType updateDisplayedOffers(tabType) // 用DiffUtil优化刷新(可选,比notifyDataSetChanged更高效) notifyDataSetChanged() } private fun updateDisplayedOffers(tabType: TabType) { displayedOffers = when(tabType) { TabType.ALL -> allOffers TabType.FAVOURITE -> allOffers.filterIsInstance<FavoriteOfferModel>() TabType.PERSONAL -> allOffers.filterIsInstance<PersonalOfferModel>() } }
4. 修正ItemViewType逻辑
根据当前展示列表中Offer的实际类型返回对应ViewType,替代原有的styling判断:
override fun getItemViewType(position: Int): Int { return when(displayedOffers[position]) { is PersonalOfferModel -> TYPE_PERSONAL_SECTION is FavoriteOfferModel -> TYPE_FAVOURITE_SECTION else -> throw IllegalArgumentException("Unknown Offer type") } } override fun getItemCount(): Int { return displayedOffers.size }
5. 实现单次埋点逻辑
在ViewHolder的bind方法中,先判断当前Offer是否已上报埋点,未上报则触发并记录:
// 修改PersonalOfferViewHolder的bind方法 class PersonalOfferViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(offer: PersonalOfferModel, trackedIds: MutableSet<String>) { // 先处理UI绑定逻辑... // 埋点判断:仅当Offer未上报过时触发 if (!trackedIds.contains(offer.id)) { // 调用TrackableRecyclerView的埋点方法 trackAnalyticsEvent(offer) trackedIds.add(offer.id) } } } // 同理修改FavouriteOfferViewHolder的bind方法 class FavouriteOfferViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView) { fun bind(offer: FavoriteOfferModel, trackedIds: MutableSet<String>) { // UI绑定... if (!trackedIds.contains(offer.id)) { trackAnalyticsEvent(offer) trackedIds.add(offer.id) } } } // 适配器的onBindViewHolder调用时传入trackedOfferIds override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) { val offer = displayedOffers[position] when (getItemViewType(position)) { TYPE_PERSONAL_SECTION -> (holder as PersonalOfferViewHolder).bind(offer as PersonalOfferModel, trackedOfferIds) TYPE_FAVOURITE_SECTION -> (holder as FavouriteOfferViewHolder).bind(offer as FavoriteOfferModel, trackedOfferIds) } }
三、关键细节说明
- 复用原列表:始终基于原始
allOffers过滤生成展示列表,不会创建新的全量副本,保证内存复用 - 高效刷新:如果数据量较大,建议用
DiffUtil.Callback替代notifyDataSetChanged,减少不必要的视图刷新 - 埋点可靠性:
trackedOfferIds是适配器内的全局集合,只要适配器实例存在,就会记录已上报的Offer;如果页面重建需要保留埋点状态,可将集合持久化到SharedPreferences - 类型安全:用
filterIsInstance过滤子类,避免类型转换错误
内容的提问来源于stack exchange,提问作者Taimoor Khan
相关产品推荐
相关产品推荐

