需求分析:
当RecycleView列表滑动时,或者页面在onResume,onPause状态切换,需求统计手机视图Item的曝光时长,并上报;
实现:
1,需要监控RecycleView的滑动事件,获取findFirstVisibleItemPosition,findFirstVisibleItemPosition第一个,和最后一个位置,并判断item的可见性
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
try {
checkCurrentVisibleItem();
}catch (Exception e){
}
}
});
/**
* 判断Item是否展示
*/
private void checkCurrentVisibleItem() {
//存储第一个,和最后一个可见item的位置
List<Integer> range = new ArrayList(2);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
int orientation;
if (manager instanceof LinearLayoutManager){
range.add(0,((LinearLayoutManager) manager).findFirstVisibleItemPosition());
range.add(1, ((LinearLayoutManager) manager).findFirstVisibleItemPosition());
orientation = ((LinearLayoutManager) manager).getOrientation();
}else {
return;
}
for (int i=range.get(0);i<=range.get(1);i++ ) {
View view = manager.findViewByPosition(i);
dispatchViewVisible(view, i, orientation);
}
}
2,对item的可见性进行分发
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
try {
checkCurrentVisibleItem();
}catch (Exception e){
}
}
});
/**
* 分发InVisible
*/
private void dispatchInvisible(
int position,
long lastTime,
long curTime) {
if (lastTime == curTime) {
return;
}
LogUtil.d("RecyclerViewTrack:"+position+":-1");
timeSparseArray.put(position, -1);
if (listener!=null)
listener.onItemViewInvisible(position, curTime - lastTime);
}
/**
* 分发Visible
*/
private void dispatchVisible(int position, long curTime) {
// Log.i(TAG, "dispatchVisible: position = $position")
timeSparseArray.put(position, curTime);
LogUtil.d("RecyclerViewTrack:"+position+":"+curTime);
if (listener!=null)
listener.onItemViewVisible(position);
}
3,用ItemExposeListener接口进行可见性的回调
public interface ItemExposeListener {
/**
* item可见回调
* @param position item在列表中的位置
*/
void onItemViewVisible(int position);
/**
* item消失回调
* @param position item在列表中的位置
* @param showTime 曝光时间
*/
void onItemViewInvisible(int position, long showTime);
}
lifecycle.addObserver(this);
/**
* 在Fragment在后续走到Resume时onScroll不会被触发,所以需要手动触发
*/
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private void dispatchResume() {
int size = timeSparseArray.size();
long curTime = System.currentTimeMillis();
for (int i=0; i<size;i++) {
int key = timeSparseArray.keyAt(i);
dispatchVisible(key, curTime);
}
}
/**
* 在Fragment走到Pause时onScroll不会被触发上报,所以需要手动触发
*/
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private void dispatchPause() {
int size = timeSparseArray.size();
for (int i=0;i<timeSparseArray.size();i++) {
int key = timeSparseArray.keyAt(i);
long value = timeSparseArray.valueAt(i);
if (value > 0) {
//是可见状态,则改为不可见状态
dispatchInvisible(key, value, System.currentTimeMillis());
} else {
//不是可见状态直接移除
timeSparseArray.delete(key);
i--;
}
}
}
public void onHiddenChanged(boolean hidden){
if (hidden){
dispatchPause();
}else {
dispatchResume();
}
}
至此,我们实现了上述需求的item曝光时长的统计监听
我们还需要当activity页面切换,或fragment切换时,item显示隐藏的判断,我们需要监听activity生命周期Lifecycle
至此,我们实现了当列表滑动时,item的显示和隐藏的监听;
完整代码:
public class RecyclerViewTrack implements LifecycleObserver {
private ItemExposeListener listener ;
private SparseLongArray timeSparseArray = new SparseLongArray(10);
RecyclerView recyclerView;
public RecyclerViewTrack(RecyclerView recyclerView) {
this.recyclerView = recyclerView;
}
public void startTrack(Lifecycle lifecycle, ItemExposeListener listener ){
this.listener = listener;
lifecycle.addObserver(this);
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
@Override
public void onScrollStateChanged(@NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
}
@Override
public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
try {
checkCurrentVisibleItem();
}catch (Exception e){
}
}
});
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
void onDestroy(LifecycleOwner owner) {
owner.getLifecycle().removeObserver(this);
}
/**
* 判断Item是否展示
*/
private void checkCurrentVisibleItem() {
List<Integer> range = new ArrayList(2);
RecyclerView.LayoutManager manager = recyclerView.getLayoutManager();
int orientation;
if (manager instanceof LinearLayoutManager){
range.add(0,((LinearLayoutManager) manager).findFirstVisibleItemPosition());
range.add(1, ((LinearLayoutManager) manager).findFirstVisibleItemPosition());
orientation = ((LinearLayoutManager) manager).getOrientation();
}else {
return;
}
for (int i=range.get(0);i<=range.get(1);i++ ) {
View view = manager.findViewByPosition(i);
dispatchViewVisible(view, i, orientation);
}
}
/**
* 判断View的可见性并进行分发
*/
private void dispatchViewVisible(View view, int position,int orientation) {
Rect rect = new Rect();
boolean rootVisible = view.getGlobalVisibleRect(rect);
//判断若超出了一半位置则算曝光
boolean visibleHeightEnough =
orientation == OrientationHelper.VERTICAL && rect.height() >= view.getMeasuredHeight() / 10;
boolean visibleWidthEnough =
orientation == OrientationHelper.HORIZONTAL && rect.width() >= view.getMeasuredHeight() / 10;
//可见区域超过百分之五十
boolean visible = (visibleHeightEnough || visibleWidthEnough) && rootVisible;
long lastValue = timeSparseArray.get(position);
long curTime = System.currentTimeMillis();
// Log.i(TAG, "checkViewVisible: position = $position, visible = $visible, lastValue = $lastValue")
if (lastValue > 0) {
//从显示到不显示
if (!visible) {
dispatchInvisible(position, lastValue, curTime);
}
} else if (visible) {
//从不显示到显示
dispatchVisible(position, curTime);
}
}
/**
* 在Fragment走到Pause时onScroll不会被触发上报,所以需要手动触发
*/
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
private void dispatchPause() {
int size = timeSparseArray.size();
for (int i=0;i<timeSparseArray.size();i++) {
int key = timeSparseArray.keyAt(i);
long value = timeSparseArray.valueAt(i);
if (value > 0) {
//是可见状态,则改为不可见状态
dispatchInvisible(key, value, System.currentTimeMillis());
} else {
//不是可见状态直接移除
timeSparseArray.delete(key);
i--;
}
}
}
public void onHiddenChanged(boolean hidden){
if (hidden){
dispatchPause();
}else {
dispatchResume();
}
}
/**
* 在Fragment在后续走到Resume时onScroll不会被触发,所以需要手动触发
*/
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
private void dispatchResume() {
int size = timeSparseArray.size();
long curTime = System.currentTimeMillis();
for (int i=0; i<size;i++) {
int key = timeSparseArray.keyAt(i);
dispatchVisible(key, curTime);
}
}
/**
* 分发InVisible
*/
private void dispatchInvisible(
int position,
long lastTime,
long curTime) {
if (lastTime == curTime) {
return;
}
LogUtil.d("RecyclerViewTrack:"+position+":-1");
timeSparseArray.put(position, -1);
if (listener!=null)
listener.onItemViewInvisible(position, curTime - lastTime);
}
/**
* 分发Visible
*/
private void dispatchVisible(int position, long curTime) {
// Log.i(TAG, "dispatchVisible: position = $position")
timeSparseArray.put(position, curTime);
LogUtil.d("RecyclerViewTrack:"+position+":"+curTime);
if (listener!=null)
listener.onItemViewVisible(position);
}
}