Android 曝光采集,商品view曝光量的統計,判斷RecyclerView中某子view是否可見 view是否顯示在屏幕中 view展示次數統計 判斷View是否顯示在界面上

近期pm提出需要統計首頁商品的曝光亮,由於我們的首頁是用的recylerview實現的,這裏就來講下如何使用監聽recylerview的滾動事件來實現子view的曝光量統計,我們這裏說的view都是列表中的子item條目(子view)

先來看下統計結果圖


左邊是我們的列表,右邊是我們統計到每個條目的曝光量。下面就來講講具體實現步驟。

一,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


那麼接下來的重點就是要去獲取屏幕內可見條目的起始位置。獲取到起始位置後,當前屏幕裏的可見條目就都能拿到了。
而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;
    }
   Rect rect = new Rect();
   boolean cover = view.getGlobalVisibleRect(rect);
        if (!cover) {
            return;
        }
       
        if (rect.height() < view.getMeasuredHeight() / 2) {
            //大於一半被覆蓋,就不統計
            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進去的


到這裏我們就實現了recylerview列表中view控件曝光量的統計了。下面貼出來完整的代碼給大家

package com.example.qcl.demo.xuexi.baoguang;

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 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;
        }

        Rect rect = new Rect();
        boolean cover = view.getGlobalVisibleRect(rect);
        if (!cover) {
            return;
        }
        if (rect.height() < view.getMeasuredHeight() / 2) {
            //大於一半被覆蓋,就不統計
            return;
        }
        //        if (rect.width() >= view.getMeasuredWidth() && rect.height() >= view.getMeasuredHeight()) {
        //            //沒有被覆蓋
        //        }

        //這裏獲取的是我們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傳遞進去就可以了。如下圖:


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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章