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);
}
Related
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).
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()
The click event associated with the ListView footer sometimes takes a long time to fire, especially after extensive scrolling has taken place. The regular list item clicks always fire immediately. Stranger still, when a regular item is clicked while the footer click is still waiting to process, it will fire immediately, and after it is done the footer click will immediately execute (the clicks execute in the wrong order).
Any feedback greatly appreciated.
private void init()
{
...
// I attach a footer to this ListActivity instance like so...
LayoutInflater inflater = ((LayoutInflater)getSystemService(Context.LAYOUT_INFLATER_SERVICE));
mFooterView = inflater.inflate(R.layout.row_feed_footer, null, false);
// Add click handler to footer
mFooterView.findViewById(R.id.feed_footer_layout).setOnClickListener(new OnClickListener()
{
#Override
public void onClick(View v)
{
onFooterClick(v);
}
});
getListView().addFooterView(mFooterView);
// Register with my custom adapter
setListAdapter(mFeedData);
}
private void onFooterClick(View footerView)
{
// After extensive scrolling, this event will take a long time to fire (as long as 5 seconds).
// Unless onListItemClick, in which case it will fire immediately after
}
#Override
protected void onListItemClick(ListView l, View v, int position, long reportSeqNo)
{
// After extensive scrolling, this event will still fire immediately
}
Just in case anyone comes across a similar problem, I never did find a solution. It appears to be a bug. My workaround was to simply implement the footer as a special-case item in my ListAdapter implementation.
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;
}
});
I'm having a problem with my ListView (using CursorAdapter). When I call getListView().getLastVisiblePosition() I am receiving -1. This is a problem since my list is populated with items. Additionally, getListView().getFirstVisiblePosition() always returns 0, no matter where I am scrolled on the list. Any ideas?
It has something to do with startManagingCursor
#Override
public void changeCursor(Cursor cursor) {
super.changeCursor(cursor);
MyActivity.this.mCursor = cursor;
//startManagingCursor(MyActivity.this.mCursor);
}
If I comment out startManagingCursor, everything works fine. I've also tried adding stopManagingCursor() before changing the Cursor and still have the same problem.
This is because the moment you call getListView().getLastVisiblePosition() the listview is not rendered. You can add the call to the view's message queue like this:
listview.post(new Runnable() {
public void run() {
listview.getLastVisiblePosition();
}
});
Even I faced the same problem. The thing is that, getLastVisiblePosition() works only when you are in the first element of your listview and for the rest you will get null being returned. So what I did is that, I made a small calculation to find out the exact view position which I have mentioned below,
int LastPos=(mylist.getLastVisiblePosition()-mylist.getFirstVisiblePosition());
This returns the exact last position without a doubt.
My guess is that your getItem(int position) and getItemId(int position) methods aren't defined correctly in your adapter.