背景
项目中首页列表页需要统计每个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)
}
完整源码
待补充