I'm trying to implement an infinite ListView, it has 10 elements loaded first, when I scroll to the bottom of these 10 elements, it should load another 10. This is the code I've written till now:
private class ResultListScrollListener implements OnScrollListener{
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (mResultListView.getLastVisiblePosition() >= mResultListView.getCount()-1 && mResultListView.getLastVisiblePosition()<count-1) {
page_num++;
//count is final variable=60 so that scrolling stops at 60.
Log.i("onScrollStateChanged", "last visible: "+ mResultListView.getLastVisiblePosition() + " Page num: " + page_num);
new LoadItemsAsyncTask().execute();
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem,
int visibleItemCount, int totalItemCount) {
}
}
It does load more elements, but it loads at wrong time, instead of loading when last visible position is 9, 19, 29 .... etc , it loads erratically, like twice at 9, then skips 19 then thrice at 29. How can I fix this? Here is my LogCat output so you can see when the onScrollStateChanged has been called:
07-09 12:16:06.197: I/onScrollStateChanged(27014): last visible: 9 Page num: 1
07-09 12:16:06.648: I/onScrollStateChanged(27014): last visible: 9 Page num: 2
07-09 12:16:11.122: D/dalvikvm(27014): GC_CONCURRENT freed 1069K, 26% free 3477K/4643K, paused 11ms+4ms
07-09 12:16:24.946: I/onScrollStateChanged(27014): last visible: 29 Page num: 3
07-09 12:16:25.376: I/onScrollStateChanged(27014): last visible: 29 Page num: 4
07-09 12:16:26.527: I/onScrollStateChanged(27014): last visible: 29 Page num: 5
EDIT
I got it to behave properly by keeping a track of the previous last visible position and loading items only if the previous is NOT equal to the current. So now I have
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if (mResultListView.getLastVisiblePosition() >= mResultListView.getCount()-1 && mResultListView.getLastVisiblePosition()<count-1
&& mResultListView.getLastVisiblePosition()!=last_visible) { //added this condition
page_num++;
Log.i("onScrollStateChanged", "last visible: "+ mResultListView.getLastVisiblePosition() + " Page num: " + page_num);
last_visible=mResultListView.getLastVisiblePosition();
new LoadItemsAsyncTask().execute();
}
}
}
Now I get proper output like:
07-09 12:33:26.152: I/onScrollStateChanged(27734): last visible: 9 Page num: 1
07-09 12:33:29.735: D/dalvikvm(27734): GC_CONCURRENT freed 1092K, 26% free 3455K/4643K, paused 10ms+7ms
07-09 12:33:33.969: I/onScrollStateChanged(27734): last visible: 19 Page num: 2
07-09 12:33:43.409: I/onScrollStateChanged(27734): last visible: 29 Page num: 3
07-09 12:33:49.014: I/onScrollStateChanged(27734): last visible: 39 Page num: 4
07-09 12:33:53.979: I/onScrollStateChanged(27734): last visible: 49 Page num: 5
But I am unable to understand why it did not work without the dit. Any explanations?
If u want an easy solution go with chris banes pull to refresh libraries
1.> https://github.com/chrisbanes/Android-PullToRefresh
2.> https://github.com/chrisbanes/ActionBar-PullToRefresh
All the listviews in this library have an onLastItemVisibleListener where you can put your code
You can also check wether the list is already refreshing by isRefreshing() to stop the list from loading again.
Here is my implementation which works enough well
list.setOnScrollListener(new AbsListView.OnScrollListener() {
// 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.
int treshhold = 0;
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
if (scrollState == SCROLL_STATE_IDLE) {
if(isLastPageEmpty){
return;
}
if (list.getLastVisiblePosition() >= list.getCount() - 1 - treshhold) {
page++;
if(itemsArray != null) {
lastVisibleListPosition = itemsArray.size();
}
GetItemsAsync async = new GetItemsAsync();
async.execute();
}
}
}
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
}
});
private class GetItemsAsync extends AsyncTask<Void, Void, Result> {
#Override
protected Result doInBackground(Void... params) {
// send request for new items
}
#Override
protected void onPostExecute(Result result) {
super.onPostExecute(result);
try {
if (result.status.equalsIgnoreCase("fail")) {
// show error
} else {
if(result.items.size() == 0){
// no more items
isLastPageEmpty = true;
return;
}
if (itemsArray == null) {
itemsArray = result.items;
} else {
itemsArray.addAll(result.items);
}
adapter = new ListAdapter(getActivity(), itemsArray);
list.setAdapter(adapter);
// scroll list to previous position
if(lastVisibleListPosition > 0){
list.getViewTreeObserver().addOnGlobalLayoutListener(
new ViewTreeObserver.OnGlobalLayoutListener() {
#Override
public void onGlobalLayout() {
list.setSelection(lastVisibleListPosition - 1); list.getViewTreeObserver().removeOnGlobalLayoutListener(this);
}
});
}
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
Related
I've integrated the pagination component in my app and it's working perfectly fine (almost).
I've Database + network model. Database initially has some items which are consumed by LivePagedListBuilder. I observe this LiveData<PagedList<InboxEntity>> and ultimately feed the list to PagedListAdapter#submitList(PagedList<T> pagedList), something like :
LiveData<PagedList<Entity>> entities;
PagedList.Config pagedListConfig =
(new PagedList.Config.Builder()).setEnablePlaceholders(true)
.setPrefetchDistance(5)
.setEnablePlaceholders(false)
.setPageSize(10)
.setInitialLoadSizeHint(10)
.build();
entities = new LivePagedListBuilder<>(DAO.getItemList(), pagedListConfig)
entities.observe(this, new Observer<PagedList<Entity>>() {
#Override
public void onChanged(#Nullable PagedList<Entity> inboxEntities) {
inboxAdapter.submitList(inboxEntities);
isFirstLoad = false;
}
});
DAO#getItemList returns DataSource.Factory<Integer, Entity>.
I am listening for the boundary callback and trigger a network call when it reaches the end of the paged list. That call populates the database again.
There's one more thing. I've registered AdapterDataObserver on recycler view because if an item has been inserted at the beginning, I've to scroll to the top position:
RecyclerView.AdapterDataObserver adapterDataObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (positionStart == 0) {
layoutManager.scrollToPositionWithOffset(positionStart, 0);
}
}
};
I am facing a problem in this model :
After making the network call, database is populated again and onChanged function is called with a new PagedList<Entity>. Now, does this paged list contains only the new items. I've confirmed this.
But onItemRangeInserted method is called with positionStart as 0 too, which suggests that items are being inserted at the beginning. But they are not. They are being inserted at the end, confirmed with stetho db inspector.
Then why is the onItemRangeInserted being called with positionStart as 0? This is making it difficult for me to distinguish when a fresh item is inserted at the beginning of the adapter and when items are inserted at the end.
Edit:
value of itemCount is 10 which is my page size.
In DiffCallback, I just compare the primary key column of the two entities in areItemsTheSame function.
This is how I'm doing it, not ideal but it works. I'm using a recycler view with reverse set to true as it's a chat app so you'll have to adjust for your use case.
In your fragment create a Timer field:
private Timer mTimer;
Add a scroll state listener that sets a "latest row id" that you can compare later.
mRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() {
#Override
public void onScrollStateChanged(#NonNull RecyclerView recyclerView, int newState) {
super.onScrollStateChanged(recyclerView, newState);
if (newState == RecyclerView.SCROLL_STATE_DRAGGING) {
//Get the latest id when we started scrolling
AppExecutors.getInstance().databaseIO().execute(() ->
mLatestMessageId = mHomeViewModel.getMessageViewModel().getLatestMessageId(mOurUsername, mChatname));
}
}});
Use the AdapterDataObserver to kick off a new timer when the insert is called.
private RecyclerView.AdapterDataObserver mAdapterDataObserver = new RecyclerView.AdapterDataObserver() {
#Override
public void onItemRangeInserted(int positionStart, int itemCount) {
if (mTimer != null) {
mTimer.cancel();
}
mTimer = new Timer();
mTimer.schedule(new CheckNewMessageTimer(), 100);
}
};
Here's the timer class, if new items have been inserted the "latest row id" will have incremented in the database. I'm only scrolling to the bottom if I'm already at the bottom, otherwise I just change the color of a "scroll to bottom" FAB that I have.
class CheckNewMessageTimer extends TimerTask {
#Override
public void run() {
if (newItemsAdded()) {
int firstItemPosition = mLinearLayoutManager.findFirstVisibleItemPosition();
boolean atBottom = firstItemPosition == 1;
SurespotLog.d(TAG, "CheckNewMessageTimer, firstItemPosition: %d, atBottom: %b", firstItemPosition, atBottom);
AppExecutors.getInstance().mainThread().execute(() -> {
if (atBottom) {
SurespotLog.d(TAG, "CheckNewMessageTimer, scrolling to bottom");
mListView.scrollToPosition(0);
}
else {
if (mFab != null) {
SurespotLog.d(TAG, "CheckNewMessageTimer, new message received but we're not scrolled to the bottom, turn the scroll button blue");
mFab.setBackgroundTintList(ColorStateList.valueOf(getResources().getColor(R.color.surespotBlue)));
}
}
});
}
}
private boolean newItemsAdded() {
int dbLatestMessageID = mHomeViewModel.getMessageViewModel().getLatestMessageId(mOurUsername, mChatname);
if (mLatestMessageId > 0 && dbLatestMessageID > mLatestMessageId) {
mLatestMessageId = dbLatestMessageID;
return true;
}
return false;
}
}
I have a spinner in a toolbar, I also have replaced the toolbar with an icon and when the user selects the first option and the last option in the spinner I do not want it to show, or in other words do not want to replace the Spinner with text but the rest for the rest between the first and last option. I want them to show. How can I do this?
public void onItemSelected(AdapterView<?> adapterView,
View view, int i, long l) {
int total = adapterView.getCount();
if(i == 0){
}
else if(i == total -1){
}
// declare it inside class
private int prev_pos=0; // initially it zero, you can set your desire position
if(i =! 0 && i!= adapterView.getCount()-1 )
{
// do what you want
prev_pos=i; // store the previous position if it's not last or first
}
else{
// display previous position
yourSpinnerObject.setSelection(prev_pos);
}
I would like to check which of elements on my listview is also a member of another list and check all of them (by changing background). But the only way I can think of is:
for (String str : list1){
if (list2.contains(str)) {
lv.getChildAt(adapter.getPosition(str)).setBackgroundColor(getResources().getColor(android.R.color.darker_gray));
}
}
But that works only for visible elements of the list and throws null pointer exception when accessing non-visible elements. What can I do to apply changes for all list items? Do I have to write my own adapter or maybe there is any "equivalent" of getChiledAt but working for all elements of the listview not only visible ones?
I didn't try by myself, but a suggestion. Can you please try this way, and check.
Idea is to use setOnScrollListener with onScroll method and to have null check inside for loop.
It's not good solution though, because for loop working on every scroll.
lv.setOnScrollListener(new OnScrollListener() {
#Override
public void onScrollStateChanged(AbsListView arg0, int arg1) {
// TODO Auto-generated method stub
}
#Override
public void onScroll(AbsListView arg0, int arg1, int arg2, int arg3) {
// TODO Auto-generated method stub
for (String str : list1) {
if (list2.contains(str)) {
if (lv.getChildAt(adapter.getPosition(str)) != null) {
lv.getChildAt(adapter.getPosition(str))
.setBackgroundColor(
getResources()
.getColor(
android.R.color.darker_gray));
}
}
}
}
});
The solution was to use onScroll method (as Chitrang suggested) and set everything only for visible item. Android Magic works, everything works fine, also for non-visible items :)
#Override
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
for (int i = lv.getFirstVisiblePosition(); i<=lv.getLastVisiblePosition(); i++){
if (list2.contains(lv.getItemAtPosition(i).toString()))
lv.getChildAt(i - lv.getFirstVisiblePosition()).setBackgroundColor(getResources().getColor(android.R.color.darker_gray));
}
}
}
You can create a new List, with the (i assume you have) id like the listview's data.
Example :
You have class Person with int id, String name. Create a new List<Integer> to store all (not just the visible one) of your listview's person id, normally by using the listview's adapter.
I have implemented a ListView that has the functionality that you see in many apps, where user scrolls to bottom and it loads more, that OnScrollListener is this:
public class OnScrolledToEndListener implements AbsListView.OnScrollListener
{
private int prevLast;
#Override
public void onScrollStateChanged(AbsListView absListView, int i)
{
}
#Override
public void onScroll(AbsListView absListView, int first, int visible, int total)
{
int last = first + visible;
if (last == total)
{
if (prevLast != last)
{
prevLast = last;
onScrolledToEnd();
}
}
}
public void onScrolledToEnd()
{
}
}
Now the problem is that when a user has scrolled to the bottom of a list, and hits the refresh button in my app, I want it to start over at the top of the list, because if it stays at the bottom of the list, then the scroll listener will immediately trigger. The best way I've found to solve this is by doing the following before executing the refresh:
mListView.setSelection(0);
mListView.post(
new Runnable()
{
#Override
public void run()
{
mListView.setVisibility(View.GONE);
mLoadingLayout.setVisibility(View.VISIBLE); //this is basically a progressbar
// do the refresh
}
}
);
But there is a slight flicker when the list scrolls to the top. Any ideas on how to make it look better?
I figured out the solution. Apparently setting the ListView to View.GONE makes it not update its layout, so I set it to View.INVISIBLE instead and it worked. I didn't even have to use a Runnable.
mListView.setSelection(0);
mListView.setVisibility(View.INVISIBLE);
mLoadingLayout.setVisibility(View.VISIBLE);
I am developing an application which consists on scroll-change-listener,Here is my problem, I am getting the Number of items for the server.Until here every thing works fine to me.
1.IF i am showing the 10 values in the list-view,that 10 values only should stream.
2.When Scroll state is changed the reaming item should hit server.
3.Below is my code .
#Override
public void onScroll(AbsListView view, int firstVisibleItem,int visibleItemCount, int totalItemCount) {
if (visibleItemCount < 1)
return;
streaming.clear();
int firstPoisitionValue = symbolList.getFirstVisiblePosition();
int lastPositionValue = symbolList.getLastVisiblePosition();
WatchListData row;
String symbol;
for (int i = firstPoisitionValue; i <= lastPositionValue; i++) {
row = model.get(i);
symbol=row.getSymbol();
Log.w("Hello Android", "Symbol Value ::>"+symbol);
streaming.add(symbol);
}
if (streamFlag) {
System.out.println("calling the request");
streamingRequest("quote", streamingSymbols);
streamFlag = false;
}
}
#Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
pauseStreaming();
if(scrollState == SCROLL_STATE_IDLE){
streamingRequest("quote", streamingSymbols);
}
Thanks,
Nikhilreddy.
You can do this by using onScroll Listener. Using this listener When scroll reaches the end you can load new items to the list. refer this link. It may help you.Dynamic listView