Reputation: 51
I have a ListView and an ArrayAdapter < CustomListViewItem > set as its adapter. I would like to track number of impressions of each CustomListViewItem item.
Whenever a ListView item comes into view that should count as one impression. When it goes out of view and then again comes into view the impression count increments. When user scrolls, some items will go out of the view and some will come into view. I want to track the items which both come into view and move out of view.
What i tried:
One thing i tried was to setOnScrollListener for the list view and do tracking in the onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) callback.
I can use firstVisibleItem and visibleItemCount to do the job. In the documentation it says that this is called when the scroll has completed. But it seems to be getting called a lot of times when the scrolling is slow. Probably after each pixel of scroll. This is making the scroll very laggy.
Is there any other way to do this? By using some kind of callback which gets called for each item when it goes out of view or comes into view?
I searched a lot on web and didn't see any posts related to such kind of tracking.
Upvotes: 0
Views: 1166
Reputation: 68
Define a callback in your adapter
var onAttachView: ((position: Int) -> Unit)? = null
and call it in onViewAttachedToWindow method of adapter
override fun onViewAttachedToWindow(holder: ViewHolder) {
super.onViewAttachedToWindow(holder)
onAttachView?.invoke(holder.bindingAdapterPosition)
}
if you want just send one time of impression of item in list, you can save reported position temporary in your view model or analytics repository then base on your session strategy to clear it.
Upvotes: 0
Reputation: 51
I couldn't find another way to do this. So, i optimized what i was already doing. I thought i should share this as it may help someone else.
I use firstVisibleItem and visibleItemCount of onScroll() callback only. I use two variables to keep information of previous onScroll() callback. They are :
prevVisibleStart and prevVisibleEnd
Lets take an example to understand the algorithm. Assume initially that after first onScroll()
firstVisibleItem = 0 and visibleItemCount = 3 => lastVisibleItem = 2
These are used to set my previous visible states.
So, prevVisibleStart = firstVisibleItem = 3 and prevVisibleEnd = firstVisibleItem + visibleItemCount - 1 = 2
Now if i scroll down, i will always have
firstVisibleItem >= prevVisibleStart and lastVisibleItem >= prevVisibleEnd
So, if the previous range was 0 to 2. The new range would be somewhat like 1 to 3. Since, i have already considered 0 to 2 for impression. I will only consider 3 as new impression. The reason being the views 1, 2 are still in view and they hadn't gone out of view. So, a new impression is not counted for these. This effectively gives the correct range to consider for impressions as well as reduces the range for checking as well.
Similarly, this can be done for scrolling up. Assume that the current visible items are 2-4. Now after scroling up the range becomes (say)1-3. This means that 1 has come into view. So, count an impression for this view. This way the number of operations in onScroll() significantly reduce.
Sample code follows
range [i-j) is considered for impression.
private static int prevVisibleStart=-1;
private static int prevVisibleEnd=-1;
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(prevVisibleStart <= firstVisibleItem) {
// scroll down
i = Math.max(firstVisibleItem, prevVisibleEnd + 1);
j = firstVisibleItem + visibleItemCount;
} else {
// scroll up
i = firstVisibleItem;
j = Math.min(prevVisibleStart, firstVisibleItem + visibleItemCount);
}
prevVisibleStart = firstVisibleItem;
prevVisibleEnd = firstVisibleItem + visibleItemCount - 1;
// now can use [i-j) to update impressions
}
Upvotes: 2