I am trying to keep my last scrolled position and populate the adapter with new data but same scrolling position. In my scenerio, I have updating expandable listview every 5 second and updating new items very 5 seconds but with last scrolling position left by user.
I have searched a lot about it and found few solutions but still it is behaving strangly each time I set scrolling position.
I am trying to achieve it with this method:
activity.runOnUiThread(new Runnable()
{
#Override public void run() {
state = list.onSaveInstanceState();
if(settings != null) {
if (mode.equals("Now")) {
info_panel = new esInfoListAdapter(activity, data, expanHash, false, esActivity.lastgrpPosition);
}
else {
info_panel = new esInfoListAdapter(Activity.this, data, tchildData, true, lastgrpPosition);
}
}
list.setAdapter(info_panel);
if(state != null)
list.onRestoreInstanceState(state);
info_panel.notifyDataSetChanged();
}
}
});
So this method gets called just before I updating expandable listview. So in short in every five seconds. I am using Parceable object to save list.onSaveInstanceState(); and then use list.onRestoreInstanceState(state); when updating new data, but the issue is sometimes it works and sometimes not? Am I missing some trick here? Thanks for your help.
I have not yet used ExpandableListView yet but I might. I think you should use combination of methods like getFirstVisiblePosition, getLastVisiblePosition, and getExpandableListPosition. I think getting the groupPosition is sufficient for a user. A link I referenced is ...get index of first/last visible group in an ExpandableListView
Related
I have a simple recyclerview with items (tips) and a loading spinner at the bottom.
here's how the item count and item view type methods look:
#Override
public int getItemViewType(int position) {
if (position == getItemCount() - 1) { // last position
return LOADING_FOOTER_VIEW_TYPE;
}
else {
return TIP_VIEW_TYPE;
}
}
#Override
public int getItemCount() {
return tips.size() + 1; // + 1 for the loading footer
}
basically, i just have a loading spinner under all my items.
I create the adapter once like so:
public TipsListAdapter(TipsActivity tipsActivity, ArrayList<Tip> tips) {
this.tipsActivity = tipsActivity;
this.tips = tips;
}
and then once i have fetched additional items, i call add like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeInserted(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
What's odd here is that when i do that, the scroll position goes to the very bottom. It almost seems like it followed the loading spinner. This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
This doesn't happen if i change notifyItemRangeInserted() to notifyItemRangeChanged() like so:
public void addTips(List<Tip> tips) {
// hide the loading footer temporarily
isAdding = true;
notifyItemChanged(getItemCount() - 1);
// insert the new items
int insertPos = this.tips.size(); // this will basically give us the position of the loading spinner
this.tips.addAll(tips);
notifyItemRangeChanged(insertPos, tips.size());
// allow the loading footer to be shown again
isAdding = false;
notifyItemChanged(getItemCount() - 1);
}
Nor does it happen if i simply call notifyDataSetChanged() like so:
public void addTips(List<Tip> tips) {
this.tips.addAll(tips);
notifyDataSetChanged();
}
Here's the code for setting the adapter in my Activity:
public void setAdapter(#NonNull ArrayList<Tip> tips) {
if (!tips.isEmpty()) { // won't be empty if restoring state
hideProgressBar();
}
tipsList.setAdapter(new TipsListAdapter(this, tips));
}
public void addTips(List<Tip> tips) {
hideProgressBar();
getAdapter().addTips(tips);
restorePageIfNecessary();
}
private TipsListAdapter getAdapter() {
return (TipsListAdapter) tipsList.getAdapter();
}
Note:
I don't manually set scroll position anywhere.
I call setAdapter() in onResume()
addTips() is called after I fetch items from the server
Let me know if you need any additional parts of my code.
This only happens on the first add (i.e. when there is only the loading spinner showing initally). subsequent adds maintains the proper scroll position (the position where the items were inserted).
RecyclerView has built-in behavior when calling the more-specific dataset change methods (like notifyItemRangeInserted() as opposed to notifyDataSetChanged()) that tries to keep the user looking at "the same thing" as before the operation.
When the data set changes, the first item the user can see is prioritized as the "anchor" to keep the user looking at approximately the same thing. If possible, the RecyclerView will try to keep this "anchor" view visible after the adapter update.
On the very first load, the first item (the only item) is the loading indicator. Therefore, when you load the new tips and update the adapter, this behavior will prioritize keeping the loading indicator on-screen. Since the loading indicator is kept at the end of the list, this will scroll the list to the bottom.
On subsequent loads, the first item is not the loading indicator, and it doesn't move. So the RecyclerView will not appear to scroll, since it doesn't have to do so to keep the "anchor" on-screen.
My recommendation is to check insertPos and see if it is zero. If it is, that means this is the first load, so you should update the adapter by calling notifyDataSetChanged() in order to avoid this anchoring behavior. Otherwise, call notifyItemRangeInserted() as you're currently doing.
Remove the setAdapter code from onResume ASAP as you are setting new TipsListAdapter(this, tips);
Every time a new reference of the adapter is created...make field mAdapter and then set it in onCreate . RecyclerView doesnt remember the scrolled position because everytime a new reference of adapter is being created.. onResume gets called infinitely when activity is in running state..
So either you setAdapter in onCreate using new operator to create reference for adapter or,
in onResume use mAdapter field variable reference..
Below is my implementation for recycler view.
The recycler view is refreshing but the view is lagging alot making it not smooth to scroll.
If I dont refresh data the recyclerView is smooth.
The problem is with refreshing only..!!
I need to refresh data completely every 1 sec in RecyclerView maintaining same scroll position.
xml
<android.support.v7.widget.RecyclerView
android:id="#+id/recyView_disp_pgms"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:divider="#null" />
Activity :
RecyclerView recyView_disp_pgms = (RecyclerView) findViewById(R.id.recyView_disp_pgms);
DisplayProgramsAdapterRecycler pgmAdapter = new DisplayProgramsAdapterRecycler(programsList);
LinearLayoutManager layoutManager = new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, false);
recyView_disp_pgms.setLayoutManager(layoutManager);
recyView_disp_pgms.setAdapter(pgmAdapter);
handler = new Handler();
runnable = new Runnable() {
#Override
public void run() {
System.out.println("AllPgm Runflag : " + runflag);
programList = // Get List from database
if (programsList != null && programsList.size() > 0) {
if (pgmAdapter != null && pgmAdapter.getItemCount() > 0) {
pgmAdapter.resetData(programsList);
}
}
handler.postDelayed(this, 1000);
}
};
handler.post(runnable);
Adapter :
public void resetData(ArrayList<ProgramDetails_Table> programsList) {
this.programDetailsList = programsList;
this.notifyDataSetChanged();
}
Problem - Lagging scroll of recyclerView only while refreshing data for every 1 sec
Tried following other posts; but still no luck.
Please help..!!
if you want to refresh your list find your changes and then just notify that position because notifyDataSetChanged resets all of your data.
you should use lightweight notifies.something like this:
notifyItemChanged(yourPosition);
notifyItemInserted(yourPosition);
notifyItemRemoved(yourPosition);
or if you get more than one change in a range you can use:
notifyItemRangeChanged(yourPosition);//and etc
use DiffUtil. Don't use notifyDataSetChanged.
According to this https://developer.android.com/reference/android/support/v7/util/DiffUtil.html,
DiffUtil is a utility class that can calculate the difference between
two lists and output a list of update operations that converts the
first list into the second one.
It can be used to calculate updates for a RecyclerView Adapter.
Unlike the notifyDataSetChanged method, it does not reload the whole list so it has a better performance.
And you don't have to manually find the differences between the new and the old list like the other notifyData methods from RecyclerView.Adapter.
Delete your line this.programDetailsList = programsList; and add the following. This is a simple hack, a way that makes you understand Async Callbacks.
/* I have done adding one element and then deleting the same element as the last element of programsList */
/* adding and deleting triggers notifyDataChange() callback */
//add an element in index 0 as the last element into the programList
programsList.add(programsList.size() - 1, programsList.get(0));
//remove the same last element from ProgramList
programsList.add(programsList.size() - 1);
//now it works
this.notifyDataSetChanged(programsList.size() - 1);
Make sure you are calling notifyDataSetChanged() inside the resetData function.
Also, try this instead of assigning directly to programList
programList.clear();
programList.addAll(arrayListOfDataFromServer);
I'm working on a note taking app. I add a note, and it get's added to the bottom of the list. As the last assertion in the espresso test, I want to make sure that the ListView displays a listItem that has just been added. This would mean grabbing the last item in the listView. I guess you might be able to do it in other ways? (e.g. get the size of adapted data, and go to THAT position? maybe?), but the last position of the list seems easy, but I haven't been able to do it. Any ideas?
I've tried this solution, but Espresso seems to hang. http://www.gilvegliach.it/?id=1
1. Find the number of elements in listView's adapter and save it in some variable. We assume the adapter has been fully loaded till now.:
final int[] numberOfAdapterItems = new int[1];
onView(withId(R.id.some_list_view)).check(matches(new TypeSafeMatcher<View>() {
#Override
public boolean matchesSafely(View view) {
ListView listView = (ListView) view;
//here we assume the adapter has been fully loaded already
numberOfAdapterItems[0] = listView.getAdapter().getCount();
return true;
}
#Override
public void describeTo(Description description) {
}
}));
2. Then, knowing the total number of elements in listView's adapter you can scroll to the last element:
onData(anything()).inAdapterView(withId(R.id.some_list_view)).getPosition(numberOfAdapterItems[0] - 1).perform(scrollTo())
I have a list of Items that are "seen" or "not seen" in ArrayList<Item>. If they're not seen I change the background color of the ListView item in my CustomArrayAdapter like this :
if(item.is_seen == null || item.is_seen == 0) {
row.setBackgroundResource(R.color.yellow);
} else {
row.setBackgroundResource(R.color.transparent);
}
Now what I want to do is set all items background to transparent after 3 seconds spent on the page.
I already tried to do something like this:
mScheduledExecutor.schedule(new Runnable() {
#Override
public void run() {
for(int i=0; i<mItems.size(); i++) {
final Item n = mItems.get(i);
if(n.is_seen == null || n.is_seen == 0) {
// update value in db
int isSeen = 1;
updateItem(n._id, isSeen);
// change the color of backgrounds
View view = listViewItem.getChildAt(i);
if(view != null) {
view.setBackgroundResource(R.color.red);
}
}
}
Updating the value in the DB works, but the rest doesn't. But I'd like to avoid to reload the data. I just need the color to change.
I don't have errors, it just does nothing.
I look everywhere for an answer and didn't find one.
Am I wrong since the beginning? Is what I want to achieve even possible?
I thank you in advance for all the help you could give me.
Instead of changing the color of the view like youre doing,
// change the color of backgrounds
View view = listViewItem.getChildAt(i);
if(view != null) {
view.setBackgroundResource(R.color.red);
}
(code above will not work, because the views are recycled in the ListAdapter) update the DATA off which you build your list - add a property to the class you are passing into your ListAdapter, then grab that instance from the list and update that property, you have the position at which it needs to be updated already, so that's easy. Then, call notifyDataSetChanged() on the list. It will not redraw the list if you didn't ADD/REMOVE items from list, but it will update correct view for you. This is the only way to do it - absolutely NO WAY to get to a view corresponding to a specific element in a list after list has been drawn already. Only way is to refresh/redraw the list with notifyDataSetChanged(), followed by refreshDrawableState().
I am writing an android app which contains a listview with some items (fetch by database, can be 100+)
i would like the scroll the listview to display the specific item (let say position x in the listview)
i used
listView.post(new Runnable() {
#Override
public void run() {
listView.smoothScrollToPosition(position);
}
});
the view has scrolled. but it stopped once the item appear. (the item is now displayed at the bottom of the screen)
i think better design is to display the item at the top of the screen or at least at the middle of the screen.
How can i do this?
I am now finding the way to find out the index of the listview which is really visible in the screen.
I have tried getFirstVisiblePosition() but it seems always return 0.
You will find setSelection() works better than smoothScrollToPosition(). If you are loading the data backing the ListView in the background, you will want to delay issuing the setSelection() until the data has been loaded.
getFirstVisiblePosition() works fine for me, but again is only useful once all the data has been loaded. Here is some code I use for a ListView that displays data fetched from a remote site:
// the list has been updated, fix the selection. loadFinished will be true if there are no phantom placeholders left.
protected void listLoaded(boolean loadFinished) {
// if the user has scrolled or we previously completed resetting the position, do nothing
if(wasScrolled)
return;
// if we were restored from saved data, reload that data now.
if(listState != null) {
postListView.onRestoreInstanceState(listState);
listState = null;
} else if(initialPos >= 0) // if we had a specific initial position, set it now
setSelection(initialPos);
else if(postId >= 0) // or if there is a specific post ID we want displayed
setSelection(postAdapter.getPostPosition(postId));
else // otherwise scroll to the first unread post
setSelection(postAdapter.getFirstUnread());
if(loadFinished) // if all data has been loaded, set a flag to prevent this being repeated
wasScrolled = true;
}