Scrolling a ListView to a position after cursor loads - android

I have a fragment with a ListView, and I want the ListView to maintain its scroll position when the fragment is saved and restored. Usually this is easy, just call ListView.getFirstVisiblePosition() when saving and ListView.setSelection() (ListView.setSelectionFromTop() if you want to get fancy) when restoring.
However, I'm using loaders, and my ListView isn't fully populated when the activity starts. If I setSelection() during onActivityCreated() the list won't have anything to scroll to yet, and the call will be ignored.
I'm getting around this now by posting the scroll for sometime in the future, but this definitely isn't ideal. I'd prefer for it to scroll right as the data finishes loading. I'm almost doing that here, but swapCursor() doesn't refresh, it just schedules an invalidation for the future.
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
if (savedScrollPosition != null) {
Log.d(TAG, "loaded and scrolling to " + savedScrollPosition);
new Handler().postDelayed(new Runnable(){
#Override
public void run() {
list.setSelection(savedScrollPosition);
savedScrollPosition = null;
}
}, 100);
}
}
Do you know of any way of scrolling the ListView right as the ListView finishes populating with data?

Try this:
listView.post(new Runnable() {
#Override
public void run() {
listView.setSelection(savedScrollPosition);
savedScrollPosition = null;
}
});

Related

Swiping to delete a cardview causes flickering for a brief second before animating

I have implemented cardview in my application with the feature swipe right to delete. The moment I swipe right the card view goes and comes back for one tenth of a second and then gain goes off causing flickering.
My code goes like this for the swipe touch listener.I am updating the Content resolver as well notifying the adapter.
SwipeableRecyclerViewTouchListener swipeTouchListener =
new SwipeableRecyclerViewTouchListener(recyclerView,
new SwipeableRecyclerViewTouchListener.SwipeListener() {
#Override
public boolean canSwipe(int position) {
return true;
}
#Override
public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
Post post = posts.get(position);
post.setIsDeleted(true);
getActivity().getContentResolver().update(PostsContract.PostEntry.buildUriForPost(posts.get(position).get_ID()), Utility.changePostToContentValue(post), "_id=" + post.get_ID(),null);
posts.remove(position);
adapter.notifyItemRemoved(position);
}
}
});
recyclerView.addOnItemTouchListener(swipeTouchListener);
My onLoadFinished Looks like this
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if(data!=null && data.getCount() != posts.size()){
posts.clear();
while (data.moveToNext()){
Post post = new Post(data);
posts.add(post);
adapter.notifyDataSetChanged();
}
}
}
When I put break points. It works fine in the handler onDismissedSwipeByRight where the deleted card isn't visible. But when it comes to OnLoadFinished break point I see the deleted card came back and then goes off automatically after the function executes even though the size of posts Arraylist is exactly same.
Since the deleted card comes back for one tenth of a second.This causes a flicker. Can anyone tell me where I a going wrong ?
From what I see in the docs, I see that the example also calls adapter.notifyDataSetChanged(); like so:
SwipeableRecyclerViewTouchListener swipeTouchListener =
new SwipeableRecyclerViewTouchListener(recyclerView,
new SwipeableRecyclerViewTouchListener.SwipeListener() {
#Override
public boolean canSwipe(int position) {
return true;
}
#Override
public void onDismissedBySwipeRight(RecyclerView recyclerView, int[] reverseSortedPositions) {
for (int position : reverseSortedPositions) {
Post post = posts.get(position);
post.setIsDeleted(true);
getActivity().getContentResolver().update(PostsContract.PostEntry.buildUriForPost(posts.get(position).get_ID()), Utility.changePostToContentValue(post), "_id=" + post.get_ID(),null);
posts.remove(position);
adapter.notifyItemRemoved(position);
}
adapter.notifyDataSetChanged();
}
});
The reason for your problem might be that the listener takes care of making the view swipe to the right, but once the gesture is complete, it takes the view back to normal so it can be reused. You can try making the view GONE until it is reused by the recyclerview (make it visible in the adapter).

Issues with ListView focusing

In my application I have a Fragment that contains both a GridView and a ListView to show items using a CursorAdapter. Only one of these two views is enabled, the user can choose the way he wants to see the items.
So, in onCreateView method of my Fragment I have
gv = (GridView) v.findViewById(R.id.itemselect_gridview);
lv = (ListView) v.findViewById(R.id.itemselect_listview);
if (!UserPreferences.isViewStyleGrid(getActivity())) {
showList();
} else {
showGrid();
}
And the code of showList and showGrid is:
private void showList() {
gv.setVisibility(View.GONE);
lv.setVisibility(View.VISIBLE);
UserPreferences.getPreferences(getActivity()).edit()
.putString(Constants.VIEWSTYLE, "list").commit();
}
private void showGrid() {
lv.setVisibility(View.GONE);
gv.setVisibility(View.VISIBLE);
UserPreferences.getPreferences(getActivity()).edit()
.putString(Constants.VIEWSTYLE, "grid").commit();
}
When the app calls showList, I cannot use a d-pad (game controller) to navigate the ListView. Pressing the d-pad keys is not effective and only touch input works.
If the app calls showGrid instead, the d-pad works properly.
If I switch from grid to list at runtime, the dpad works properly also on the ListView, the switch is made using the same methods mentioned before. What could cause this problem? I tried to use lv.setFocusable(true) and lv.requestFocus() with no results.
I realized it was too soon to call showGrid and showList, so I changed the onCreateView a little bit:
if (UserPreferences.isViewStyleGrid(mFragment.getActivity())) {
gv.post(new Runnable() {
#Override
public void run() {
showGrid();
}
});
} else {
lv.post(new Runnable() {
#Override
public void run() {
showList();
}
});
}
This way both the functions are called after the views are fully initialized.

Update a listview item row but on scroll the modifications don't remain

I have a ListView in an Android Activity and a custom adapter for that listview.
I want to be able to edit a row item and update that row instantly. This works, the modifications of the row is seen But, on scroll i loose all data.
This is my Asynk task from where i get the data and update the list row item:
/**
*
*/
public class EditNewsFeedPostAsyncTask extends AsyncTask<Void, Void, Boolean> {
public Activity context;
public String content;
public int rowPosition;
public ListView listView;
public TextView decriptionTxt;
#Override
protected Boolean doInBackground(Void... params) {
try {
token = Utils.getToken(context);
if (token != null) {
....
// {"status":"true"}
if (result != null) {
....
}
}
} catch (Exception e) {
e.printStackTrace();
}
return true;
}
#Override
protected void onPostExecute(final Boolean success) {
if (success) {
updateListView(rowPosition, content);
}
}
public boolean updateListView(int position, String content) {
int first = listView.getFirstVisiblePosition();
int last = listView.getLastVisiblePosition();
if (position < first || position > last) {
return false;
} else {
View convertView = listView.getChildAt(position - first);
decriptionTxt.setText(content);
listView.invalidateViews();
return true;
}
}
private void updateView(int index, TextView decriptionTxt) {
View v = listView.getChildAt(index - listView.getFirstVisiblePosition());
if (v == null)
return;
decriptionTxt.setText(content);
listView.invalidateViews();
}
#Override
protected void onPreExecute() {
super.onPreExecute();
}
#Override
protected void onCancelled() {
}
}
What am i missing? shouldn't the data be persistent?
Thx
You must update the object in your listView adapter, not only the views!
after scrolling, the getView method inside your list's adapter will call and you will return the default view for that.
if you want to change that item permanent, you should update your data set and call notifyDataSetChanged on your adapter.
Make sure you're updating the data, not just the view.
When you modify the row, are you changing the underlying data or just the view? If just the view...
You're probably running into ListView recycling issues. This answer has a great explanation. Basically, ListViews are about efficiency in displaying views based on data, but are not good for holding new data on screen. Every time a ListView item is scrolled out of view, its View is recycled to be used for the item that just scrolled into view. Therefore, if you put "hi" in an EditText and then scroll it off screen, you can say goodbye to that string.
I solved this in my app by ditching ListView altogether and using an array of LinearLayouts (probably a clunky approach, but I had a known list size and it works great now). If you want to continue using a ListView, you'll have to approach it from a "data first" perspective. Like I said, ListViews are great at showing info from underlying data. If you put "hi" in an EditText and simultaneously put that string in the underlying data, it would be there regardless of any scrolling you do. Updating onTextChanged might be cumbersome, so you could also let each row open a dialog in which the user enters their data which then updates the underlying dataset when the dialog closes.
These are just some ideas, based on some assumptions, but editing views in a ListView is, in general, not very in line with how ListViews work.

Android: ListView.getChildAt() throws null

Sometimes, when I call goToLast() it throws me a null exception in vista=lista.getChildAt(), it happens when the list is full, I dont know why I have this code:
private void goToLast() {
lista.post(new Runnable() {
#Override
public void run() {
lista.setSelection(mensajes.getCount() - 1);
View vista = lista.getChildAt(mensajes.getCount() - 1);
TextView txtMensaje = (TextView)vista.findViewById(R.id.txtMensajeLista);
txtMensaje.setBackgroundColor(Color.RED);
}
});
}
You should log lista.getChildCount(), see how many children the ListView has. ListView recycles views, which means it only hold limit number of views.
So if you want to get the last view, you should do something like this
private void goToLast() {
lista.post(new Runnable() {
#Override
public void run() {
lista.setSelection(mensajes.getCount() - 1);
// Figure out the last position of the view on the list.
int lastViewIndex = lista.getChildCount() - 1;
View vista = lista.getChildAt(lastViewIndex);
TextView txtMensaje = (TextView)vista.findViewById(R.id.txtMensajeLista);
txtMensaje.setBackgroundColor(Color.RED);
}
});
}
ListView.getChildAt() position is different to the position in your adapter. ListView recycles its views, so if you have more items in your adapter, than can fit on the screen it will not create views for all of those, but only the ones that are visible. If you want to update an item in the ListView you need to update it in the adapter and call notifyDataSetChanged()

ListView Requires 2 setSelection's to Scroll to Item

Peculiar problem I'm having with a cursoradapter in a listfragment.
In my onLoadFinished I select the previously selected item in order to scroll the listview to the previous position (and then highlight that item).
This works splendidly, except for the scrolling part.
If I just use one post and delay it (even say 5 seconds), the item gets selected but the list will not scroll (the selected item may out of view at this time) With or without delay same behavior with just one post.
I have to post setSelection AGAIN to get the listview to scroll so the selected item is in view.
It doesn't matter how long I delay the initial or second scroll post.
Here's my grubby workaround, but I'm not pleased with it. Any ideas?
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
getListView().post(new Runnable() {
#Override
public void run() {
getListView().requestFocusFromTouch();
getListView().setSelection(selectedposition);
getListView().performItemClick(getListView().getAdapter().getView(selectedposition, null, null), selectedposition, selectedid);
getListView().clearFocus();
}
});
getListView().postDelayed(new Runnable() {
#Override
public void run() {
getListView().requestFocusFromTouch();
getListView().setSelection(selectedposition);
getListView().performItemClick(getListView().getAdapter().getView(selectedposition, null, null), selectedposition, selectedid);
getListView().clearFocus();
}
}, 500);
}
Rewrite:
If I take them out of the runnable it works with calling setselection just once. However then I don't get a delayed scrolling effect (to show the user it scrolled), which I guess I sorta like.
When I took a closer look at your code I noticed that you were calling getView() in the Adapter manually. Adapters recycle their views in a very particular but unpredictable order and attempting to call getView() yourself might create unwanted behavior... You should avoid doing this, try a different tactic:
#Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data); // or changeCursor(data) as explained below
getListView().postDelayed(new Runnable() {
#Override
public void run() {
ListView listView = getListView(); // Save a local reference rather than calling `getListView()` three times
listView.setSelection(selectedposition);
listView.performItemClick(listView.getChildAt(0), selectedposition, selectedposition);
}
}, 500);
}

Categories

Resources