Filtering a cursor the right way?

前端 未结 3 1558
我在风中等你
我在风中等你 2020-12-04 22:41

At the moment I need to filter a Cursor/CursorAdapter to only show rows that match a specific condition in the ListView. I don\'t want to requery the db all the time. I just

相关标签:
3条回答
  • 2020-12-04 23:12

    UPDATE:

    I have rewritten the source and my employer has made it available as open source software: https://github.com/clover/android-filteredcursor

    You don't need to override all the move methods in CursorWrapper, you do need to override a bunch though due to the design of the Cursor interface. Let's pretend you want to filter out row #2 and #4 of a 7 row cursor, make a class that extends CursorWrapper and override these methods like so:

    private int[] filterMap = new int[] { 0, 1, 3, 5, 6 };
    private int mPos = -1;
    
    @Override
    public int getCount() { return filterMap.length }
    
    @Override
    public boolean moveToPosition(int position) {
        // Make sure position isn't past the end of the cursor
        final int count = getCount();
        if (position >= count) {
            mPos = count;
            return false;
        }
    
        // Make sure position isn't before the beginning of the cursor
        if (position < 0) {
            mPos = -1;
            return false;
        }
    
        final int realPosition = filterMap[position];
    
        // When moving to an empty position, just pretend we did it
        boolean moved = realPosition == -1 ? true : super.moveToPosition(realPosition);
        if (moved) {
            mPos = position;
        } else {
            mPos = -1;
        }
        return moved;
    }
    
    @Override
    public final boolean move(int offset) {
        return moveToPosition(mPos + offset);
    }
    
    @Override
    public final boolean moveToFirst() {
        return moveToPosition(0);
    }
    
    @Override
    public final boolean moveToLast() {
        return moveToPosition(getCount() - 1);
    }
    
    @Override
    public final boolean moveToNext() {
        return moveToPosition(mPos + 1);
    }
    
    @Override
    public final boolean moveToPrevious() {
        return moveToPosition(mPos - 1);
    }
    
    @Override
    public final boolean isFirst() {
        return mPos == 0 && getCount() != 0;
    }
    
    @Override
    public final boolean isLast() {
        int cnt = getCount();
        return mPos == (cnt - 1) && cnt != 0;
    }
    
    @Override
    public final boolean isBeforeFirst() {
        if (getCount() == 0) {
            return true;
        }
        return mPos == -1;
    }
    
    @Override
    public final boolean isAfterLast() {
        if (getCount() == 0) {
            return true;
        }
        return mPos == getCount();
    }
    
    @Override
    public int getPosition() {
        return mPos;
    }
    

    Now the interesting part is creating the filterMap, that's up to you.

    0 讨论(0)
  • 2020-12-04 23:16

    I've compared iterating through cursor 1790 entries against query in cursor with REGEXP and it is 1 min 15 sec against 15 sec.

    Use REGEXP - it is much faster.

    0 讨论(0)
  • 2020-12-04 23:37

    I was looking for something similar, in my case I wanted to filter items based on a string comparision. I found this gist https://gist.github.com/ramzes642/5400792, which works fine unless you start playing around with the position of the cursor. So I found satur9nine answer, his one respects the position api but just needs some adjustments for filtering based on cursor, so I merged the two. You can change your code to fit it: https://gist.github.com/rfreitas/ab46edbdc41500b20357

    import java.text.Normalizer;
    
    import android.database.Cursor;
    import android.database.CursorWrapper;
    import android.util.Log;
    
    
    //by Ricardo derfreitas@gmail.com
    //ref: https://gist.github.com/ramzes642/5400792 (the position retrieved is not correct)
    //ref: http://stackoverflow.com/a/7343721/689223 (doesn't do string filtering)
    //the two code bases were merged to get the best of both worlds
    //also added was an option to remove accents from UTF strings
    public class FilterCursorWrapper extends CursorWrapper {
        private static final String TAG = FilterCursorWrapper.class.getSimpleName();
        private String filter;
        private int column;
        private int[] filterMap;
        private int mPos = -1;
        private int mCount = 0;
    
        public FilterCursorWrapper(Cursor cursor,String filter,int column) {
            super(cursor);
            this.filter = deAccent(filter).toLowerCase();
            Log.d(TAG, "filter:"+this.filter);
            this.column = column;
            int count = super.getCount();
    
            if (!this.filter.isEmpty()) {
                this.filterMap = new int[count];
                int filteredCount = 0;
                for (int i=0;i<count;i++) {
                    super.moveToPosition(i);
                    if (deAccent(this.getString(this.column)).toLowerCase().contains(this.filter)){
                        this.filterMap[filteredCount] = i;
                        filteredCount++;
                    }
                }
                this.mCount = filteredCount;
            } else {
                this.filterMap = new int[count];
                this.mCount = count;
                for (int i=0;i<count;i++) {
                    this.filterMap[i] = i;
                }
            }
    
            this.moveToFirst();
        }
    
    
    
        public int getCount() { return this.mCount; }
    
        @Override
        public boolean moveToPosition(int position) {
            Log.d(TAG,"moveToPosition:"+position);
            // Make sure position isn't past the end of the cursor
            final int count = getCount();
            if (position >= count) {
                mPos = count;
                return false;
            }
            // Make sure position isn't before the beginning of the cursor
            if (position < 0) {
                mPos = -1;
                return false;
            }
            final int realPosition = filterMap[position];
            // When moving to an empty position, just pretend we did it
            boolean moved = realPosition == -1 ? true : super.moveToPosition(realPosition);
            if (moved) {
                mPos = position;
            } else {
                mPos = -1;
            }
            Log.d(TAG,"end moveToPosition:"+position);
            return moved;
        }
        @Override
        public final boolean move(int offset) {
            return moveToPosition(mPos + offset);
        }
        @Override
        public final boolean moveToFirst() {
            return moveToPosition(0);
        }
        @Override
        public final boolean moveToLast() {
            return moveToPosition(getCount() - 1);
        }
        @Override
        public final boolean moveToNext() {
            return moveToPosition(mPos + 1);
        }
        @Override
        public final boolean moveToPrevious() {
            return moveToPosition(mPos - 1);
        }
        @Override
        public final boolean isFirst() {
            return mPos == 0 && getCount() != 0;
        }
        @Override
        public final boolean isLast() {
            int cnt = getCount();
            return mPos == (cnt - 1) && cnt != 0;
        }
        @Override
        public final boolean isBeforeFirst() {
            if (getCount() == 0) {
                return true;
            }
            return mPos == -1;
        }
        @Override
        public final boolean isAfterLast() {
            if (getCount() == 0) {
                return true;
            }
            return mPos == getCount();
        }
        @Override
        public int getPosition() {
            return mPos;
        }
    
        //added by Ricardo
        //ref: http://stackoverflow.com/a/22612054/689223
        //other: http://stackoverflow.com/questions/8523631/remove-accents-from-string
        //other: http://stackoverflow.com/questions/15190656/easy-way-to-remove-utf-8-accents-from-a-string
        public static String deAccent(String str) {
            //return StringUtils.stripAccents(str);//this method from apache.commons respects chinese characters, but it's slower than flattenToAscii
            return flattenToAscii(str);
        }
    
        //ref: http://stackoverflow.com/a/15191508/689223
        //this is the fastest method using the normalizer found yet, the ones using Regex are too slow
        public static String flattenToAscii(String string) {
            char[] out = new char[string.length()];
            string = Normalizer.normalize(string, Normalizer.Form.NFD);
            int j = 0;
            for (int i = 0, n = string.length(); i < n; ++i) {
                char c = string.charAt(i);
                int type = Character.getType(c);
                if (type != Character.NON_SPACING_MARK){
                    out[j] = c;
                    j++;
                }
            }
            return new String(out);
        }
    }
    
    0 讨论(0)
提交回复
热议问题