EnhancedListView icon indicating copy to clipboard operation
EnhancedListView copied to clipboard

View flicker issue with CursorAdapter

Open hsanson opened this issue 12 years ago • 13 comments

When using a cursor adapter with EnhancedListView after I swipe to dismiss the swiped view reappears momentarily and then disappears creating a very ugly flickering effect.

After looking on StackOverflow it looks this is a known issue with CursorAdapters and animated list views such as EnhancedListView. The issue seems to be that the animation finishes and redraws the list view before the item is actually deleted from the adapter. This causes the item being swiped out to reappear on the list and latter when the CursorAdapter finishes updating with the item removed the list is again redrawn without the removed item. This results in noticeable flickering.

I tried several solutions proposed (see links below) but they did not work with the EnhancedListView. The last solution I tried was to reload the cursor without the deleted item inside the dismiss callback but the flickering is still there.

  • http://stackoverflow.com/questions/15468100/cursoradapter-backed-listview-delete-animation-flickers-on-delete
  • http://stackoverflow.com/questions/18049681/correctly-animate-removing-row-in-listview

I am open to suggestions on how to work around this issue.

hsanson avatar Oct 02 '13 02:10 hsanson

I got a simple (and ugly) work around to the issue. In the onDismiss callback I create a new cursor with the list data but that excludes the item being deleted:

public Undoable onDismiss(EnhancedListView list, int pos) {
  long id = mAdapter.getItemId(pos);
  String selection = "_id <> " + id;
  mTempCursor = getContentResolver(URI, COLUMNS,  selection. ....);
  mAdapter.swapCursor(mTempCursor);

   // after this then proceed to delete the item from the db
   getContentResolver().delete(.....);
}

Make sure to close the cursor when we get the real cursor with the item already deleted

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
  mAdapter.swapCursor(cursor);
  if(mTempCursor != null) {
    mTempCursor.close();
    mTempCursor = null;
  }
}

Loading the cursor on the onDismiss callback may beat the purpose of using a loader in the first place and may potentially present a performance issue. If performance is an issue for you then using the ProxyCursor approach (see links on previous comment) may be better. This if you are willing to write and override all methods needed to implement the proxy cursor class.

hsanson avatar Oct 02 '13 06:10 hsanson

Thanks for your report. As you mentioned the swapping adapters might not be the finest solution.

I explained here why I cannot fix this issue.

Still I am thinking about good workaround solutions. If I find some free time I will dig a bit into the CursorAdapter and perhaps I will find a solution to add a wrapper (or something similar) to the library, that blocks until deletion has finished.

timroes avatar Oct 02 '13 06:10 timroes

Hi guys. Same problem here. Did you find a workaround for that?

edoardotognoni avatar Mar 25 '14 14:03 edoardotognoni

Hi, I solved it by creating my own adapter which extends BaseAdapter and it has two private members List<ClassA> and Stack<ClassA>. List contains elements provided by given Cursor and Stack excluded elements. So, when you want to delete ClassA from list, you just push it on stack, remove from list and call notifyDataSetChanged(), and when you want to do undo, you just pull it from stack ,put it in list again in its old position and call notifyDataSetChanged() again. You really delete those object in onLoaderReset() by deliberetly calling doRealDelete() in your adapter, which pulls elements from stack one by one and do bulk delete through your ContentProvider class. You obviuosly won't delete them one by one... This approach probably has its flaws but it is doing well in my app.

draskosaric avatar Mar 25 '14 20:03 draskosaric

Another solution could be to convert your cursor adapter to an ArrayList adapter or Base adapter. When onLoadFinished is called, move all cursor items into your ListView adapter. In this way you solve the problem. But as all of other ways, there could be performance issues, because first you have to iterate all cursor items, pass them to the list adapter which will iterate again through all of them. I think this is the simpliest solution but in my case would result in a huge code review.

edoardotognoni avatar Mar 26 '14 08:03 edoardotognoni

If you find better solution, please post it here, since I am looking for better solution myself.

draskosaric avatar Mar 26 '14 08:03 draskosaric

I decided to go for an ArrayAdapter. I'm using 20 records in the worst case, so performances wouldn't be a problem. Please keep me updated if you find better solutions

edoardotognoni avatar Mar 26 '14 10:03 edoardotognoni

Did anyone find a better solution for this issue?

Darclight avatar Apr 02 '14 22:04 Darclight

Finally I moved to ArrayAdapter in onLoadFinished usage as edoardotognoni already told. I believe there could be a performance issue, but it works in my concrete case. Not a solution but workaround of course.

Yougin avatar Apr 18 '14 11:04 Yougin

@timroes maybe a workaround would be to introduce a small delay after the collapse animation. Let's say a method like setDelayAfterCollapse(int delayInMiliseconds). This way the collapse animation would be followed by this delay and adapter would have time to swap the Cursor.

micHar avatar Apr 30 '14 08:04 micHar

I've made an implementation of base cursor adapter class that can be used as workaround for this problem: https://gist.github.com/q1p/0b95633ab9367fb86785 Still not perfect solution, but technically more efficient then listed above and on StackOverflow. It's based on HashSet and default item id (getItemId()), so performance should not be an issue, as contains() method has O(1) time complexity and actually set will contain zero or one item most of the time. Also it's depends on Guava. If you are not using Guava, just replace set construction on line 91. To use it in your project you can just extend this class instead of CursorAdapter and add few lines of code in onDismiss():

    @Override
    public EnhancedListView.Undoable onDismiss(EnhancedListView enhancedListView, int i) {
        adapter.putPendingDismiss(id);
        adapter.notifyDataSetChanged();
        ...
    }

This solution will not work if you're using list dividers (because this adapter displays empty view instead of dismissed item). You should add margins in your item layout to make spacing between items and include divider in item layout.

Have a nice swiping.

v3nko avatar May 05 '14 21:05 v3nko

i meet the same problem

malinkang avatar Jul 12 '14 05:07 malinkang

for me this worked: http://stackoverflow.com/a/25202243/371749

m2kk avatar Nov 18 '14 17:11 m2kk