Android笔记(三十七):封装一个RecyclerView Item曝光工具——用于埋点上报

背景

项目中首页列表页需要统计每个item的曝光情况,给产品运营提供数据报表分析用户行为,于是封装了一个通用的列表Item曝光工具,方便曝光埋点上报

源码分析

  • 核心就是监听RecyclerView的滚动,在滚动状态为SCROLL_STATE_IDLE的时候开始计算哪些item是可见的
kotlin 复制代码
private fun calculateVisibleItemInternal() {
        if (!isRecording) {
            return
        }
        val lastRange = currVisibleRange
        val currRange = findItemVisibleRange()
        val currStartTime = System.currentTimeMillis()


        val newVisibleItemPosList = createCurVisiblePosList(lastRange, currRange)

        visibleItemCheckTasks.forEach {
            it.updateVisibleRange(currRange)
        }
        if (newVisibleItemPosList.isNotEmpty()) {
            VisibleCheckTimerTask(newVisibleItemPosList, this, threshold).also {
                visibleItemCheckTasks.add(it)
            }.execute()
        }
        val it = currVisibleItemPosList.iterator()
        while (it.hasNext()) {
            val entry = it.next()
            if (entry !in currRange) {
                it.remove()
            }
        }
        currVisibleItemPosList.addAll(newVisibleItemPosList)
        currRangeVisibleStartTime = currStartTime
        currVisibleRange = currRange
    }
  • 根据LayoutManager找出当前可见item的范围,剔除掉显示不全的item
kotlin 复制代码
private fun findItemVisibleRange(): IntRange {
        return when (val lm = currRecyclerView?.layoutManager) {
            is GridLayoutManager -> {
                val first = lm.findFirstVisibleItemPosition()
                val last = lm.findLastVisibleItemPosition()
                return fixCurRealVisibleRange(first, last)
            }
            is LinearLayoutManager -> {
                val first = lm.findFirstVisibleItemPosition()
                val last = lm.findLastVisibleItemPosition()
                return fixCurRealVisibleRange(first, last)
            }
            is StaggeredGridLayoutManager -> {
                val firstItems = IntArray(lm.spanCount)
                lm.findFirstVisibleItemPositions(firstItems)
                val lastItems = IntArray(lm.spanCount)
                lm.findLastVisibleItemPositions(lastItems)

                val first = when (RecyclerView.NO_POSITION) {
                    firstItems[0] -> {
                        firstItems[lm.spanCount - 1]
                    }
                    firstItems[lm.spanCount - 1] -> {
                        firstItems[0]
                    }
                    else -> {
                        min(firstItems[0], firstItems[lm.spanCount - 1])
                    }
                }

                val last = when (RecyclerView.NO_POSITION) {
                    lastItems[0] -> {
                        lastItems[lm.spanCount - 1]
                    }
                    lastItems[lm.spanCount - 1] -> {
                        lastItems[0]
                    }
                    else -> {
                        max(lastItems[0], lastItems[lm.spanCount - 1])
                    }
                }
                return fixCurRealVisibleRange(first, last)
            }
            else -> {
                IntRange.EMPTY
            }
        }
    }
  • 对可见的item进行分组,形成一个位置列表,上一次和这一次的分开组队
kotlin 复制代码
private fun createCurVisiblePosList(lastRange: IntRange, currRange: IntRange): List<Int> {
        val result = mutableListOf<Int>()
        currRange.forEach { pos ->
            if (pos !in lastRange) {
                result.add(pos)
            }
        }
        return result
    }
  • 将分组好的列表装进一个定时的task,在延迟一个阈值的时间后执行onVisibleCheck
kotlin 复制代码
class VisibleCheckTimerTask(
    input: List<Int>,
    private val callback: VisibleCheckCallback,
    private val delay: Long
) : Runnable {

    private val visibleList = mutableListOf<Int>()
    private var isExecuted = false

    init {
        visibleList.addAll(input)
    }

    fun updateVisibleRange(keyRange: IntRange) {
        val iterator = visibleList.iterator()
        while (iterator.hasNext()) {
            val entry = iterator.next()
            if (entry !in keyRange) {
                iterator.remove()
            }
        }
    }

    override fun run() {
        callback.onVisibleCheck( this, visibleList)
    }

    fun execute() {
        if (isExecuted) {
            return
        }
        mHandler.postDelayed(this, delay)
        isExecuted = true
    }

    fun cancel() {
        mHandler.removeCallbacks(this)
    }
}
  • 达到阈值后,再获取一遍可见item的范围,对于仍然可见的item回调onItemShow
kotlin 复制代码
override fun onVisibleCheck(task: VisibleCheckTimerTask, visibleList: List<Int>) {
        val visibleRange = findItemVisibleRange()
        visibleList.forEach {
            if (it in visibleRange) {
                notifyItemShow(it)
            }
        }
        visibleItemCheckTasks.remove(task)
    }

完整源码

待补充

相关推荐
金融RPA机器人丨实在智能6 分钟前
Android Studio开发App项目进入AI深水区:实在智能Agent引领无代码交互革命
android·人工智能·ai·android studio
科技块儿7 分钟前
利用IP查询在智慧城市交通信号系统中的应用探索
android·tcp/ip·智慧城市
独行soc37 分钟前
2026年渗透测试面试题总结-18(题目+回答)
android·网络·安全·web安全·渗透测试·安全狮
王码码20351 小时前
Flutter for OpenHarmony 实战之基础组件:第二十七篇 BottomSheet — 动态底部弹窗与底部栏菜单
android·flutter·harmonyos
2501_915106321 小时前
app 上架过程,安装包准备、证书与描述文件管理、安装测试、上传
android·ios·小程序·https·uni-app·iphone·webview
JustDI-CM1 小时前
AI学习笔记-提示词工程
人工智能·笔记·学习
爱写bug的野原新之助1 小时前
加密摘要算法MD5、SHA、HMAC:学习笔记
笔记·学习
vistaup1 小时前
OKHTTP 默认构建包含 android 4.4 的TLS 1.2 以及设备时间不对兼容
android·okhttp
常利兵1 小时前
ButterKnife在Android 35 + Gradle 8.+环境下的适配困境与现代化迁移指南
android
撩得Android一次心动1 小时前
Android LiveData 全面解析:使用Java构建响应式UI【源码篇】
android·java·android jetpack·livedata