Android 曝光采集,商品view曝光量的統計,判斷RecyclerView中某子view是否可見 view是否顯示在屏...
近期pm提出需要統計首頁商品的曝光亮,由於我們的首頁是用的recylerview實現的,這裡就來講下如何使用監聽recylerview的滾動事件來實現子view的曝光量統計,我們這裡說的view都是列表中的子item條目(子view)
先來看下統計結果圖

image.png
左邊是我們的列表,右邊是我們統計到每個條目的曝光量。下面就來講講具體實現步驟。
一,activity中使用recylerview並顯示資料
這裡我不再囉嗦,recylerview最基礎的使用。
二,監聽recylerview的滾動事件OnScrollListener
onScrollStateChanged:監聽滾動狀態
onScrolled:監聽滾動
我們接下來的統計工作,就是拿這兩個方法做文章。
//檢測recylerview的滾動事件 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { /* 我這裡通過的是停止滾動後螢幕上可見view。如果滾動過程中的可見view也要統計,你可以根據newState去做區分 SCROLL_STATE_IDLE:停止滾動 SCROLL_STATE_DRAGGING: 使用者慢慢拖動 SCROLL_STATE_SETTLING:慣性滾動 */ if (newState == RecyclerView.SCROLL_STATE_IDLE) { ..... } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); ........ } });
首先再次明確下,我們要統計的是使用者停止滑動時,顯示在螢幕的上控制元件。所以我們要監測到onScrollStateChanged 方法中
newState == RecyclerView.SCROLL_STATE_IDLE 時,也就是使用者停止滾動。然後在這裡做文章。
三,獲取螢幕內可見條目的起始位置
這裡的起始位置就是指我們螢幕當中最上面和最下面條目的位置。比如下圖的0就是最上面的可見條目,3就是最下面的可見條目。我們次數的曝光view就是0,1,2,3 這個時候這四個條目顯示在螢幕中。我們這時就要對這4個view的曝光量進行加1

image.png
那麼接下來的重點就是要去獲取螢幕內可見條目的起始位置。獲取到起始位置後,當前螢幕裡的可見條目就都能拿到了。
而recylerview的manager正好給我們提供的有對應的方法。
findFirstVisibleItemPosition()和findLastVisibleItemPosition()看字面意思就能知道這時幹嘛用的。
但是我們的manager不止LinearLayoutManager一種,所以我們要做下區分,
//這裡我們用一個數組來記錄起始位置 int[] range = new int[2]; RecyclerView.LayoutManager manager = reView.getLayoutManager(); if (manager instanceof LinearLayoutManager) { range = findRangeLinear((LinearLayoutManager) manager); } else if (manager instanceof GridLayoutManager) { range = findRangeGrid((GridLayoutManager) manager); } else if (manager instanceof StaggeredGridLayoutManager) { range = findRangeStaggeredGrid((StaggeredGridLayoutManager) manager); }
LinearLayoutManager和GridLayoutManager獲取起始位置方法如下
private int[] findRangeLinear(LinearLayoutManager manager) { int[] range = new int[2]; range[0] = manager.findFirstVisibleItemPosition(); range[1] = manager.findLastVisibleItemPosition(); return range; } private int[] findRangeGrid(GridLayoutManager manager) { int[] range = new int[2]; range[0] = manager.findFirstVisibleItemPosition(); range[1] = manager.findLastVisibleItemPosition(); return range; }
StaggeredGridLayoutManager獲取起始位置有點複雜,如下
private int[] findRangeStaggeredGrid(StaggeredGridLayoutManager manager) { int[] startPos = new int[manager.getSpanCount()]; int[] endPos = new int[manager.getSpanCount()]; manager.findFirstVisibleItemPositions(startPos); manager.findLastVisibleItemPositions(endPos); int[] range = findRange(startPos, endPos); return range; } private int[] findRange(int[] startPos, int[] endPos) { int start = startPos[0]; int end = endPos[0]; for (int i = 1; i < startPos.length; i++) { if (start > startPos[i]) { start = startPos[i]; } } for (int i = 1; i < endPos.length; i++) { if (end < endPos[i]) { end = endPos[i]; } } int[] res = new int[]{start, end}; return res; }
四,獲取到起始位置以後,我們就根據位置獲取到view及view中的資料
上面第三步拿到螢幕內可見條目的起始位置以後,我們就用一個for迴圈,獲取當前螢幕內可見的所有子view
for (int i = range[0]; i <= range[1]; i++) { View view = manager.findViewByPosition(i); recordViewCount(view); }
recordViewCount是我自己寫的用於獲取子view內繫結資料的方法
//獲取view繫結的資料 private void recordViewCount(View view) { if (view == null || view.getVisibility() != View.VISIBLE || !view.isShown() || !view.getGlobalVisibleRect(new Rect())) { return; } int top = view.getTop(); int halfHeight = view.getHeight() / 2; int screenHeight = UiUtils.getScreenHeight((Activity) view.getContext()); int statusBarHeight = UiUtils.getStatusBarHeight(view.getContext()); if (top < 0 && Math.abs(top) > halfHeight) { return; } if (top > screenHeight - halfHeight - statusBarHeight) { return; } //這裡獲取的是我們view繫結的資料,相應的你要去在你的view裡setTag,只有set了,才能get ItemData tag = (ItemData) view.getTag(); String key = tag.toString(); if (TextUtils.isEmpty(key)) { return; } hashMap.put(key, !hashMap.containsKey(key) ? 1 : (hashMap.get(key) + 1)); Log.i("qcl0402", key + "----出現次數:" + hashMap.get(key)); }
這裡有幾點需要注意
- 1,這這裡起始位置的view顯示區域如果不超過50%,就不算這個view可見,進而也就不統計曝光。
-
2,我們通過view.getTag();獲取view裡的資料,必須在此之前setTag()資料,我這裡setTag是在viewholder中把資料set進去的
image.png
到這裡我們就實現了recylerview列表中view控制元件曝光量的統計了。下面貼出來完整的程式碼給大家
package com.example.qcl.demo.xuexi.baoguang; import android.app.Activity; import android.graphics.Rect; import android.support.v7.widget.GridLayoutManager; import android.support.v7.widget.LinearLayoutManager; import android.support.v7.widget.RecyclerView; import android.support.v7.widget.StaggeredGridLayoutManager; import android.text.TextUtils; import android.util.Log; import android.view.View; import com.example.qcl.demo.utils.UiUtils; import java.util.concurrent.ConcurrentHashMap; /** * 2019/4/2 13:31 * author: qcl * desc: 安卓曝光量統計工具類 * wechat:2501902696 */ public class ViewShowCountUtils { //剛進入列表時統計當前螢幕可見views private boolean isFirstVisible = true; //用於統計曝光量的map private ConcurrentHashMap<String, Integer> hashMap = new ConcurrentHashMap<String, Integer>(); /* * 統計RecyclerView裡當前螢幕可見子view的曝光量 * * */ void recordViewShowCount(RecyclerView recyclerView) { hashMap.clear(); if (recyclerView == null || recyclerView.getVisibility() != View.VISIBLE) { return; } //檢測recylerview的滾動事件 recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrollStateChanged(RecyclerView recyclerView, int newState) { /* 我這裡通過的是停止滾動後螢幕上可見view。如果滾動過程中的可見view也要統計,你可以根據newState去做區分 SCROLL_STATE_IDLE:停止滾動 SCROLL_STATE_DRAGGING: 使用者慢慢拖動 SCROLL_STATE_SETTLING:慣性滾動 */ if (newState == RecyclerView.SCROLL_STATE_IDLE) { getVisibleViews(recyclerView); } } @Override public void onScrolled(RecyclerView recyclerView, int dx, int dy) { super.onScrolled(recyclerView, dx, dy); //剛進入列表時統計當前螢幕可見views if (isFirstVisible) { getVisibleViews(recyclerView); isFirstVisible = false; } } }); } /* * 獲取當前螢幕上可見的view * */ private void getVisibleViews(RecyclerView reView) { if (reView == null || reView.getVisibility() != View.VISIBLE || !reView.isShown() || !reView.getGlobalVisibleRect(new Rect())) { return; } //保險起見,為了不讓統計影響正常業務,這裡做下try-catch try { int[] range = new int[2]; RecyclerView.LayoutManager manager = reView.getLayoutManager(); if (manager instanceof LinearLayoutManager) { range = findRangeLinear((LinearLayoutManager) manager); } else if (manager instanceof GridLayoutManager) { range = findRangeGrid((GridLayoutManager) manager); } else if (manager instanceof StaggeredGridLayoutManager) { range = findRangeStaggeredGrid((StaggeredGridLayoutManager) manager); } if (range == null || range.length < 2) { return; } Log.i("qcl0402", "螢幕內可見條目的起始位置:" + range[0] + "---" + range[1]); for (int i = range[0]; i <= range[1]; i++) { View view = manager.findViewByPosition(i); recordViewCount(view); } } catch (Exception e) { e.printStackTrace(); } } //獲取view繫結的資料 private void recordViewCount(View view) { if (view == null || view.getVisibility() != View.VISIBLE || !view.isShown() || !view.getGlobalVisibleRect(new Rect())) { return; } int top = view.getTop(); int halfHeight = view.getHeight() / 2; int screenHeight = UiUtils.getScreenHeight((Activity) view.getContext()); int statusBarHeight = UiUtils.getStatusBarHeight(view.getContext()); if (top < 0 && Math.abs(top) > halfHeight) { return; } if (top > screenHeight - halfHeight - statusBarHeight) { return; } //這裡獲取的是我們view繫結的資料,相應的你要去在你的view裡setTag,只有set了,才能get ItemData tag = (ItemData) view.getTag(); String key = tag.toString(); if (TextUtils.isEmpty(key)) { return; } hashMap.put(key, !hashMap.containsKey(key) ? 1 : (hashMap.get(key) + 1)); Log.i("qcl0402", key + "----出現次數:" + hashMap.get(key)); } private int[] findRangeLinear(LinearLayoutManager manager) { int[] range = new int[2]; range[0] = manager.findFirstVisibleItemPosition(); range[1] = manager.findLastVisibleItemPosition(); return range; } private int[] findRangeGrid(GridLayoutManager manager) { int[] range = new int[2]; range[0] = manager.findFirstVisibleItemPosition(); range[1] = manager.findLastVisibleItemPosition(); return range; } private int[] findRangeStaggeredGrid(StaggeredGridLayoutManager manager) { int[] startPos = new int[manager.getSpanCount()]; int[] endPos = new int[manager.getSpanCount()]; manager.findFirstVisibleItemPositions(startPos); manager.findLastVisibleItemPositions(endPos); int[] range = findRange(startPos, endPos); return range; } private int[] findRange(int[] startPos, int[] endPos) { int start = startPos[0]; int end = endPos[0]; for (int i = 1; i < startPos.length; i++) { if (start > startPos[i]) { start = startPos[i]; } } for (int i = 1; i < endPos.length; i++) { if (end < endPos[i]) { end = endPos[i]; } } int[] res = new int[]{start, end}; return res; } }
使用就是在我們的recylerview設定完資料以後,把recylerview傳遞進去就可以了。如下圖:

image.png
我們統計到曝光量,拿到曝光view繫結的資料,就可以結合後面的view點選,來看下那些商品view的曝光量高,那些商品的轉化率高。當然,這都是運營小夥伴的事了,我們只需要負責把曝光量統計到即可。
如果你有任何程式設計方面的問題,可以加我微信交流 2501902696 (備註程式設計)