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)
    }

完整源码

待补充

相关推荐
月阳羊7 小时前
【硬件-笔试面试题】硬件/电子工程师,笔试面试题-26,(知识点:硬件电路的调试方法:信号追踪,替换,分段调试)
笔记·嵌入式硬件·面试·职场和发展
Star在努力8 小时前
14-C语言:第14天笔记
c语言·笔记·算法
Dnelic-8 小时前
Android 5G NR 状态类型介绍
android·5g·telephony·connectivity·自学笔记·移动网络数据
霜绛9 小时前
机器学习笔记(三)——决策树、随机森林
人工智能·笔记·学习·决策树·随机森林·机器学习
吗喽对你问好10 小时前
Android UI 控件详解实践
android·ui
charlie11451419112 小时前
快速入门Socket编程——封装一套便捷的Socket编程——导论
linux·网络·笔记·面试·网络编程·socket
东风西巷13 小时前
X-plore File Manager v4.34.02 修改版:安卓设备上的全能文件管理器
android·网络·软件需求
yzpyzp13 小时前
Android 15中的16KB大页有何优势?
android
安卓开发者13 小时前
Android Room 持久化库:简化数据库操作
android·数据库
程序视点13 小时前
FadCam安卓后台录制神器:2025最全使用指南(开源/免费/息屏录制)
android