Вопрос

I have a ListView populated by a ResourceCursorAdapter. I use the loaders mechanism to query a ContentProvider for list items. I detect swipe gestures on the list items to perform some actions on them. New items get added by a background service, so the list can change dynamically.

Everything works fine, except when I start swiping and a database change occurs (as a result of the background service adding a new row). In such case the gesture is not detected properly. I noticed that ACTION_CANCEL is dispatched to the list item view and also that bindView is executed for all visible items. Inside the bindView method I only set some text - I don't change any listeners there.

How can I make gestures work even when new items are being added by the background service? Perhaps there's a way to prevent the motion from being cancelled or I can pause database updates so they don't interrupt the gesture.

Это было полезно?

Решение

You need to decouple updates to the data and to the UI. This is going to be a bit ugly.

1)Don't use a cursor adapter. Load the results from the cursor into some data structure, and make the adapter use that copy of the data. Then when you get new data, create a new copy of that data structure, then save it to the variable the adapter looks at and call notifyDataSetChanged. That way changing the cursor and updating the screen aren't linked.

2)When the service updates the cursor, do not automatically update the listview. Then subclass ListView. When you get an update from the service, save the new data but do not update the listview. Instead, set a flag. In the new ListView subclass, override onTouchEvent so that when a ACTION_UP event occurs, you also check that flag. If the flag is set, turn it false and then notifyDataSetChanged on the adapter. This way, the list will not update until after the user finishes scrolling.

Другие советы

In your ResourceCursorAdapter set a touch listener for your view in newView(). You'll probably need to modify your adapter's constructor to pass a touch listener to use in newView().

I ended up solving this with the following approach.

In my list adapter, I have a flag:

private boolean mIsSwiping;

I set this to true when the user starts swiping, and false when any swipe action completes (which may be via an automated animation).

At the top of getView(), I check the value of the flag, and return early if needed. This prevents re-drawing the wrong list view items.

if (convertView != null && mIsSwiping) {
    return convertView;
}

Also, in the fragment that uses the adapter, I have similar logic, but apply it to the cursor load logic:

private Cursor mNewCursor;

public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    if (mListAdapter.isSwiping()) {
        mNewCursor = cursor;

    } else {
        mListAdapter.swapCursor(cursor);
        mNewCursor = null;

    }
}

And then in the same fragment, just before setting the mListFragment's mIsSwiping flag to false:

if (mNewCursor != null) {
    mListFragment.swapCursor(mNewCursor);
    mNewCursor = null;
}

Hope this helps - it's really just fleshing out Gabe's answer above with some concrete code.

One thing you might need to track separately is whether mNewCursor is closed before swapping it out. I haven't run into that issue myself, but YMMV.

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top