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.
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
}
Related
I'm making an app and I'm using grid view to show all of my images, the problem is I have lots of images, about 12,000, and I don't want to load all of them at the start, because lets face it, it will take forever, so I was wondering what is the best way of accessing the server to fetch more items once the gridview has reached the end.
Thanks.
To achieve load more items on scrolled to end of the gridview
gridView.setOnScrollListener(new AbsListView.OnScrollListener(){
#Override
public void onScroll(AbsListView view,
int firstVisibleItem, int visibleItemCount,
int totalItemCount) {
//Algorithm to check if the last item is visible or not
final int lastItem = firstVisibleItem + visibleItemCount;
if(lastItem == totalItemCount){
// here you have reached end of list, load more data
fetchMoreItems();
}
}
#Override
public void onScrollStateChanged(AbsListView view,int scrollState) {
//blank, not required in your case
}
});
One way is to start the server request for an image in the adapter getView(). When the request completes, you set the retrieved bitmap on the ImageView. There is a very in-depth description of this technique on the Android Developer Blog: Multithreading For Performance | Android Developers Blog. Most servers are fast enough that the lag time to display the image is not objectionable.
However, if you want to load images before the ImageViews are displayed, here's another way you can do it with an OnScrollListener, an AsyncTask, and a list adapter for the `GridView.
Say you have a constant final int THRESHOLD = 50; and a variable lastItem that starts at zero.
When user displays your GridView the AsyncTask kicks off and retrieves the first 100 images. In the onPostExecute(), it adds the images to your adapter and calls notifyDataSetChanged(). It also adds 100 to lastItem.
So now your adapter has 100 images. The user is scrolling through images and the OnScrollListener gets called.
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
if (firstVisibleItem >= lastItem - THRESHOLD) {
new FetchImageAsyncTask().execute();
}
}
Your AsyncTask executes, lastItem is updated to 200, and your adapter gets 100 more images.
Then the user scrolls some more and it starts all over again.
By using the threshold, the user can scroll another 50 images before the end of the list, and that may be enough time to load the next 100 images.
I have gridview listing 9 items per page, and when user scrolls it should load 9 more. But i cannot set it properly, i am struggling with this code:
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if((firstVisibleItem + visibleItemCount) == totalItemCount)
{
load_more();
}
}
});
I am not sure what to put on calculation line, so to give me 9 by 9 as user scrolls. I tried different calculations, but without success, sometimes it just loads automatically couple of times, sometimes infinite. How to accomplish what I need?
There is a nice library which exactly does what you need.
https://github.com/shontauro/android-pulltorefresh-and-loadmore
Sample
((LoadMoreListView) getListView())
.setOnLoadMoreListener(new OnLoadMoreListener() {
public void onLoadMore() {
// Do the work to load more items at the end of list here
}
});
There are more options available in the library, you can try that as well.
For actually implementing for yourself, its quite difficult to do on onScroll. I too gave up and implemented through different procedure.
TL;DR - ViewPager as ListView Header creating some issues.
my activity has a ListView that presents several types of data..
I have a HeaderView, a sticky view, and the rest of the data is a "normal" list item.
I am using this Library -> https://github.com/LarsWerkman/QuickReturnListView
for my list view.
In my HeaderView i have a view pager for 2 profile images of my users.
2 problems :
scrolling the pager right to the next image doesn't open the image to the full screen width..
(but if scrolling down in the list view, scrolling back up to the top of the screen "redraw" the header view and then if you scroll the pager again it's fixed!)
sometimes the images do not load to my view pager.
you can see that bug in those images, first image is of the state from trying to scroll the pager to the right, and the second image is the bug
image one
image two
anyone encountered such a problem before?
I've read in some places that using a view pager inside a list view is not exactly optimal, is there a different way to achieve my goal?
I had that same problem,
try removing that third party library of the list view,
back in the day i was using that same one and it's really buggy.
use a normal listview instead.
A few months ago i did a similar trick as you want to do. I tried almost all libraries in web about this quick return and all of them has bugs and not suitable for me. Also not suitable for a header which contains a viewpager in.
Later i implemented my own scroll listener. This is not exactly a quick return header pattern but you can add animations in if you have time.
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
#SuppressLint("NewApi")
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
int scrollOffset = 0;
float transitionY;
if (firstVisibleItem > 0) {
scrollOffset += headerHeight;
if (firstVisibleItem > 1) {
scrollOffset += (firstVisibleItem - 1) * cellHeight;
}
}
if (listView.getChildCount() > 0) {
scrollOffset += -listView.getChildAt(0).getTop();
scrollOffset = -scrollOffset;
}
float scrollDelta = scrollOffset - prevOffset;
float nextY = mQuickReturnView.getY() + scrollDelta;
if (nextY < minRawY) {
transitionY = minRawY;
}
else if (nextY > qReturnDelta) {
transitionY = qReturnDelta;
}
else {
transitionY = nextY;
}
mQuickReturnView.setY(transitionY);
prevOffset = scrollOffset;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
});
And i used a view pager and a pageradapter in header. In my solution you have to use fixed size for listview items and declare it as CellHeight and a fixed size for header. It's QuickReturnHeight
It's a bit hard to implement this pattern with ListView + Header + ViewPager.
I hope this'll help you.
I want to find out the position or ids related to a ListView's items: only those ones which are completely visible on the screen.
Using listview.getFirstVisibleposition and listview.getLastVisibleposition takes partial list items into account.
I followed a little bit similar approach as suggested by Rich, to suit my requirement which was to fetch completely visible items on screen when List View is scrolled every time.
This is what i did
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
//Loop to get tids of all completely visible List View's item scrolled on screen
for (int listItemIndex = 0; listItemIndex <= getListView().getLastVisiblePosition() - getListView().getFirstVisiblePosition(); listItemIndex++) {
View listItem = getListView().getChildAt(listItemIndex);
TextView tvNewPostLabel = (TextView) listItem.findViewById(R.id.tvNewPostLabel);
if (tvNewPostLabel != null && tvNewPostLabel.getVisibility() == View.VISIBLE) {
int listTid = (int) tvNewPostLabel.getTag();
if (listItem.getBottom() < getListView().getHeight()) {//If List View's item is not partially visible
listItemTids.add(listTid);
}
}
}
}
I have not tried this, but here are the pieces of the framework that I believe will get you to what you're looking for (at least this is what I'd try first)
As you've stated, you should get the last visible position from the list view using ListView.getLastVisiblePosition()
You can then access the View representing this position using ListView.getChildAt(position)
You now have a reference to the view, which you can call a combination of View.getLocationOnScreen(location) and View.getHeight()
Also call View.getLocationOnScreen(location) and View.getHeight() on the ListView. y + height of the View should be less than or equal to y + height of the ListView if it is fully visible.
just wanted to ask is there a possibility to bind some listener to the ListView scroll event.
What I would like to achieve is that once ListView have been scrolled to the bottom I would like to ask server for more data if any is there.
I know that there is a function like: getLastVisiblePosition() but I assume it must bu used in some kind of listener in order to compare with currently last visible position, am I right ?
cheers,
/Marcin
Just to extrapolate on TomTo's answer, some thoughts on how you might do this (theoretical, I haven't tried this in practice):
ListView lv = (ListView)findViewById(R.id.list_view);
lv.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//Check if the last view is visible
if (++firstVisibleItem + visibleItemCount > totalItemCount) {
//load more content
}
}
});
Since firstVisibleItem is returns an index (0-based), I increment by one to get the actual item number. Then, if that, plus the number of visible items, is greater than the total number of items, then in theory the last view should be visible. Shouldn't be a problem, but keep in mind this isn't called till the scrolling is finished.
used
if (++visibleItemCount > totalItemCount)
instead
if (++firstVisibleItem + visibleItemCount > totalItemCount)
AbsListView.OnScrollListener ?