CursorAdapter backed ListView delete animation "flickers" on delete
Asked Answered
R

7

14

I'm trying to implement swipe to delete and in a ListView using the SwipeToDismissUndoList library which extends Roman Nurik's SwipeToDismiss sample.

My issue is in the delete animation. Since the ListView is backed by a CursorAdapter, the animation triggers the onDismiss callback in onAnimationEnd but this means that the animation has run and reset itself before the CursorAdapter updates with the delete.

This ends up looking like a flicker to the user where they delete a note by swiping it away, then the view is back for a split second and then disappears because the CursorAdapter has picked up the data change.

Here is my OnDismissCallback:

private SwipeDismissList.OnDismissCallback dismissCallback = 
        new SwipeDismissList.OnDismissCallback() {
    @Override
    public SwipeDismissList.Undoable onDismiss(ListView listView, final int position) {
        Cursor c = mAdapter.getCursor();
        c.moveToPosition(position);
        final int id = c.getInt(Query._ID);
        final Item item = Item.findById(getActivity(), id);
        if (Log.LOGV) Log.v("Deleting item: " + item);

        final ContentResolver cr = getActivity().getContentResolver();
        cr.delete(Items.buildItemUri(id), null, null);
        mAdapter.notifyDataSetChanged();

        return new SwipeDismissList.Undoable() {
            public void undo() {
                if (Log.LOGV) Log.v("Restoring Item: " + item);
                ContentValues cv = new ContentValues();
                cv.put(Items._ID, item.getId());
                cv.put(Items.ITEM_CONTENT, item.getContent());
                cr.insert(Items.CONTENT_URI, cv);
            }
        };
    }
};
Renwick answered 18/3, 2013 at 0:41 Comment(0)
S
6

I know this question has been tagged as "answered" but as I pointed out in the comments, the problem with using a MatrixCursor is that it's too inefficient. Copying all the rows except for the row to be deleted means that row deletion runs in linear time (linear in the number of items in the listview). For large data and slower phones, that's probably unacceptable.

An alternate approach is to implement your own AbstractCursor that ignores the row to be deleted. This results in the fake row deletion running in constant time and with negligible performance penalty when drawing.

A sample implementation:

public class CursorWithDelete extends AbstractCursor {

private Cursor cursor;
private int posToIgnore;

public CursorWithDelete(Cursor cursor, int posToRemove)
{
    this.cursor = cursor;
    this.posToIgnore = posToRemove;
}

@Override
public boolean onMove(int oldPosition, int newPosition)
{
    if (newPosition < posToIgnore)
    {
        cursor.moveToPosition(newPosition);
    }
    else
    {
        cursor.moveToPosition(newPosition+1);
    }
    return true;
}

@Override
public int getCount()
{
    return cursor.getCount() - 1;
}

@Override
public String[] getColumnNames()
{
    return cursor.getColumnNames();
}

//etc.
//make sure to override all methods in AbstractCursor appropriately

Follow all the steps as before except:

  • In SwipeDismissList.OnDismissCallback.onDismiss(), create new CursorWithDelete.
  • swap over the new cursor
Sidelong answered 9/8, 2013 at 2:7 Comment(4)
onMove() shouldn't always return true, it should return the result of whichever moveToPosition() call is made.Epiblast
This is the right approach but with a couple of issues 1. I'd suggest to use CursorWrapper instead of AbstractCursor because that's what we are doing here, wrap a cursor. 2. It's not sufficient to override onMove, you need to override all position related methods (move, moveToFirst, moveToLast, moveToNext, moveToPosition, moveToPrevious, isBeforeFirst, isFirst, isLast, isAfterLast). Also getPosition needs to be overridden because the position the wrapper returns is different from the one the actual cursor is on, meaning we need to keep track of the virtual position. 3. see drewhannay's commentRachmaninoff
@EmanuelMoecklin hey, do you mind reposting that code snippet? The link no longer works. Thank you.Ringhals
Thanks, i've implemented CursorWithDelete based on your solution which handles multiple deleted positions. Hope it will be helpful to somebody https://mcmap.net/q/809269/-cursoradapter-backed-listview-delete-animation-quot-flickers-quot-on-deleteFidel
M
4

I think SwipeToDismissUndoList is not good for cursor-based adapters. Because the adapters rely on changes from content providers (setNotificationUri() or registerContentObserver()…) to update UI. You don't know when the data is available or not. That's the problem you're facing.

I think there is something like a trick. You can use MatrixCursor.

  • In onLoadFinished(Loader, Cursor), you keep a reference to the cursor returned from content provider. You need to close it manually later.
  • In SwipeDismissList.OnDismissCallback.onDismiss(), create new MatrixCursor, copy all items from the current cursor except the items that are being removed.
  • Set the newly created matrix cursor to the adapter with swapCursor() (not changeCursor()). Because swapCursor() doesn't close the old cursor. You need to keep it open so the loader works properly.
  • Now the UI is updated, you make a call to getContentResolver().delete() and actually remove the items that the user wanted to remove. When the content provider finishes deleting data, it notifies the original cursor to reload data.
  • Make sure to close the original cursor that you swapped. For example:

    private Cursor mOrgCursor;
    
    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        if (mOrgCursor != null)
            mOrgCursor.close();
        mOrgCursor = data;
        mAdapter.changeCursor(mOrgCursor);
    }
    
    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        if (mOrgCursor != null) {
            mOrgCursor.close();
            mOrgCursor = null;
        }
        mAdapter.changeCursor(null);
    }
    
  • Don't worry about the matrix cursor, changeCursor() will close it.
Marijn answered 19/3, 2013 at 14:12 Comment(4)
Dude, this is the best answer I've ever received on SO. It's so detailed, nice work. It only took me 5 hours to implement, but that was probably the whiskey :)Renwick
@Renwick Thank you. You're welcome. And your question gave me a useful library too :-) It also gave me a potential problem that I'd ever face (but now it's solved :-D )Marijn
I'm having the exact same problem (#18050181). However, I'm worried the MatrixCursor is too inefficient---you get a normal cursor, then copy it over to the matrixcursor. Ditto when you delete the row. If you have many rows, you probably slowed it down significantly. Other than that, it works fine.Sidelong
This solution is too complicated and too slow. Using a CursorWrapper is the way to go. Here's a code snippet: chopapp.com/#ifj74ch4Rachmaninoff
A
3

Hey I had a similar problem and solved like this, hope it helps you:

I used what Chet Haase showed in this devbyte: http://www.youtube.com/watch?v=YCHNAi9kJI4

It's very similar to Roman's code, but here he uses a ViewTreeObserver, so after you deleted the item from the adapter, but before the list is redrawn, you have time to animate closing the gap, and it won't flicker. Another difference is, that he sets the Listener to each view(item) of the list in the adapter, and not on the ListView itself.

So a sample of my code:

This is the ListActivity's onCreate, here I pass the listener to the adapter nothing special:

ListAdapterTouchListener listAdapterTouchListener = new ListAdapterTouchListener(getListView());
    listAdapter = new ListAdapter(this,null,false,listAdapterTouchListener);

Here is part of the ListAdapter(it's my own adapter that extends the CursorAdapter), I pass the Listener in the Constructor,

private View.OnTouchListener onTouchListener;

public ListAdapter(Context context, Cursor c, boolean autoRequery,View.OnTouchListener listener) {
    super(context, c, autoRequery);
    onTouchListener = listener;
}

and then in the newView method I set it to the view:

@Override
public View newView(final Context context, Cursor cursor, ViewGroup parent) {
    View view = layoutInflater.inflate(R.layout.list_item,parent,false);
    // here should be some viewholder magic to make it faster
    view.setOnTouchListener(onTouchListener);

    return view;
}

The listener is mostly the same as in the code shown in the video, I don't use the backgroundcontainer but that's just my choice. So the animateRemoval has the part what is interesting, here it is:

private void animateRemoval(View viewToRemove){
    for(int i=0;i<listView.getChildCount();i++){
        View child = listView.getChildAt(i);
        if(child!=viewToRemove){

        // since I don't have stableIds I use the _id from the sqlite database
        // I'm adding the id to the viewholder in the bindView method in the ListAdapter

            ListAdapter.ViewHolder viewHolder = (ListAdapter.ViewHolder)child.getTag();
            long itemId = viewHolder.id;
            itemIdTopMap.put(itemId, child.getTop());
        }
    }

    // I'm using content provider with LoaderManager in the activity because it's more efficient, I get the id from the viewholder

    ListAdapter.ViewHolder viewHolder = (ListAdapter.ViewHolder)viewToRemove.getTag();
    long removeId = viewHolder.id;

    //here you remove the item

    listView.getContext().getContentResolver().delete(Uri.withAppendedPath(MyContentProvider.CONTENT_ID_URI_BASE,Long.toString(removeId)),null,null);

    // after the removal get a ViewTreeObserver, so you can set a PredrawListener
    // the rest of the code is pretty much the same as in the sample shown in the video

    final ViewTreeObserver observer = listView.getViewTreeObserver();
    observer.addOnPreDrawListener(new ViewTreeObserver.OnPreDrawListener() {
        @Override
        public boolean onPreDraw() {
            observer.removeOnPreDrawListener(this);
            boolean firstAnimation = true;
            for(int i=0;i<listView.getChildCount();i++){
                final View child = listView.getChildAt(i);
                ListAdapter.ViewHolder viewHolder = (ListAdapter.ViewHolder)child.getTag();
                long itemId = viewHolder.id;
                Integer startTop = itemIdTopMap.get(itemId);
                int top = child.getTop();
                if(startTop!=null){
                    if (startTop!=top) {
                        int delta=startTop-top;
                        child.setTranslationY(delta);
                        child.animate().setDuration(MOVE_DURATION).translationY(0);
                        if(firstAnimation){
                            child.animate().setListener(new Animator.AnimatorListener() {
                                @Override
                                public void onAnimationStart(Animator animation) {

                                }

                                @Override
                                public void onAnimationEnd(Animator animation) {
                                        swiping=false;
                                    listView.setEnabled(true);
                                }

                                @Override
                                public void onAnimationCancel(Animator animation) {

                                }

                                @Override
                                public void onAnimationRepeat(Animator animation) {

                                }
                            });
                            firstAnimation=false;
                        }
                    }
                }else{
                    int childHeight = child.getHeight()+listView.getDividerHeight();
                    startTop = top+(i>0?childHeight:-childHeight);
                    int delta = startTop-top;
                    child.setTranslationY(delta);
                    child.animate().setDuration(MOVE_DURATION).translationY(0);
                    if(firstAnimation){
                        child.animate().setListener(new Animator.AnimatorListener() {
                            @Override
                            public void onAnimationStart(Animator animation) {

                            }

                            @Override
                            public void onAnimationEnd(Animator animation) {
                                swiping=false;
                                listView.setEnabled(true);
                            }

                            @Override
                            public void onAnimationCancel(Animator animation) {

                            }

                            @Override
                            public void onAnimationRepeat(Animator animation) {

                            }
                        });
                        firstAnimation=false;
                    }
                }
            }
            itemIdTopMap.clear();
            return true;
        }
    });
}

Hope this helps you, it's working nice for me! You really should watch the devbyte, it helped me a lot!

Alexio answered 6/11, 2013 at 10:55 Comment(0)
H
3

At the moment of posting this answer I've tried all listed approaches from this thread. CursorWrapper is most efficient from point of performance but unfortunately not safe, as there no guarantee that position of dismissed item is stable (if data can be changed from another source, for example by background sync). Alternatevely, you can try my simple implementation of base cursor adapter:

/*
 * Copyright (C) 2014. Victor Kosenko (http://qip-blog.eu.org)
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

// your package here

import android.content.Context;
import android.database.Cursor;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CursorAdapter;

import com.google.api.client.util.Sets;

import java.util.Set;

/**
 * This is basic implementation of swipable cursor adapter that allows to skip displaying dismissed
 * items by replacing them with empty view. This adapter overrides default implementation of
 * {@link #getView(int, android.view.View, android.view.ViewGroup)}, so if you have custom
 * implementation of this method you should review it according to logic of this adapter.
 *
 * @author Victor Kosenko
 */
public abstract class BaseSwipableCursorAdapter extends CursorAdapter {

    protected static final int VIEW_ITEM_NORMAL = 0;
    protected static final int VIEW_ITEM_EMPTY = 1;

    protected Set<Long> pendingDismissItems;
    protected View emptyView;
    protected LayoutInflater inflater;

    /**
     * If {@code true} all pending items will be removed on cursor swap
     */
    protected boolean flushPendingItemsOnSwap = true;

    /**
     * @see android.widget.CursorAdapter#CursorAdapter(android.content.Context, android.database.Cursor, boolean)
     */
    public BaseSwipableCursorAdapter(Context context, Cursor c, boolean autoRequery) {
        super(context, c, autoRequery);
        init(context);
    }

    /**
     * @see android.widget.CursorAdapter#CursorAdapter(android.content.Context, android.database.Cursor, int)
     */
    protected BaseSwipableCursorAdapter(Context context, Cursor c, int flags) {
        super(context, c, flags);
        init(context);
    }

    /**
     * Constructor with {@code null} cursor and enabled autoRequery
     *
     * @param context The context
     */
    protected BaseSwipableCursorAdapter(Context context) {
        super(context, null, true);
        init(context);
    }

    /**
     * @param context                 The context
     * @param flushPendingItemsOnSwap If {@code true} all pending items will be removed on cursor swap
     * @see #BaseSwipableCursorAdapter(android.content.Context)
     */
    protected BaseSwipableCursorAdapter(Context context, boolean flushPendingItemsOnSwap) {
        super(context, null, true);
        init(context);
        this.flushPendingItemsOnSwap = flushPendingItemsOnSwap;
    }

    protected void init(Context context) {
        inflater = LayoutInflater.from(context);
        pendingDismissItems = Sets.newHashSet();
        emptyView = new View(context);
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        if (!getCursor().moveToPosition(position)) {
            throw new IllegalStateException("couldn't move cursor to position " + position);
        }
        if (isPendingDismiss(position)) {
            return emptyView;
        } else {
            return super.getView(position, convertView, parent);
        }
    }

    @Override
    public int getViewTypeCount() {
        return 2;
    }

    @Override
    public int getItemViewType(int position) {
        return pendingDismissItems.contains(getItemId(position)) ? VIEW_ITEM_EMPTY : VIEW_ITEM_NORMAL;
    }

    /**
     * Add item to pending dismiss. This item will be ignored in
     * {@link #getView(int, android.view.View, android.view.ViewGroup)} when displaying list of items
     *
     * @param id Id of item that needs to be added to pending for dismiss
     * @return {@code true} if this item already in collection if pending items, {@code false} otherwise
     */
    public boolean putPendingDismiss(Long id) {
        return pendingDismissItems.add(id);
    }

    /**
     * Confirm that specified item is no longer present in underlying cursor. This method should be
     * called after the fact of removing this item from result set of underlying cursor.
     * If you're using flushPendingItemsOnSwap flag there is no need to call this method.
     *
     * @param id Id of item
     * @return {@code true} if this item successfully removed from pending to dismiss, {@code false}
     * if it's not present in pending items collection
     */
    public boolean commitDismiss(Long id) {
        return pendingDismissItems.remove(id);
    }

    /**
     * Check if this item should be ignored
     *
     * @param position Cursor position
     * @return {@code true} if this item should be ignored, {@code false} otherwise
     */
    public boolean isPendingDismiss(int position) {
        return getItemViewType(position) == VIEW_ITEM_EMPTY;
    }

    public boolean isFlushPendingItemsOnSwap() {
        return flushPendingItemsOnSwap;
    }

    /**
     * Automatically flush pending items when calling {@link #swapCursor(android.database.Cursor)}
     *
     * @param flushPendingItemsOnSwap If {@code true} all pending items will be removed on cursor swap
     */
    public void setFlushPendingItemsOnSwap(boolean flushPendingItemsOnSwap) {
        this.flushPendingItemsOnSwap = flushPendingItemsOnSwap;
    }

    @Override
    public Cursor swapCursor(Cursor newCursor) {
        if (flushPendingItemsOnSwap) {
            pendingDismissItems.clear();
        }
        return super.swapCursor(newCursor);
    }
}

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() (if you're using EnhancedListView or similar library):

@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.

This code can be updated in future, so I've posted it on github gist: https://gist.github.com/q1p/0b95633ab9367fb86785

Also, I want to recommend you to not use I/O operations in main thread like in your example :)

Hebert answered 6/5, 2014 at 20:57 Comment(1)
By far it seems to be the most efficient and elegant approach of all described hereNutshell
B
3

Just come here with the same problem and resolved perfectly and easily with the code provided by Emanuel Moecklin.

It's really simple: Inside the onDismiss method, do this:

        //Save cursor for later
        Cursor cursor = mAdapter.getCursor();
        SwipeToDeleteCursorWrapper cursorWrapper = new SwipeToDeleteCursorWrapper(mAdapter.getCursor(), reverseSortedPositions[0]);
        mAdapter.swapCursor(cursorWrapper);
        //Remove the data from the database using the cursor

And then create SwipteToDeleteCursorWrapper as wrote by Emanuel:

public class SwipeToDeleteCursorWrapper extends CursorWrapper
{
    private int mVirtualPosition;
    private int mHiddenPosition;

    public SwipeToDeleteCursorWrapper(Cursor cursor, int hiddenPosition)
    {
        super(cursor);
        mVirtualPosition = -1;
        mHiddenPosition = hiddenPosition;
    }

    @Override
    public int getCount()
    {
        return super.getCount() - 1;
    }

    @Override
    public int getPosition()
    {
        return mVirtualPosition;
    }

    @Override
    public boolean move(int offset)
    {
        return moveToPosition(getPosition() + offset);
    }

    @Override
    public boolean moveToFirst()
    {
        return moveToPosition(0);
    }

    @Override
    public boolean moveToLast()
    {
        return moveToPosition(getCount() - 1);
    }

    @Override
    public boolean moveToNext()
    {
        return moveToPosition(getPosition() + 1);
    }

    @Override
    public boolean moveToPosition(int position)
    {
        mVirtualPosition = position;
        int cursorPosition = position;
        if (cursorPosition >= mHiddenPosition)
        {
            cursorPosition++;
        }
        return super.moveToPosition(cursorPosition);
    }

    @Override
    public boolean moveToPrevious()
    {
        return moveToPosition(getPosition() - 1);
    }

    @Override
    public boolean isBeforeFirst()
    {
        return getPosition() == -1 || getCount() == 0;
    }

    @Override
    public boolean isFirst()
    {
        return getPosition() == 0 && getCount() != 0;
    }

    @Override
    public boolean isLast()
    {
        int count = getCount();
        return getPosition() == (count - 1) && count != 0;
    }

    @Override
    public boolean isAfterLast()
    {
        int count = getCount();
        return getPosition() == count || count == 0;
    }
}

That's all!

Blackhead answered 8/8, 2014 at 11:15 Comment(1)
What is reverseSortedPositions here?Tertias
B
2

(This answer is with regards to Roman Nuriks library. For libraries branched from that, it should be similar).

This issue happens because these libraries want to recycle the deleted views. Basically after a row item is animated to disappear, the library resets it to its original position and look, so that the listView can reuse it. There are two workarounds.

Solution 1

in performDismiss(...) method of the library, find the portion of code that resets the dismissed view. This is the portion:

ViewGroup.LayoutParams lp;
for (PendingDismissData pendingDismiss : mPendingDismisses) {
   // Reset view presentation
   pendingDismiss.view.setAlpha(1f);
   pendingDismiss.view.setTranslationX(0);
   lp = pendingDismiss.view.getLayoutParams();
   lp.height = originalHeight;
   pendingDismiss.view.setLayoutParams(lp);
}

mPendingDismisses.clear();

Remove this portion and put it in a separate public method:

/**
 * Resets the deleted view objects to their 
 * original form, so that they can be reused by the
 * listview. This should be called after listview has 
 * the refreshed data available, e.g., in the onLoadFinished
 * method of LoaderManager.LoaderCallbacks interface.
 */
public void resetDeletedViews() {
    ViewGroup.LayoutParams lp;
    for (PendingDismissData pendingDismiss : mPendingDismisses) {
        // Reset view presentation
        pendingDismiss.view.setAlpha(1f);
        pendingDismiss.view.setTranslationX(0);
        lp = pendingDismiss.view.getLayoutParams();
        lp.height = originalHeight;
        pendingDismiss.view.setLayoutParams(lp);
    }

    mPendingDismisses.clear();
}

Finally, in your main activity, call this method when the new cursor is ready.

Solution 2

Forget about recycling the row item (it is just one row, after all). Rather than resetting the view and preparing it for recycle, somehow mark it as stained in performDismiss(...) method of the library.

Then when filling your listView (by overriding the adapter's getView(View convertView, ...) method), check for that mark on the convertView object. If it is there, don't use convertView. For example you can do (the following piece is a pseudo-code)

if (convertView is marked as stained) {
   convertView = null;
}
return super.getView(convertView, ...);
Baiss answered 30/7, 2013 at 3:28 Comment(3)
I'd tried both approaches with limited success. For (1), I found that when I swapcursors in onLoadFinished(), it redraws the listview twice (once with old and once with new data). For (2), I'm unable to track the stained view when scrolling has happened.Sidelong
@UAvalos (1): this method does not alter the way listView's data is loaded. It just keeps the dismissed view hidden until the new data is available. Did you mean you still experienced the flicker? (2): You don't need to track it anymore. All that matters is the first time that you detect it, it is going to be trashed for good. After that, it is non-existent. Basically the check for the stain mark succeeds only the first time that the view wants to be re-used. After that the stained view does not exist anymore.Baiss
(1) Yes, if I didn't track a stained view. (2) Actually, this may have been a problem with my touch listener.Sidelong
F
1

Based on U Avalos answer i've implemented Cursor wrapper which handles multiple deleted positions. However solution is not yet fully tested and may contain bugs. Use it like this when you setting a cursor

mAdapter.changeCursor(new CursorWithDelete(returnCursor));

If you want to hide some item from the list

CursorWithDelete cursor = (CursorWithDelete) mAdapter.getCursor();
cursor.deleteItem(position);
mAdapter.notifyDataSetChanged();

CusrsorWithDelete.java

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;

import android.database.AbstractCursor;
import android.database.Cursor;

public class CursorWithDelete extends AbstractCursor {
    private List<Integer> positionsToIgnore = new ArrayList<Integer>();
    private Cursor cursor;

    public CursorWithDelete(Cursor cursor) {
        this.cursor = cursor;
    }

    @Override
    public boolean onMove(int oldPosition, int newPosition) {
        cursor.moveToPosition(adjustPosition(newPosition));
        return true;
    }

    public int adjustPosition(int newPosition) {
        int ix = Collections.binarySearch(positionsToIgnore, newPosition);
        if (ix < 0) {
            ix = -ix - 1;
        } else {
            ix++;
        }
        int newPos;
        int lastRemovedPosition;
        do {
            newPos = newPosition + ix;
            lastRemovedPosition = positionsToIgnore.size() == ix ? -1 : positionsToIgnore.get(ix);
            ix++;
        } while (lastRemovedPosition >= 0 && newPos >= lastRemovedPosition);
        return newPos;
    }

    @Override
    public int getCount() {
        return cursor.getCount() - positionsToIgnore.size();
    }

    @Override
    public String[] getColumnNames() {
        return cursor.getColumnNames();
    }

    @Override
    public String getString(int column) {
        return cursor.getString(column);
    }

    @Override
    public short getShort(int column) {
        return cursor.getShort(column);
    }

    @Override
    public int getInt(int column) {
        return cursor.getInt(column);
    }

    @Override
    public long getLong(int column) {
        return cursor.getLong(column);
    }

    @Override
    public float getFloat(int column) {
        return cursor.getFloat(column);
    }

    @Override
    public double getDouble(int column) {
        return cursor.getDouble(column);
    }

    @Override
    public boolean isNull(int column) {
        return cursor.isNull(column);
    }

    /**
     * Call if you want to hide some position from the result
     * 
     * @param position in the AdapterView, not the cursor position
     */
    public void deleteItem(int position) {
        position = adjustPosition(position);
        int ix = Collections.binarySearch(positionsToIgnore, position);
        positionsToIgnore.add(-ix - 1, position);
    }
}
Fidel answered 23/4, 2014 at 8:57 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.