Implement endless recyclerview -android? - android

so i'm trying to implement an endless recyclerview. I read other tutorials and samples on stack and none of them are straightforward. I'm looking for a universal answer that can be applied for a lot of people's needs including my own.
Currently, i have a recyclerview + adapter working that gets data from my backend and loads it. Now, when i reach the end I want to call my api methods to get data again and load it into my Recycler view.
Here is my Recycler view initialization.
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState)
{
// Inflate the layout for this fragment
rootView = inflater.inflate(R.layout.activity_9b_newsfeed , container, false);
bindActivity();
return rootView;
}
private void bindActivity()
{
progressBar = (ProgressBar) rootView.findViewById(R.id.newsfeed_PB);
recyclerView = (RecyclerView) rootView.findViewById(R.id.newsfeed_RV);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager(getActivity().getBaseContext());
recyclerView.setLayoutManager(linearLayoutManager);
recyclerView.setNestedScrollingEnabled(false);
endlessScrollListener = new EndlessScrollListener()//no clue how the hell to use it.
{
#Override
public boolean onLoadMore(int page, int totalItemsCount)
{
//when i reach my last post, this doesn't even get called..so don't really know how to use this.
return false;
}
};
recyclerView.addOnScrollListener(endlessScrollListener);
GetPostsAsyncTask getPostsAsyncTask = new GetPostsAsyncTask();
getPostsAsyncTask.execute();
}
This is my GetPostAsyncTask class, which grabs my post data, encapsulates it into Post Objects, then shoves into my recyclerview, which displays the posts as a newsfeed like feature.
private class GetPostsAsyncTask extends AsyncTask<Void,Void,Void>
{
#Override
protected Void doInBackground(Void... params)
{
if(doesPostListExist)
{
getNewsFeed = new GetNewsFeed(getActivity(),true,Global_Class.getInstance().getValue().user.getUsername());
getNewsFeed.getMyNewsFeed();
}
else
{
getNewsFeed = new GetNewsFeed(getActivity(),false,Global_Class.getInstance().getValue().user.getUsername());
getNewsFeed.getMyNewsFeed();
}
return null;
}
#Override
public void onPostExecute(Void var)
{
progressBar.setVisibility(View.GONE);
Calendar c = Calendar.getInstance();
farTime = c.getTimeInMillis();
Log.d(MY_NEWSFEED,"my far time is " + String.valueOf(farTime));
if(getNewsFeed.isGetNewsFeedSuccess())
{
complexRecyclerViewAdapter = new ComplexRecyclerViewAdapter(getNewsFeed.getNewsFeedPost());//change getNewsFeed.getNewsFeedPost() -> postArrayList
recyclerView.setAdapter(complexRecyclerViewAdapter);
recyclerView.setItemAnimator(new DefaultItemAnimator());
recyclerView.setNestedScrollingEnabled(false);
}
}
}
This is the endlessScrollListener Class, which i scrapped up somewhere, I have no idea how to use it, but here it is.
public abstract class EndlessScrollListener extends RecyclerView.OnScrollListener implements AbsListView.OnScrollListener {
// The minimum number of items to have below your current scroll position
// before loading more.
private int visibleThreshold = 5;
// The current offset index of data you have loaded
private int currentPage = 0;
// The total number of items in the dataset after the last load
private int previousTotalItemCount = 0;
// True if we are still waiting for the last set of data to load.
private boolean loading = true;
// Sets the starting page index
private int startingPageIndex = 0;
public EndlessScrollListener() {
}
public EndlessScrollListener(int visibleThreshold) {
this.visibleThreshold = visibleThreshold;
}
public EndlessScrollListener(int visibleThreshold, int startPage) {
this.visibleThreshold = visibleThreshold;
this.startingPageIndex = startPage;
this.currentPage = startPage;
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount)
{
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
this.currentPage = this.startingPageIndex;
this.previousTotalItemCount = totalItemCount;
if (totalItemCount == 0) { this.loading = true; }
}
// If it's still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading and update the current page
// number and total item count.
if (loading && (totalItemCount > previousTotalItemCount)) {
loading = false;
previousTotalItemCount = totalItemCount;
currentPage++;
}
// If it isn't currently loading, we check to see if we have breached
// the visibleThreshold and need to reload more data.
// If we do need to reload some more data, we execute onLoadMore to fetch the data.
if (!loading && (firstVisibleItem + visibleItemCount + visibleThreshold) >= totalItemCount ) {
loading = onLoadMore(currentPage + 1, totalItemCount);
}
}
// Defines the process for actually loading more data based on page
// Returns true if more data is being loaded; returns false if there is no more data to load.
public abstract boolean onLoadMore(int page, int totalItemsCount);
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
// Don't take any action on changed
}
}
My adapter class is gigantic as hell, (to display all the UI), It may be confusing to read all that code. But assume you have a normal recyclerview adapter that handles an arraylist of strings as input, please write that down in your answer if there is anything I myself must put in my adapter in order for endless scrolling to work.
Overall, im looking for a complete easy and functional solution for this universal problem.
thanks!

Your method seems a little overly complicated. Using a scroll listener at all is unneeded. I would take another approach at it.
The main idea behind an infinite scroller is that you hold a subset of the items in memory, because fetching all of them is too expensive (in time, memory, etc). Instead you fetch and display a subset, then when it looks like you need them soon, you fetch the rest. Lets say that there's 1000 items to display. You may only want to display the first 100 at the beginning. So when you think you want to show the next 100 soon you start the fetch. Like this:
bindViewHolder(VH holder, int position) {
if(position > getCount() - ITEM_FETCH_THRESHOLD && !fetching) {
fetching = true;
performFetch();
}
}
performFetch should start an asynchronous fetch. When the fetch finishes, it should call notifyDataSetChanged(). ITEM_FETCH_THRESHOLD is a constant that you can play with until you get right (the right value should be large enough that you can finish a fetch before it ends, but as small as possible to avoid overfetching).
That's all you need to do an infinite scroll. There's some UI tweaks you can add in case the user hits the bottom before the fetch completes, but those are trivial to add.

Related

Have RecyclerView load page 1 automatically

I'm using a RecyclerView in my Android project. I have a function, getPosts(int page) that adds new items to the relevant ArrayList. This is called in onLoadMore().
Now, when the activity starts, nothing happens. So I decided to call getPosts(1) manually from onCreate(). The problem with this is now page 1 is being loaded twice. Once by me in onCreate(), and for some reason again in onLoadMore(). Subsequent pages load perfectly.
So is there some way to tell the RecyclerView to start loading? If not, what should I do here?
Edit: As requested, here is a summary of my code:
private ArrayList<Post> postArrayList;
private RecyclerView recycler;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main_diary);
//...
postArrayList = new ArrayList<>();
recycler = ((RecyclerView) findViewById(R.id.recyclerDiary));
recycler.setHasFixedSize(true);
recycler.setLayoutManager(new LinearLayoutManager(this));
EndlessRecyclerViewScrollListener scrollListener = new EndlessRecyclerViewScrollListener(((LinearLayoutManager) recycler.getLayoutManager())){
#Override
public void onLoadMore(int page, int totalItemsCount, RecyclerView view) {
Log.d("devlog", "Requesting page "+page+" from onLoadMore()");
getPosts(page);
}
};
recycler.setAdapter(new PostAdapter(postArrayList, new RecyclerViewClickHandler()));
recycler.addOnScrollListener(scrollListener);
Log.d("devlog", "Requesting page 1 from onCreate()");
getPosts(1);
}
//...
private void getPosts(int page){
int insertStartPos = postArrayList.size();
Post[] posts;
//get posts from the backend
for (int i = 0; i < posts.length(); i++){
postArrayList.add(posts[i]);
}
DiaryAdapter adap = ((DiaryAdapter) recycler.getAdapter());
if (insertStartPos == 0){
adap.notifyDataSetChanged();
} else {
adap.notifyItemRangeInserted(insertStartPos, bundleSize);
}
}
One way to avoid calling getNewItems(1) twice that means that you should have a global variable on your class, for example, named currentPage that is initialised with value 1 and is incremented every time you call getNewItems().
From now on call
getNewItems(currentPage);
currentPage++;

RecyclerView scrolls to top on notifyDataSetChanged in chat screen

I am trying to create messaging kind of screen using recyclerView which will start from bottom and will loadMore data when user reached top end of chat. But I am facing this weird issue.
My recyclerView scrolls to top on calling notifyDataSetChanged. Due to this onLoadMore gets called multiple times.
Here is my code:
LinearLayoutManager llm = new LinearLayoutManager(context);
llm.setOrientation(LinearLayoutManager.VERTICAL);
llm.setStackFromEnd(true);
recyclerView.setLayoutManager(llm);
** In Adapter
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks) {
mLoadMoreCallbacks.onLoadMore();
}
** In Activity
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
chatMessagesAdapter.notifyDataSetChanged();
}
It's just that recyclerView scrolls to top. If scrolling to top stops, this issue will be resolved. Please help me to figure out the cause of this issue. Thanks in advance.
I think you shouldn't use onBindViewHolder that way, remove that code, the adapter should only bind model data, not listen scrolling.
I usually do the "onLoadMore" this way:
In the Activity:
private boolean isLoading, totallyLoaded; //
RecyclerView mMessages;
LinearLayoutManager manager;
ArrayList<Message> messagesArray;
MessagesAdapter adapter;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//...
mMessages.setHasFixedSize(true);
manager = new LinearLayoutManager(this);
manager.setStackFromEnd(true);
mMessages.setLayoutManager(manager);
mMessages.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if (manager.findFirstVisibleItemPosition() == 0 && !isLoading && !totallyLoaded) {
onLoadMore();
isLoading = true;
}
}
});
messagesArray = new ArrayList<>();
adapter = new MessagesAdapter(messagesArray, this);
mMessages.setAdapter(adapter);
}
#Override
public void onLoadMore() {
//get more messages...
messagesArray.addAll(0, moreMessagesArray);
adapter.notifyItemRangeInserted(0, (int) moreMessagesArray.size();
isLoading = false;
}
This works perfeclty for me, and the "totallyLoaded" is used if the server doesn't return more messages, to stop making server calls. Hope it helps you.
You see, it's natural for List to scroll to the most top item when you insert new Items. Well you going in the right direction but I think you forgot adding setReverseLayout(true).
Here the setStackFromEnd(true) just tells List to stack items starting from bottom of the view but when used in combination with the setReverseLayout(true) it will reverse order of items and views so the newest item is always shown at the bottom of the view.
Your final layoutManager would seems something like this:
mLayoutManager = new LinearLayoutManager(getActivity());
mLayoutManager.setReverseLayout(true);
mLayoutManager.setStackFromEnd(true);
mRecyclerView.setLayoutManager(mLayoutManager);
DON'T call notifyDataSetChanged() on the RecyclerView. Use the new methods like notifyItemChanged(), notifyItemRangeChanged(), notifyItemInserted(), etc...
And if u use notifyItemRangeInserted()--
don't call setAdapter() method after that..!
This is my way to avoid scrollview move to top
Instead of using notifyDataSetChanged(), I use notifyItemRangeChanged();
List<Object> tempList = new ArrayList<>();
tempList.addAll(mList);
mList.clear();
mList.addAll(tempList);
notifyItemRangeChanged(0, mList.size());
Update:
For another reason, Your another view in the top is focusing so it will jump to top when you call any notifies, so remove all focuses by adding android:focusableInTouchMode="true" in the GroupView.
I do not rely on onBindViewHolder for these kind of things. It can be called multiple times for a position. For the lists which has load more option maybe you should use something like this after your recyclerview inflated.
recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrolled(RecyclerView recyclerView, int dx, int dy) {
super.onScrolled(recyclerView, dx, dy);
if ((((LinearLayoutManager) recyclerView.getLayoutManager()).findFirstCompletelyVisibleItemPosition() == 0)) {
if (args.listModel.hasMore && null != mLoadMoreCallback && !loadMoreStarted) {
mLoadMoreCallbacks.onLoadMore();
}
}
}
});
Hope it helps.
I suggest you to use notifyItemRangeInserted method of RecyclerView.Adapter for LoadMore operations. You add a set of new items to your list so you do not need to notify whole dataset.
notifyItemRangeInserted(int positionStart, int itemCount)
Notify any registered observers that the currently reflected itemCount
items starting at positionStart have been newly inserted.
For more information:
https://developer.android.com/reference/android/support/v7/widget/RecyclerView.Adapter.html
You need to nofity the item in specific range like below:
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
List<Messages> messegaes=getFromDB();
chatMessagesAdapter.setMessageItemList(messages);
// Notify adapter with appropriate notify methods
int curSize = chatMessagesAdapter.getItemCount();
chatMessagesAdapter.notifyItemRangeInserted(curSize,messages.size());
}
Checkout Firebase Friendlychat source-code on Github.
It behaves like you want, specially at:
mFirebaseAdapter.registerAdapterDataObserver(new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
super.onItemRangeInserted(positionStart, itemCount);
int friendlyMessageCount = mFirebaseAdapter.getItemCount();
int lastVisiblePosition = mLinearLayoutManager.findLastCompletelyVisibleItemPosition();
// If the recycler view is initially being loaded or the user is at the bottom of the list, scroll
// to the bottom of the list to show the newly added message.
if (lastVisiblePosition == -1 ||
(positionStart >= (friendlyMessageCount - 1) && lastVisiblePosition == (positionStart - 1))) {
mMessageRecyclerView.scrollToPosition(positionStart);
}
}
});
You have this issue because every time your condition be true you call loadMore method even loadMore was in running state, for solving this issue you must put one boolean value in your code and check that too.
check my following code to get more clear.
1- declare one boolean value in your adapter class
2- set it to true in your condition
3- set it to false after you've got data from database and notified your adapter.
so your code must be like as following code:
public class YourAdapter extend RecylerView.Adapter<.....> {
private boolean loadingDataInProgress = false;
public void setLoadingDataInProgress(boolean loadingDataInProgress) {
this.loadingDataInProgress = loadingDataInProgress
}
....
// other code
#Override
public void onBindViewHolder(RecyclerView.ViewHolder holder, int position) {
if (messages.size() > 8 && position == 0 && null != mLoadMoreCallbacks && !loadingDataInProgress){
loadingDataInProgress = true;
mLoadMoreCallbacks.onLoadMore();
}
......
//// other adapter code
}
in Activity :
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
chatMessagesAdapter.notifyDataSetChanged();
chatMessagesAdapter. setLoadingDataInProgress(false);
}
This must fix your problem but I prefer to handle loadMore inside Activity or Presenter class with set addOnScrollListener on RecyclerView and check if findFirstVisibleItemPosition in LayoutManager is 0 then load data.
I've wrote one library for pagination, feel free to use or custom it.
PS: As other user mentioned don't use notifyDataSetChanged because this will refresh all view include visible views that you don't want to refresh those, instead use notifyItemRangeInsert, in your case you must notify from 0 to size of loaded data from database.
In your case as you load from top, notifyDataSetChanged will change scroll position to top of new loaded data, so you MUST use notifyItemRangeInsert to get good feel in your app
You need to nofity the item in specific range
#Override
public void onLoadMore() {
// Get data from database and add into arrayList
List<Messages> messegaes=getFromDB();
chatMessagesAdapter.setMessageItemList(messages);
// Notify adapter with appropriate notify methods
int curSize = chatMessagesAdapter.getItemCount();
chatMessagesAdapter.notifyItemRangeInserted(curSize,messages.size());
}

in Android Which View to use and how to display n values per page

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.

Android: Implementing progressbar and "loading..." for Endless List like Android Market

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()`
}
}

Android Endless List

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.

Categories

Resources