一、背景

产品需求中我们经常会有统计recyclerView的每个item的曝光需求:
recyclerView上下滚动每个item从不可见进入到屏幕可见范围(这里包含item的可见范围,还有item的曝光时长)
在tab切换,或者页面切换的时候会引起recyclerView从不可见到可见的变化(当前屏幕上可见的item都算一次曝光)
数据变化引起的曝光
为了达到产品需求,我们首先需要数据收集,在滑动过程中收集所有需要上报的item,然后在适当的时机进行上报比如滑动停止、页面切换。

二、需求分析

通过上面的需求分析,我们可以知道recyclerView的曝光主要分为滑动曝光和可见性变化曝光,还有数据变化曝光。
1、滑动曝光
我们可以通过监听recyclerView的滑动过程,在滑动的过程中收集曝光的数据(因为曝光行为就是在滑动过程中产生的),然后当滑动停止的时候去进行一个曝光上报(这样既能保证实时性,又可以兼顾手机的性能)。

2、可见性变化曝光
这里我们需要监听recyclerView的可见性变化,但是并没有提供给我们View可见性变化的监听。虽然有一些焦点变化的监听,但是并不能完全覆盖View的可见性变化。所以这里我们必须想别的办法来实现,这里我通过Actvity的生命周期的onResume和onPause想到有没有可能实现Fragment的onFragmentResume和onFragmentPause来监听Fragment的可见性,监听到Fragment的可见性,也就相当于监听到recyclerView的可见性。然后遍历当前可见的Item收集,并上报即可。

3、数据变化引起的曝光
有时候数据变化也会引起相应的曝光,这种的我们比较好处理只需要监听相应的数据变化。然后对可见的需要曝光的item进行曝光处理即可。

三、实现recyclerView滑动过程中引起的曝光

实现原理流程图如下:

因为Adatper控制着RecyclerView的ViewHolder的创建和绑定,并且对应的数据适配都是在Adapter中完成的,所以这里选择重写Adapter来实现曝光功能。

首先我们需要在recyclerView的滑动过程中进行数据收集,即收集显示到屏幕上需要曝光的item。
我们知道当recylerView的ViewHolder加载到屏幕上会先调用onViewAttachedToWindow(holder: VH),所以我们就选择在这个方法进行数据收集。只要显示到屏幕的数据都会被收集到collectDatas列表当中

 /*** 进行曝光items收集*/
override fun onViewAttachedToWindow(holder: VH) {val item = ExpItem<T>()item.data = holder.mDataitem.itemView = holder.itemViewitem.postion = holder.mPositioncollectDatas.add(item)super.onViewAttachedToWindow(holder)//检查曝光范围,并更新曝光开始时间if (innerCheckExposureData(item)){item.startTime = TimeUtil.getCurrentTimeMillis()}}

接着我们需要筛选需要进行曝光的数据,计算每个Item在屏幕上的位置,自定义筛选条件(比如:只曝光广告)这个筛选我们需要在recyclerView的滚动过程中进行计算,因为滚动过程中ViewHolder的露出范围是不断发生改变的,然后我们把筛选的数据从collectDatas中移动到expDatas列表当中

为什么这个筛选过程要放在onScrolled过程中?
首先曝光的行为是在滑动过程中达成的,比如我们不断的上下滑动recyclerView,导致item_1不断的出现在屏幕中和消失在屏幕中。假如这个过程中item_1曝光了5次,滑动停止后回到我们初始的滑动位置。如果我们在滑动停止的时候来筛选曝光的item,可能会认为完全没有新曝光的item。因为我们滑动停止在了原来初始的位置,显然这个计算是不对的。想要正确的记录曝光的item,就必须要在recyclerView的滑动过程中去筛选达到曝光条件的item。
其次考虑到曝光时间,在滑动过程中Item达到曝光条件,这时候我们就应该记录曝光的开始时间。在其他的时机无法正确的记录曝光时间。

在onScrolled的过程中进行筛选计算是否会影响recyclerView的性能,导致滑动不流畅?
这个筛选计算分为两个部分,一个部分是需要开发者定义的筛选逻辑,这里就需要开发者自己注意不要有耗时的判断逻辑。 第二个部分是内部的筛选逻辑,主要是判断item的露出高度是否达到曝光要求。
这个判断是否会影响recyclerView的性能?其实也是不会的。
首先item的自身的高度、位置(滑动偏移量)在滑动过程中每一帧的渲染之前都是已经由recyclerView计算好的,否则recyclerView也没有办法把每个item绘制在正确的位置。所以显然这个计算肯定不会影响recyclerView的流畅性。
而我们需要做的判断主要是拿到当前item的位置信息,进行比较看是否达到曝光要求,这个显然也不是一个耗时操作。我们是通过 itemView!!.getGlobalVisibleRect(rect)这个方法来获取item的位置信息的,通过代码跟踪,我们可以看下具体实现逻辑

public boolean getChildVisibleRect(
View child, Rect r, android.graphics.Point offset, boolean forceParentCheck) {...rect.set(r);final int dx = child.mLeft - mScrollX;final int dy = child.mTop - mScrollY;...rect.offset(dx, dy);...rectIsVisible = rect.intersect(0, 0, width, height);...return rectIsVisible;
}public boolean intersect(float left, float top, float right, float bottom) {if (this.left < right && left < this.right&& this.top < bottom && top < this.bottom) {if (this.left < left) {this.left = left;}if (this.top < top) {this.top = top;}if (this.right > right) {this.right = right;}if (this.bottom > bottom) {this.bottom = bottom;}return true;}return false;
}

通过代码可知我们获取itemView的可见范围主要是通过itemView的当前位置rect和他所在的ViewGroup(即recyclerView)的范围大小做交集。看代码可知也都是一些比较大小的逻辑,没有耗时操作,所以无需担心这个会造成recyclerView的滑动卡顿。
那么接下来我们看下具体实现筛选过程

override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {super.onScrolled(recyclerView, dx, dy)val it = collectDatas.iterator()while (it.hasNext()) {val item = it.next()//判断露出范围if (innerCheckExposureData(item)) {if (item.startTime == 0L) {item.startTime = TimeUtil.getCurrentTimeMillis()}if (funcCheck == null) {expDatas.add(item)//自定义过滤条件} else if (funcCheck!!.invoke(item)) {expDatas.add(item)}it.remove()}}
}

onScrolled方法第8-10行是更新曝光开始时间 onScrolled方法第7行是露出范围的检测

/*** 内部判断当itemView的可见度达到多少才需要曝光*/
private fun innerCheckExposureData(item: ExpItem<*>): Boolean {val rect = Rect()val visible = item.itemView!!.getGlobalVisibleRect(rect)if (visible) {if (rect.height() >= item.itemView!!.measuredHeight * outPercent) {return true}}return false
}

onScrolled方法第11-16行是我们自定义的筛选条件判断,即下面对应第10行的筛选条件

/*** 设置曝光监听*/
myAdapter.setExposureFunc(items->{//返回需要曝光的数据列表for (ExpItem<NewFeedBean> item : items) {LogUtil.d("kami","exposure position = " + item.getPostion() + ": " +item.getData().sourceFeed.content + ",duration = " + (item.getEndTime() - item.getStartTime()));}return null;
},item->{//自定义需要曝光筛选:比如只曝光广告数据return item.getData().isAd();
});

最后在滑动停止进行曝光数据回调之前进行曝光时长的筛选。从expDatas中选择达到曝光时长的数据,最后进行数据上报

//设置曝光监听
when (newState) {//滑动完成RecyclerView.SCROLL_STATE_IDLE ->val needExpDatas = getExposureList(expDatas)if (!needExpDatas.isEmpty()) {funcExp?.invoke(needExpDatas)}else -> {}

第5行就是我们对曝光时长的筛选

/*** 内部判断当itemView的曝光时长达到多少才需要进行曝光*/
private fun getExposureList(expDatas: ArrayList<ExpItem<T>>): ArrayList<ExpItem<T>> {val needExpDatas = ArrayList<ExpItem<T>>()val it = expDatas.iterator()while (it.hasNext()) {val item = it.next()if (item.endTime != 0L) {if (item.endTime - item.startTime >= exposureTime) {needExpDatas.add(item)}it.remove()} else {if (TimeUtil.getCurrentTimeMillis() - item.startTime >= exposureTime) {item.endTime = TimeUtil.getCurrentTimeMillis()needExpDatas.add(item)it.remove()}}}return needExpDatas
}

在ViewHolder AttachToWindow 的时候,和RecyclerView 滑动的时候我们会更新曝光开始时间,在 ViewHolder DettachToWindw 和 RecyclerView 滑动停止的时候我们会更新曝光结束时间。
item.endTime 不为零如果达到曝光时长,则表示需要进行曝光加入曝光列表,否则就舍弃。
item.endTime 为零则表示ViewHoler还在持续曝光,则用当前时间计算,达到曝光时间加入曝光列表,否则不做处理(因为还在持续曝光,等下次滑动达到曝光时长在进行曝光)。
下面是onViewDetachedFromWindow的具体代码,包括移除无需曝光的数据和更新曝光结束时间。

/*** 对移除的itemView进行曝光时长的修改*/
override fun onViewDetachedFromWindow(holder: VH) {//在Dettached的时候,未被移动到expDatas列表的数据证明没有达到曝光条件,无需曝光。就可以把它们从collectDatas列表中移除了val it = collectDatas.iterator()while (it.hasNext()) {val item = it.next()if (holder .mPosition == item.postion && holder.itemView === item.itemView) {it.remove()}}//更新曝光结束时间for (expItem in expDatas) {if (holder.mPosition == expItem.postion && expItem.itemView === holder.itemView) {expItem.endTime = TimeUtil.getCurrentTimeMillis()}}super.onViewDetachedFromWindow(holder)
}

这样我们就完成了RecyclerView在滑动过程中引起的上报。

Android recyclerView曝光统计相关推荐

  1. Android RecyclerView曝光采集

    一.背景 近期pm提出需要统计首页商品的曝光亮,由于我们的首页是用的recylerview实现的,这里就来讲下如何使用监听recylerview的滚动事件来实现子view的曝光量统计,我们这里说的vi ...

  2. RecyclerView的曝光统计

    本文提供了一种Android列表曝光统计的功能实现.使开发者无需关心数据收集过程,只需简单的设置即可在适当的曝光时机通知开发者所需的曝光数据. 产品需求中我们经常会有统计recyclerView的每个 ...

  3. android view 曝光,Android 曝光采集(商品view曝光量的统计)第二弹

    安卓端有效曝光统计步骤 1 ,要确定什么样的算有效曝光(在屏幕停留时间超过一个值如2秒) 2,监听到每个view移入和移出屏幕的事件 3,把数据绑定到view(view相当于数据的载体) 4,根据监听 ...

  4. Android 列表曝光数据统计全面解析

    列表曝光统计 开发越往后走,越发觉察到数据的宝贵,所谓量变产生质变,即便是一些平时看上去无足轻重的数据一旦量上去了加以分析也会是一比巨大的财富. 列表可以说是当下互联网产品中最最最常见的呈现形式了,几 ...

  5. Android RecyclerView 基本使用

    Android RecyclerView 基本使用 概述 RecyclerView出现已经有一段时间了,相信大家肯定不陌生了,大家可以通过导入support-v7对其进行使用. 据官方的介绍,该控件用 ...

  6. Android RecyclerView添加Header头部

     Android RecyclerView添加Header头部 Android RecyclerView不像以前的ListView那样直接添加头部,如果要给RecyclerView增加头部,则需要 ...

  7. android 炫酷背景,炫酷-背景图垂直循环滚动登录页,Android RecyclerView实现

    炫酷-背景图上下循环滚动登录页,Android RecyclerView实现方法 某站的登录页背景不停循环滚动,和街边的广告箱很像,感觉不错我也心动了.决定高仿一下,参考了几篇文章后就动手了. 实现步 ...

  8. android批量删除图片,Android RecyclerView单点、批量数据元素项目item的增加、删除和移动...

    Android RecyclerView单点.批量数据元素项目item的增加.删除和移动 前文附录1,2介绍了基本的Android RecyclerView单点.批量元素项目的更新.现在给出其他比较重 ...

  9. Android RecyclerView设计通用Adapter

    RecylerView 的使用频率现在也算做是很高了吧?使用起来的确是挺方便的,也容易实现一些比较好看的效果 一.一般步骤 一般的设计流程都是如下所示 首先是需要一个 JavaBean 来承载数据,包 ...

最新文章

  1. OSD的主要实现方法和类型(转)
  2. (原创)惠州市惠阳区房价偏低的原因深入分析
  3. STL-bitset源码解析
  4. node --- 在express中配置使用模板引擎(art-template)
  5. Java 获取集合元素的值
  6. 幕乔美化版音乐网站源码
  7. mysql5.6 排序失效_mysql 使用union(all) + order by 导致排序失效
  8. 1.3_bubble_sort_冒泡排序
  9. 保存页面的滚动条的位置
  10. 下载silverlight官网的全部视频教程
  11. [转贴]彻底解决 CrystalReports 登录失败问题。
  12. 一汽丰田RAV4电路图2012至2013
  13. 5G技术对我们生活的影响
  14. 《MATLAB 神经网络43个案例分析》:第37章 基于灰色神经网络的预测算法研究——订单需求预测
  15. 自然语言处理 # 中文分词技术 概述
  16. OpenCvSharp工作学习笔记5-图片缩放
  17. matlab 球坐标绘图,在Matlab中绘制球坐标系
  18. verilog语言实现全加器
  19. 诺基亚java模拟器exe,无需模拟器!骁龙810手机成功运行Win10:可开exe程序
  20. 微信小程序开发教程(破解版IDE 无内测资格也可使用)

热门文章

  1. InfluxDB 踩坑小记
  2. 查询高考成绩湖北孝感2021,速看!湖北高考查分及志愿填报时间公布!
  3. java寂静岭 攻略,寂静岭1
  4. [原]flash研究(二)——与asp.net服务交互(访问数据库)
  5. 关于SMI、MSI、SCI、INTx各种中断小结
  6. PCI板卡在工控机上无法使用怎么办?
  7. URL的格式以及每一部分的详细解释(配图哦)
  8. MySQL - 函数及约束命令
  9. 关于Android Studio无法检测到夜神模拟器的原因及解决方案
  10. openpyxl详细教程