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

基于ItemViewType筛选RecyclerView并实现单次埋点追踪

实现方案

一、核心思路拆解

要同时满足标签切换筛选、复用原列表、单次埋点三个需求,核心要做三件事:

  1. 维护全量原始数据,基于标签动态生成过滤后的展示列表
  2. 适配器根据当前展示列表的Offer类型返回对应ItemViewType
  3. 用全局集合记录已上报埋点的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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 23:50:04