How can I create a list where when you reach the end of the list I am notified so I can load more items?
One solution is to implement an OnScrollListener and make changes (like adding items, etc.) to the ListAdapter at a convenient state in its onScroll method.
The following ListActivity shows a list of integers, starting with 40, adding items when the user scrolls to the end of the list.
public class Test extends ListActivity implements OnScrollListener {
Aleph0 adapter = new Aleph0();
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setListAdapter(adapter);
getListView().setOnScrollListener(this);
}
public void onScroll(AbsListView view,
int firstVisible, int visibleCount, int totalCount) {
boolean loadMore = /* maybe add a padding */
firstVisible + visibleCount >= totalCount;
if(loadMore) {
adapter.count += visibleCount; // or any other amount
adapter.notifyDataSetChanged();
}
}
public void onScrollStateChanged(AbsListView v, int s) { }
class Aleph0 extends BaseAdapter {
int count = 40; /* starting amount */
public int getCount() { return count; }
public Object getItem(int pos) { return pos; }
public long getItemId(int pos) { return pos; }
public View getView(int pos, View v, ViewGroup p) {
TextView view = new TextView(Test.this);
view.setText("entry " + pos);
return view;
}
}
}
You should obviously use separate threads for long running actions (like loading web-data) and might want to indicate progress in the last list item (like the market or gmail apps do).
Just wanted to contribute a solution that I used for my app.
It is also based on the OnScrollListener interface, but I found it to have a much better scrolling performance on low-end devices, since none of the visible/total count calculations are carried out during the scroll operations.
Let your ListFragment or ListActivity implement OnScrollListener
Add the following methods to that class:
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//leave this empty
}
#Override
public void onScrollStateChanged(AbsListView listView, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (listView.getLastVisiblePosition() >= listView.getCount() - 1 - threshold) {
currentPage++;
//load more list items:
loadElements(currentPage);
}
}
}
where currentPage is the page of your datasource that should be added to your list, and threshold is the number of list items (counted from the end) that should, if visible, trigger the loading process. If you set threshold to 0, for instance, the user has to scroll to the very end of the list in order to load more items.
(optional) As you can see, the "load-more check" is only called when the user stops scrolling. To improve usability, you may inflate and add a loading indicator to the end of the list via listView.addFooterView(yourFooterView). One example for such a footer view:
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="#+id/footer_layout"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:padding="10dp" >
<ProgressBar
android:id="#+id/progressBar1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_gravity="center_vertical" />
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerVertical="true"
android:layout_toRightOf="#+id/progressBar1"
android:padding="5dp"
android:text="#string/loading_text" />
</RelativeLayout>
(optional) Finally, remove that loading indicator by calling listView.removeFooterView(yourFooterView) if there are no more items or pages.
You can detect end of the list with help of onScrollListener, working code is presented below:
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (view.getAdapter() != null && ((firstVisibleItem + visibleItemCount) >= totalItemCount) && totalItemCount != mPrevTotalItemCount) {
Log.v(TAG, "onListEnd, extending list");
mPrevTotalItemCount = totalItemCount;
mAdapter.addMoreData();
}
}
Another way to do that (inside adapter) is as following:
public View getView(int pos, View v, ViewGroup p) {
if(pos==getCount()-1){
addMoreData(); //should be asynctask or thread
}
return view;
}
Be aware that this method will be called many times, so you need to add another condition to block multiple calls of addMoreData().
When you add all elements to the list, please call notifyDataSetChanged() inside yours adapter to update the View (it should be run on UI thread - runOnUiThread)
At Ognyan Bankov GitHub i found a simple and working solution!
It makes use of the Volley HTTP library that makes networking for Android apps easier and most importantly, faster. Volley is available through the open AOSP repository.
The given code demonstrates:
ListView which is populated by HTTP paginated requests.
Usage of NetworkImageView.
"Endless" ListView pagination with read-ahead.
For future consistence i forked Bankov's repo.
Here is a solution that also makes it easy to show a loading view in the end of the ListView while it's loading.
You can see the classes here:
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java
- Helper to make it possible to use the features without extending from SimpleListViewWithLoadingIndicator.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java
- Listener that starts loading data when the user is about to reach the bottom of the ListView.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java
- The EndlessListView. You can use this class directly or extend from it.
May be a little late but the following solution happened very useful in my case.
In a way all you need to do is add to your ListView a Footer and create for it addOnLayoutChangeListener.
http://developer.android.com/reference/android/widget/ListView.html#addFooterView(android.view.View)
For example:
ListView listView1 = (ListView) v.findViewById(R.id.dialogsList); // Your listView
View loadMoreView = getActivity().getLayoutInflater().inflate(R.layout.list_load_more, null); // Getting your layout of FooterView, which will always be at the bottom of your listview. E.g. you may place on it the ProgressBar or leave it empty-layout.
listView1.addFooterView(loadMoreView); // Adding your View to your listview
...
loadMoreView.addOnLayoutChangeListener(new View.OnLayoutChangeListener() {
#Override
public void onLayoutChange(View v, int left, int top, int right, int bottom, int oldLeft, int oldTop, int oldRight, int oldBottom) {
Log.d("Hey!", "Your list has reached bottom");
}
});
This event fires once when a footer becomes visible and works like a charm.
The key of this problem is to detect the load-more event, start an async request for data and then update the list. Also an adapter with loading indicator and other decorators is needed. In fact, the problem is very complicated in some corner cases. Just a OnScrollListener implementation is not enough, because sometimes the items do not fill the screen.
I have written a personal package which support endless list for RecyclerView, and also provide a async loader implementation AutoPagerFragment which makes it very easy to get data from a multi-page source. It can load any page you want into a RecyclerView on a custom event, not only the next page.
Here is the address: https://github.com/SphiaTower/AutoPagerRecyclerManager
Best solution so far that I have seen is in FastAdapter library for recycler views. It has a EndlessRecyclerOnScrollListener.
Here is an example usage: EndlessScrollListActivity
Once I used it for endless scrolling list I have realised that the setup is a very robust. I'd definitely recommend it.
I've been working in another solution very similar to that, but, I am using a footerView to give the possibility to the user download more elements clicking the footerView, I am using a "menu" which is shown above the ListView and in the bottom of the parent view, this "menu" hides the bottom of the ListView, so, when the listView is scrolling the menu disappear and when scroll state is idle, the menu appear again, but when the user scrolls to the end of the listView, I "ask" to know if the footerView is shown in that case, the menu doesn't appear and the user can see the footerView to load more content. Here the code:
Regards.
listView.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// TODO Auto-generated method stub
if(scrollState == SCROLL_STATE_IDLE) {
if(footerView.isShown()) {
bottomView.setVisibility(LinearLayout.INVISIBLE);
} else {
bottomView.setVisibility(LinearLayout.VISIBLE);
} else {
bottomView.setVisibility(LinearLayout.INVISIBLE);
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
});
I know its an old question and the Android world has mostly moved on to RecyclerViews, but for anyone interested, you may find this library very interesting.
It uses the BaseAdapter used with the ListView to detect when the list has been scrolled to the last item or when it is being scrolled away from the last item.
It comes with an example project(barely 100 lines of Activity code) that can be used to quickly understand how it works.
Simple usage:
class Boy{
private String name;
private double height;
private int age;
//Other code
}
An adapter to hold Boy objects would look like:
public class BoysAdapter extends EndlessAdapter<Boy>{
ViewHolder holder = null;
if (convertView == null) {
LayoutInflater inflater = LayoutInflater.from(parent
.getContext());
holder = new ViewHolder();
convertView = inflater.inflate(
R.layout.list_cell, parent, false);
holder.nameView = convertView.findViewById(R.id.cell);
// minimize the default image.
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Boy boy = getItem(position);
try {
holder.nameView.setText(boy.getName());
///Other data rendering codes.
} catch (Exception e) {
e.printStackTrace();
}
return super.getView(position,convertView,parent);
}
Notice how the BoysAdapter's getView method returns a call to the EndlessAdapter superclass's getView method. This is 100% essential.
Now to create the adapter, do:
adapter = new ModelAdapter() {
#Override
public void onScrollToBottom(int bottomIndex, boolean moreItemsCouldBeAvailable) {
if (moreItemsCouldBeAvailable) {
makeYourServerCallForMoreItems();
} else {
if (loadMore.getVisibility() != View.VISIBLE) {
loadMore.setVisibility(View.VISIBLE);
}
}
}
#Override
public void onScrollAwayFromBottom(int currentIndex) {
loadMore.setVisibility(View.GONE);
}
#Override
public void onFinishedLoading(boolean moreItemsReceived) {
if (!moreItemsReceived) {
loadMore.setVisibility(View.VISIBLE);
}
}
};
The loadMore item is a button or other ui element that may be clicked to fetch more data from the url.
When placed as described in the code, the adapter knows exactly when to show that button and when to disable it. Just create the button in your xml and place it as shown in the adapter code above.
Enjoy.
Related
I have a custom cursor adapter to populate a list view in my fragment.
I want to set the visibility of certain elements within the list view items, depending on whether the item is the first visible one in the list or not. Is it possible to get that info in the bindView() method of the Cursor adapter?
Adapter's purpose plan:
Create views
Attaching data to them
Return the view for the ListView to use.
Conclusion: Adapter doesn't know where the view it's creating will be shown.
However, the ListView does know about this and it's probably the only way you can get this working.
Example code to get you started:
listView.setOnScrollListener(new AbsListView.OnScrollListener() {
int previousFirst = -1;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if (previousFirst != firstVisibleItem) {
previousFirst = firstVisibleItem;
TextView textView = (TextView) view.findViewById(R.id.title);
if (textView != null) textView.setText("First: " + firstVisibleItem);
}
}
});
Problems with this code:
Once the first item changes, you need to set it's text back to the previous one.
Same goes with the view hierarchy. If you change how this view looks, after it's not the first one anymore you need to change it back.
ListView doesn't scroll upon creation. So the first item will not have the text changed until you scroll manually.
ListView doesn't include the options to customize the first visible item internally, that's why you have to use all these dirty hacks. However, it is possible :). I leave you to overcome these problems yourself.
Good luck!
This is for example as I haven't coded it yet, since I don't know how to start.
Let's say in Android I have String array with 100 values:
String[] myString = new String[100];
for (int number = 0; number < 100; number++) {
myString[number] = "image " + number;
}
Which way should I display for example 5 values per page (which view to use (table, grid) and should I use Fragment replace for each page).
I would like to achieve something like this, but in Android. I just need some guidelines to start.
In general if you have noticed, explicit pagination is not so often done in android, instead, infinite scrolling is used, for example your news feed in Facebook is a list, when you reach the bottom, it loads more and you can scroll more, at the bottom of that it loads again.
To implement this, it is quite easy, just set an onScrollListener and override onScrollStateChanged() method.
Set onScrollListener after initializing your ListView:
//In onCreate()
ListView listView = (ListView)findViewById(R.id.list_view);
listView.setOnScrollListener(new ListScrollListener());
Make your listener class:
private class ListScrollListener implements OnScrollListener{
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (listView.getLastVisiblePosition() >= listView.getCount()-1) {
page_num++;
//TODO Load more list items.
//notify the adapter of the listview that data has changed
mAdapter.notifyDataSetChanged();
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
//This can be left blank
}
}
If you want you can add a footer with a ProgressBar to your ListView to display when you are loading more items with listView.addFooterView(View v);
You can find a nice tutorial about a custom ListView at Vogella tutorials.
I am trying to do a endless listview with the Commonsware Endless Adapter (https://github.com/commonsguy/cwac-endless), which works fine by default.
What I am trying to do is a basic chat application.
New items should appear at the end and the chat history should be loadable by the endless adapter when the user scrolls to the top.
This by itself is not hard to do. If the ArrayAdapter contains the data s.t. newest items are at position 0, then simply using android:stackFromBottom in the XML declaration of the ListView will put the beginning of the list at the end.
To make sure that the 'load more' inicator is located at the top, I override getItem, getView etc. to provide it with the reversed positions (count-1-position).
My Problem: When the EndlessAdapter adds new data at the end of the List, the "load more" indicator remains visible, causing it to endlessly fetch more data until there is no more to fetch.
Expected is that it loads one batch and the user then needs to scroll down (here:up) to load further elements.
What am I missing?
Personally, I'd consider pull-to-refresh for this scenario, rather than EndlessAdapter.
That being said, if you are seeing additional rows appear, but the pending view is still there, then your modified getView() is not working properly. The additional rows appearing would indicated that notifyDataSetChanged() itself is functioning (otherwise, those rows would not show up). So, if the pending view is still shown, then getView() presumably is returning the pending view (position 0, I'd guess). In fact, I have no idea how you can get a reverse EndlessAdapter to work, as the first row should always be the pending view and should always be loading data, until you run out of data (and, in the case of a chat, that's possibly never the case).
Hence, again, I'd use pull-to-refresh, or you are going to have to switch to a different "endless" scheme that is paying attention to scroll events, rather than just waiting for the pending view to be displayed, as the trigger to fetch more data.
Do not care on notifyDataSetChanged() help.
I implemented an adapter to return Integer.MAX_VALUE as count of elements and especially calculate index in a cache.
Here is some snippet:
<ListView
android:id="#+id/logContainer"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:stackFromBottom="true" />
......
logContainer.setOnScrollListener(new OnScrollListener() {
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if ((logAdapter.getCachedCount() - (Integer.MAX_VALUE - firstVisibleItem)) <= LINES_TO_TRIGGER_PRELOAD && !logAdapter.isPreloading()) {
logAdapter.preloadAtBackground();
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {}
});
class LogAdapter extends BaseAdapter {
private ArrayList<String> logList = new ArrayList<String>();
#Override
public int getCount() {
return Integer.MAX_VALUE;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
TextView logLineView = new TextView(LogViewDialog.this);
int idx = logList.size() - (Integer.MAX_VALUE - position);
logLineView.setText(logList.get(idx));
return logLineView;
}
.........
Taking inspiration from Android Market, i have implemented a Endless List which loads more data from the server when we reach the end of the List.
Now, i need to implement the progressbar & "Loading.." text as shown
Sample code to take inspiration from would be great.
Here is a solution that also makes it easy to show a loading view in the end of the ListView while it's loading.
You can see the classes here:
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/helper/ListViewWithLoadingIndicatorHelper.java
- Helper to make it possible to use the features without extending from SimpleListViewWithLoadingIndicator.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/listener/EndlessScrollListener.java
- Listener that starts loading data when the user is about to reach the bottom of the ListView.
https://github.com/CyberEagle/OpenProjects/blob/master/android-projects/widgets/src/main/java/br/com/cybereagle/androidwidgets/view/SimpleListViewWithLoadingIndicator.java
- The EndlessListView. You can use this class directly or extend from it.
Add an onScrollListener to the ListView. When the user scrolls, check if the ListView is nearing its end. If yes, then fetch more data. As an example :
public abstract class LazyLoader implements AbsListView.OnScrollListener {
private static final int DEFAULT_THRESHOLD = 10 ;
private boolean loading = true ;
private int previousTotal = 0 ;
private int threshold = DEFAULT_THRESHOLD ;
public LazyLoader() {}
public LazyLoader(int threshold) {
this.threshold = threshold;
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
if(loading) {
if(totalItemCount > previousTotal) {
// the loading has finished
loading = false ;
previousTotal = totalItemCount ;
}
}
// check if the List needs more data
if(!loading && ((firstVisibleItem + visibleItemCount ) >= (totalItemCount - threshold))) {
loading = true ;
// List needs more data. Go fetch !!
loadMore(view, firstVisibleItem,
visibleItemCount, totalItemCount);
}
}
// Called when the user is nearing the end of the ListView
// and the ListView is ready to add more items.
public abstract void loadMore(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount);
}
Activity :
public class MainActivity extends AppCompatActivity {
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_layout);
ListView listView = (ListView) findViewById(R.id.listView);
listView.setOnScrollListener(new LazyLoader() {
#Override
public void loadMore(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
// Fetch your data here !!!
}
});
}
}
You can find the complete implementation at this link
The other answers here refer to outdated, unmaintained solutions. This article, however, seems to be kept up-to-date:
https://github.com/codepath/android_guides/wiki/Endless-Scrolling-with-AdapterViews-and-RecyclerView
There's too much there to put it all in a SO answer, but here's some important bits as of the time I'm writing this answer:
Implementing endless pagination for RecyclerView requires the following steps:
Copy over the EndlessRecyclerViewScrollListener.java into your application.
Call addOnScrollListener(...) on a RecyclerView to enable endless pagination. Pass in an instance of EndlessRecyclerViewScrollListener and implement the onLoadMore which fires whenever a new page needs to be loaded to fill up the list.
Inside the aforementioned onLoadMore method, load additional items into the adapter either by sending out a network request or by loading from another source.
To start handling the scroll events for steps 2 and 3, we need to use the addOnScrollListener() method in our Activity or Fragment and pass in the instance of the EndlessRecyclerViewScrollListener with the layout manager as shown below:
public class MainActivity extends Activity {
// Store a member variable for the listener
private EndlessRecyclerViewScrollListener scrollListener;
#Override
protected void onCreate(Bundle savedInstanceState) {
// Configure the RecyclerView
RecyclerView rvItems = (RecyclerView) findViewById(R.id.rvContacts);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(this);
rvItems.setLayoutManager(linearLayoutManager);
// Retain an instance so that you can call `resetState()` for fresh searches
scrollListener = new EndlessRecyclerViewScrollListener(linearLayoutManager) {
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
// Triggered only when new data needs to be appended to the list
// Add whatever code is needed to append new items to the bottom of the list
loadNextDataFromApi(page);
}
};
// Adds the scroll listener to RecyclerView
rvItems.addOnScrollListener(scrollListener);
}
// Append the next page of data into the adapter
// This method probably sends out a network request and appends new data items to your adapter.
public void loadNextDataFromApi(int offset) {
// Send an API request to retrieve appropriate paginated data
// --> Send the request including an offset value (i.e `page`) as a query parameter.
// --> Deserialize and construct new model objects from the API response
// --> Append the new data objects to the existing set of items inside the array of items
// --> Notify the adapter of the new items made with `notifyItemRangeInserted()`
}
}
I have a class which implements expandable list activity.
In the XML code (or I can do it in java), I set fastScrollEnabled to true. This does in deed enable fast scroll. BUT fast scroll only works in the top portion of the list. Like I can use the fastscroll thumb bar to scroll the whole list but only works in the top section of the scroll bar. It's not proportionate to the entire list. I can drag the thumb bar to the bottom of the list but it does no scrolling since the listview is already scrolled to the bottom due to the odd behaviour of it only working in the top portion of the list.
Confusing I know, I can try to clarify more if needed....
I do implement a custom BaseExpandableListAdapter.
I've just found a workaround to prevent the system to display this wrong behaviour.
There are two scenarios which use different code for the SectionIndexer to work.
The first scenario is the case that you use the FastScrollbar-Thumb to navigate to the next section. Assuming that the groups are your sections the overriden methods for implementing the SectionIndexer would look like that:
#Override
public int getPositionForSection(int section) {
return section;
}
// Gets called when scrolling the list manually
#Override
public int getSectionForPosition(int position) {
return ExpandableListView.getPackedPositionGroup(
expandableListView
.getExpandableListPosition(position));
}
The second scenario is the case that you scroll the list manually and the fast scrollbars move according to the sections, not to all items. The code therefore looks like that:
#Override
public int getPositionForSection(int section) {
return expandableListView.getFlatListPosition(
ExpandableListView.getPackedPositionForGroup(section));
}
// Gets called when scrolling the list manually
#Override
public int getSectionForPosition(int position) {
return ExpandableListView.getPackedPositionGroup(
expandableListView
.getExpandableListPosition(position));
}
As one can see these two behaviours can not play together without further adoption.
The workaround to make it both work is to catch the case when someone is scrolling per hand (i.e. scrolling via touch). This can be done with implementing the OnScrollListener interface with the adapter class and set it onto the ExpandableListView:
public class MyExpandableListAdapter extends BaseExpandableListAdapter
implements SectionIndexer, AbsListView.OnScrollListener {
// Your fields here
// ...
private final ExpandableListView expandableListView;
private boolean manualScroll;
public MyExpandableListAdapter(ExpandableListView expandableListView
/* Your other arguments */) {
this.expandableListView = expandableListView;
this.expandableListView.setOnScrollListener(this);
// Other initializations
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
this.manualScroll = scrollState == SCROLL_STATE_TOUCH_SCROLL;
}
#Override
public void onScroll(AbsListView view,
int firstVisibleItem,
int visibleItemCount,
int totalItemCount) {}
#Override
public int getPositionForSection(int section) {
if (manualScroll) {
return section;
} else {
return expandableListView.getFlatListPosition(
ExpandableListView.getPackedPositionForGroup(section));
}
}
// Gets called when scrolling the list manually
#Override
public int getSectionForPosition(int position) {
return ExpandableListView.getPackedPositionGroup(
expandableListView
.getExpandableListPosition(position));
}
// Your other methods
// ...
}
That fixed the bug for me.
This is a bug in the fast scroller. It does not play well with ExpandableListView.
See my code.
(it also includes a work-around for some cases)