需求
数据埋点,要采集列表里显示的所有卡片。展开讲就是进入界面时把所有已展示的item统计出来,滑动时把新展示的item再统计进来
代码实现
/**
* 创建人:linmutang
* 创建时间:9/29/21
*/
interface OnExposeListener {
/**
* 返回刚出现的item数组
* @param list MutableList<Int>
*/
fun onItemViewVisible(list: MutableList<Int>)
}
/**
*
* 用于ScrollView采点用
*
* 创建人:linmutang
* 创建时间:9/29/21
*/
class ScrollViewExposeUtil {
lateinit var mRecyclerView: RecyclerView
lateinit var mListener: OnExposeListener
var lastStart: Int = 0
var lastEnd: Int = 0
fun setRecyclerExposeListener(recyclerView: RecyclerView?, listener: OnExposeListener) {
if (recyclerView == null || recyclerView.visibility != View.VISIBLE) {
return
}
mRecyclerView = recyclerView
mListener = listener
mRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() {
override fun onScrollStateChanged(recyclerView: RecyclerView, newState: Int) {
//关注:SCROLL_STATE_IDLE:停止滚动; SCROLL_STATE_DRAGGING: 用户慢慢拖动
// 关注:SCROLL_STATE_SETTLING:惯性滚动
if (newState == RecyclerView.SCROLL_STATE_IDLE || newState == RecyclerView.SCROLL_STATE_DRAGGING
|| newState == RecyclerView.SCROLL_STATE_SETTLING) {
handleVisibleItems()
}
}
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy)
handleVisibleItems()
}
})
}
fun handleVisibleItems() {
//View.getGlobalVisibleRect(new Rect()),true表示view视觉可见,无论可见多少。
if (mRecyclerView == null || mRecyclerView.visibility != View.VISIBLE ||
!mRecyclerView.isShown || !mRecyclerView.getGlobalVisibleRect(Rect())
) {
return
}
//保险起见,为了不让统计影响正常业务,这里做下try-catch
try {
// 获取显示的第一个与最后一个位置,存在数组里
var range: IntArray? = IntArray(2)
when (val manager = mRecyclerView.layoutManager) {
is LinearLayoutManager -> {
range = findRangeLinear(manager)
}
is GridLayoutManager -> {
range = findRangeGrid(manager)
}
is StaggeredGridLayoutManager -> {
range = findRangeStaggeredGrid(manager)
}
}
if (range == null || range.size < 2) {
return
}
Log.e("linmutang", " range[0] = ${range[0]} range[1] = ${range[1]}")
Log.e("linmutang", " lastStart =$lastStart lastEnd = $lastEnd")
// 首次进来
if (lastEnd == 0) {
var list = arrayListOf<Int>()
for (i in range[0]..range[1]) {
list.add(i)
}
mListener.onItemViewVisible(list)
lastEnd = range[1]
return
}
// 向上滑动
if (range[1] > lastEnd || range[0] > lastStart) {
var list = arrayListOf<Int>()
for (i in (lastEnd + 1)..range[1]) {
list.add(i)
}
mListener.onItemViewVisible(list)
lastStart = range[0]
lastEnd = range[1]
return
}
// 向下滑动
if (range[0] < lastStart || range[1] < lastEnd) {
var list = arrayListOf<Int>()
for (i in range[0] until lastStart) {
list.add(i)
}
mListener.onItemViewVisible(list)
lastStart = range[0]
lastEnd = range[1]
return
}
} catch (e: Exception) {
e.printStackTrace()
}
}
private fun findRangeLinear(manager: LinearLayoutManager): IntArray? {
val range = IntArray(2)
range[0] = manager.findFirstVisibleItemPosition()
range[1] = manager.findLastVisibleItemPosition()
return range
}
private fun findRangeGrid(manager: GridLayoutManager): IntArray? {
val range = IntArray(2)
range[0] = manager.findFirstVisibleItemPosition()
range[1] = manager.findLastVisibleItemPosition()
return range
}
private fun findRangeStaggeredGrid(manager: StaggeredGridLayoutManager): IntArray? {
val startPos = IntArray(manager.spanCount)
val endPos = IntArray(manager.spanCount)
manager.findFirstVisibleItemPositions(startPos)
manager.findLastVisibleItemPositions(endPos)
return findRange(startPos, endPos)
}
private fun findRange(startPos: IntArray, endPos: IntArray): IntArray {
var start = startPos[0]
var end = endPos[0]
for (i in 1 until startPos.size) {
if (start > startPos[i]) {
start = startPos[i]
}
}
for (i in 1 until endPos.size) {
if (end < endPos[i]) {
end = endPos[i]
}
}
return intArrayOf(start, end)
}
}
实现思路比较简单,调用 ScrollView 的 OnScrollListener 方法。滑动时找到当前所有显示item的第一个与最后一个的位置,根据这个范围值对比上次的范围值,确定哪些是新出现的item,把这些记录在数组中返回出去,可以批量处理
使用
ScrollViewExposeUtil().setRecyclerExposeListener(binding.rvData, object : OnExposeListener {
override fun onItemViewVisible(list: MutableList<Int>) {
for (i in list) {
Log.e(TAG, mData[i] )
}
}
})
延伸:采集展现超过50%的item
/**
* 在上面过滤的基础上再过滤一遍
*
* @param view 可见item的view
* @param position 可见item的position
* @param orientation recyclerView的方向
*/
private void setCallbackForLogicVisibleView(View view, int position, int orientation) {
if (view == null || view.getVisibility() != View.VISIBLE ||
!view.isShown() || !view.getGlobalVisibleRect(new Rect())) {
return;
}
Rect rect = new Rect();
boolean cover = view.getGlobalVisibleRect(rect);
// item逻辑上可见:可见且可见高度(宽度)>view高度(宽度)50%才行
boolean visibleHeightEnough = orientation == OrientationHelper.VERTICAL && rect.height() > view.getMeasuredHeight() / 2;
boolean visibleWidthEnough = orientation == OrientationHelper.HORIZONTAL && rect.width() > view.getMeasuredWidth() / 2;
boolean isItemViewVisibleInLogic = visibleHeightEnough || visibleWidthEnough;
if (cover && isItemViewVisibleInLogic) {
// 此处是符合要求的 position
}
}