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

完整源码

待补充

相关推荐
Jerry3 小时前
Compose 中的绘制功能简介
android
蒙奇D索大3 小时前
【数据结构】考研数据结构核心考点:二叉排序树(BST)全方位详解与代码实现
数据结构·笔记·学习·考研·算法·改行学it
玲娜贝儿--努力学习买大鸡腿版3 小时前
推荐算法学习笔记(十九)阿里SIM 模型
笔记·学习·推荐算法
我科绝伦(Huanhuan Zhou)4 小时前
【脚本升级】银河麒麟V10一键安装MySQL9.3.0
android·adb
消失的旧时光-19434 小时前
Android回退按钮处理方法总结
android·开发语言·kotlin
叫我龙翔5 小时前
【MySQL】从零开始了解数据库开发 --- 数据表的约束
android·c++·mysql·数据库开发
2501_916013745 小时前
iOS 上架 App 全流程实战,应用打包、ipa 上传、App Store 审核与工具组合最佳实践
android·ios·小程序·https·uni-app·iphone·webview
我命由我123455 小时前
Photoshop - Photoshop 工具栏(10)透视裁剪工具
经验分享·笔记·学习·ui·职场和发展·职场发展·photoshop
2501_915106325 小时前
iOS 26 能耗监测全景,Adaptive Power、新电池视图
android·macos·ios·小程序·uni-app·cocoa·iphone