水平竖直容器曝光实现

最近做广告相关的曝光,分别针对listView里的item和HorizontalScrollView里的每个view,曝光的主要定义是由不可见到可见时算一次曝光,快速滑动的时候也算。

ListView曝光实现

不放在getView()里来实现主要是因为我们的下拉刷新组件会不断的设置自己的layoutParams,导致整个View树进行measure、layout、draw,getView会多次调用,不能实现准确曝光,所以需要自己实现一个。因为快速滚动也算,所以只能放在了listView的OnScrollListener的onScroll()里做了,可以用onScroll()里的firstVisiableItemvisiableItemCount参数来实现,我们每次可以获取屏幕显示的开始和结束item的位置,然后针对位置坐相关的处理。比如:如果一开始曝光了0-5,用户滑动,滚到1-6,这个时候我们可以知道6是新曝光的。但是要考虑到onScroll里也算上了header和footer,所以要针对这两个情况做一个映射。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
/**
* 滚动的统计曝光逻辑,listView的Adapater必须继承自BaseListAdapter
* Created by kevin on 16/9/1.
*/
public abstract class ScrollExposedHelper<T> {
private int lastStart = -1;
private int lastEnd = -1;
protected ListView mListView;
protected BaseListAdapter<T> mAdapterList;
public ScrollExposedHelper(BaseListAdapter adapterPoiList, ListView listPoiList) {
mAdapterList = adapterPoiList;
mListView = listPoiList;
}
/**
* 统计逻辑:假设第一次曝光1-3 第二次 2-4 则曝光第4个,所以上传取两个区间交集之外的一部分
* 注意header和footer的处理
* @param firstVisiableItemIndex
* @param visiableCount
*/
public void expose(int firstVisiableItemIndex, int visiableCount) {
if (visiableCount == 0 || mAdapterList == null
|| mAdapterList.getData() == null || mAdapterList.getData().size() <= 0 || mListView == null) {
return;
}
int headerCount = mListView.getHeaderViewsCount();
int start;
int end;
//header
if (firstVisiableItemIndex < headerCount) {
start = 0;
end = firstVisiableItemIndex + visiableCount - 1 - headerCount;
if(end < 0) {
return;
}
} else {
start = firstVisiableItemIndex - headerCount;
end = firstVisiableItemIndex + visiableCount - 1 - headerCount;
}
//footer
if(end > mAdapterList.getData().size() - 1) {
end = mAdapterList.getData().size() - 1;
}
// Log.e("CPC: ", "start: " + start + "end: " + end);
if (lastEnd < start || lastStart > end) {
show(start, end);
} else if (start < lastEnd && end > lastEnd) {
show(lastEnd + 1, end);
} else if (end > lastStart && start < lastStart) {
show(start, lastStart - 1);
} else if (start == lastStart && end > lastEnd) {
show(lastStart + 1, end);
} else if (end == lastEnd && start < lastStart) {
show(start, lastStart - 1);
}
lastStart = start;
lastEnd = end;
}
public void setInit() {
lastStart = -1;
lastEnd = -1;
}
protected abstract void show(int start, int end);
public abstract void click(int position);
}

HorizontalScrollView

水平滑动容器的曝光实现思路类似,只不过需要自己获取屏幕内显示了哪几个View,可以把整个容器想象成一个滑动的窗口,而容器内的View依次排列,容器在View上进行滚动。具体实现思路是,在进行动态添加View时对View进行测量,依次获取View的开始位置,下一个View的开始就是前一个View的结束(这里暂时不考虑view之间的padding)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
/**
* 用于水平容器的曝光
* Created by kevin on 16/11/30.
*/
public abstract class ExposeHorizontalView extends HorizontalScrollView {
protected LinearLayout mContainer;
protected List<Integer> mViewWidthList = new ArrayList();
protected int currentViewWidth;
protected int start;
protected int end;
protected int lastStart = -1;
protected int lastEnd = -1;
private boolean isExposed = false;
public ExposeHorizontalView(Context context) {
super(context);
init();
}
public ExposeHorizontalView(Context context, AttributeSet attrs) {
super(context, attrs);
init();
}
public ExposeHorizontalView(Context context, AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
init();
}
private void init() {
mContainer = new LinearLayout(getContext());
mContainer.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
mContainer.setOrientation(LinearLayout.HORIZONTAL);
this.addView(mContainer);
}
protected void initExposedView(View view) {
view.measure(0, 0);
mViewWidthList.add(currentViewWidth);
currentViewWidth += view.getMeasuredWidth();
}
@Override
protected void onScrollChanged(int l, int t, int oldl, int oldt) {
super.onScrollChanged(l, t, oldl, oldt);
// Log.e("horizontalView", "scrollChanged:" + l + "");
// Log.e("horizontalView", "linearlayout" + this.getMeasuredWidth() + "");
if(mViewWidthList.size() > 0) {
calculate(l, this.getMeasuredWidth() + l);
}
}
private void calculate(int left, int right) {
for(int i = 0; i < mViewWidthList.size(); i++) {
int viewStart = mViewWidthList.get(i);
int viewEnd = currentViewWidth;
if(i < mViewWidthList.size() - 1) {
viewEnd = mViewWidthList.get(i + 1);
}
if(left >= viewStart && left < viewEnd) {
start = i;
}
if(right > viewStart && right <= viewEnd) {
end = i;
}
}
if (lastEnd < start || lastStart > end) {
expose(start, end);
} else if (start < lastEnd && end > lastEnd) {
expose(lastEnd + 1, end);
} else if (end > lastStart && start < lastStart) {
expose(start, lastStart - 1);
} else if (start == lastStart && end > lastEnd) {
expose(lastStart + 1, end);
} else if (end == lastEnd && start < lastStart) {
expose(start, lastStart - 1);
}
lastStart = start;
lastEnd = end;
}
protected abstract void expose(int start, int end);
public void clearExposeData() {
lastStart = -1;
lastEnd = -1;
}
/**
* 在一开始时horizontalView不会调用onScrollChanged(),需要手动调用
*/
public void exposeManu() {
clearExposeData();
calculate(getScrollX(), getScrollX() + getMeasuredWidth());
}
public void notifyScroll(int scrollState) {
if (scrollState == AbsListView.OnScrollListener.SCROLL_STATE_IDLE) {
if (mContainer != null) {
// 判断view先是区域和屏幕区域是否有交集 有交集则说明需要曝光
boolean isIntersect = ViewUtils.isInScreen(this, getContext());
if (isIntersect) {
if(!isExposed) {
exposeManu();
}
isExposed = true;
} else {
isExposed = false;
}
}
}
}
}