AutoCompleteTextView при поддержке CursorLoader

Так что у меня возникли проблемы с расширениемMultiAutoCompleteTextView и поддерживая егоCursorLoader, одновременно используя пользовательскийTokenizer, Проблема возникает именно сmAdapter.setCursorToStringConverter(); вызов.convertToString() Метод, имеющий в качестве аргумента Курсор, имеет действительный и незамкнутый курсор при первом вызове этого метода. Однако последующие вызовы приводят либо к нулевому курсору, либо к закрытому курсору. Я предполагаю, что это как-то связано с тем, какLoaderManager управляетCursorLoader.

Если я прокомментируюsetCursorToStringConverter() метод, затем я вижу список доступных вариантов на основе текста, который я ввел в это представление. Тем не менее, так как нетconvertToString() метод, тоterminateToken () пользовательский методTokenizer не получает строку, которую я намереваюсь, а скорее репрезентативную строку объекта курсора, поскольку курсор не использовался для получения текущего значения строки нужного столбца в результирующем запросе.

Кто-нибудь смог реализовать комбинацию трех классов (CursorLoader/LoaderManger, MultiAutoCompleteTextView, а такжеTokenizer)

Я иду в правильном направлении с этим, или это просто невозможно?

Я смог реализовать кастомMultiAutoCompleteTextView при поддержкеSimpleCursorAdapter наряду с обычаемTokenizer, Мне просто интересно, если это возможно реализовать это с помощьюCursorLoader вместо этого, поскольку строгий режим жалуется на курсор вMultiAutoCompleteTextView не будучи явно закрытым.

Любая помощь будет принята с благодарностью.

public class CustomMultiAutoCompleteTextView extends MultiAutoCompleteTextView
  implements LoaderManager.LoaderCallbacks<Cursor> {

    private final String DEBUG_TAG = getClass().getSimpleName().toString();
    private Messenger2 mContext;
    private RecipientsCursorAdapter mAdapter;
    private ContentResolver mContentResolver;
    private final char delimiter = ' ';
    private CustomMultiAutoCompleteTextView mView;

    // If non-null, this is the current filter the user has provided.
    private String mCurFilter;

    // These are the Contacts rows that we will retrieve.
    final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
        ContactsContract.Contacts._ID,
        ContactsContract.Contacts.DISPLAY_NAME };

    public CustomMultiAutoCompleteTextView(Context c) {
        super(c);
        init(c);
    }

    public CustomMultiAutoCompleteTextView(Context c, AttributeSet attrs) {
        super(c, attrs);
        init(c);
    }

    private void init(Context context) {
        mContext = (Messenger2) context;
        mContentResolver = mContext.getContentResolver();
        mView = this; 

        mAdapter = new RecipientsCursorAdapter(mContext, 0, null, new String[0], new int[0], mContext);

        mAdapter.setCursorToStringConverter(new CursorToStringConverter() {
            @Override
            public CharSequence convertToString(Cursor c) {
                String contactName = c.getString(c.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME));
                return contactName;
            }
        });

        addTextChangedListener(new TextWatcher() {
            @Override
            public void beforeTextChanged(CharSequence s, int start, int count, int after) {
            }

            @Override
            public void onTextChanged(CharSequence s, int start, int before, int count) {
                Log.d(DEBUG_TAG, "onTextChanged()");
                if (!s.equals(""))
                    mCurFilter = s.toString();
                else
                    mCurFilter = "";

                mContext.getLoaderManager().restartLoader(0, null, mView);

            }

            @Override
            public void afterTextChanged(Editable s) {
            }
        });

        setAdapter(mAdapter);
        setTokenizer(new SpaceTokenizer());

        mContext.getLoaderManager().initLoader(0, null, this);

    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // This is called when a new Loader needs to be created. This
        // sample only has one Loader, so we don't care about the ID.
        // First, pick the base URI to use depending on whether we are
        // currently filtering.
        Log.d(DEBUG_TAG, "onCreateLoader()");
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath( ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        // Now create and return a CursorLoader that will take care of
        // creating a Cursor for the data being displayed.
        String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
                + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
                + " COLLATE LOCALIZED ASC";

        return new CursorLoader(mContext, baseUri, CONTACTS_SUMMARY_PROJECTION,
                selection, null, sortOrder);
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        // Swap the new cursor in. (The framework will take care of closing
        // the old cursor once we return.)
        Log.d(DEBUG_TAG, "onLoadFinished()");
        mAdapter.swapCursor(data);

    }

    public void onLoaderReset(Loader<Cursor> loader) {
        // This is called when the last Cursor provided to onLoadFinished()
        // above is about to be closed. We need to make sure we are no
        // longer using it.
        Log.d(DEBUG_TAG, "onLoaderReset()");
        mAdapter.swapCursor(null);
    }

    private class SpaceTokenizer implements Tokenizer {

        public int findTokenStart(CharSequence text, int cursor) {
            int i = cursor;

            while (i > 0 && text.charAt(i - 1) != delimiter) {
                i--;
            }
            while (i < cursor && text.charAt(i) == delimiter) {
                i++;
            }

            return i;
        }

        public int findTokenEnd(CharSequence text, int cursor) {
            int i = cursor;
            int len = text.length();

            while (i < len) {
                if (text.charAt(i) == delimiter) {
                    return i;
                } else {
                    i++;
                }
            }

            return len;
        }

        public CharSequence terminateToken(CharSequence text) {
            Log.d(DEBUG_TAG, "terminateToken()");
            int i = text.length();
            while (i > 0 && text.charAt(i - 1) == delimiter) {
                i--;
            }

            if (i > 0 && text.charAt(i - 1) == delimiter) {
                return text;
            } else {

                CharSequence contactName = createContactBubble(text);

                return contactName;
            }
        }

    }

}

ОБНОВЛЕНИЕ 1

Я сейчас звонюsetStringConversionColumn() метод вместоsetCursorToStringConverter() как предложил @Olaf. Я установил это вonLoadFinished() так как это единственный раз, когдаCursor доступно, так как это реализуетLoaderManger.

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in. (The framework will take care of closing
    // the old cursor once we return.)
    Log.d(DEBUG_TAG, "onLoadFinished()");   
    mAdapter.setStringConversionColumn(data.getColumnIndexOrThrow(ContactsContract.Contacts.DISPLAY_NAME)); 
    mAdapter.swapCursor(data);
}

Это работает при выборе одного элемента дляMultiAutoCompleteTextView, но не позволит выбрать несколько элементов вMultiAutoCompleteTextView.

Я предполагаю, что есть некоторая проблема сonTextChanged() метод, так как он вызываетrestartLoader(), Это работает для первой записи в этом представлении, но не для последующих записей. Я не слишком уверен в этом, что не так.

ОБНОВЛЕНИЕ 2

Итак, я определил проблему. Проблема заключается в TextWatcher'sonTextChanged() метод. Сделав выбор для завершения первого токена (скажем, токен был «Джо Джонсон»), затем введите в него больше символовMultiAutoCompleteTextView ( такие какal ) значение аргs который передается вonTextChanged() метод теперь содержит не только дополнительно добавленные символы, но также символы из токена, который был ранее завершен (значениеs на данный моментJoe Johnson al ). Теперь значениеmCursor устанавливается наJoe Johnson al который впоследствии передается в запрос вonCreateLoader() который, очевидно, не даст никаких результатов. Есть ли способы обойти эту ситуацию? Я открыт для любых предложений.

ОБНОВЛЕНИЕ 3

Когда я реализовал кастомMultiAutoCompleteTextView при поддержкеSimpleCursorAdapter наряду с обычаемTokenizer Я установилFilterQueryProvider нравится:

mAdapter.setFilterQueryProvider(new FilterQueryProvider() {
    @Override
    public Cursor runQuery(CharSequence constraint) {
    Log.d(DEBUG_TAG, "runQuery() : constraint " + constraint);
        Uri baseUri;
        if (constraint != null) {
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                Uri.encode(constraint.toString()));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
            }

        String selection = "((" + ContactsContract.Contacts.DISPLAY_NAME
            + " NOTNULL) AND ("
            + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";

        final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME};
        String sortOrder = ContactsContract.Contacts.DISPLAY_NAME
            + " COLLATE LOCALIZED ASC";

        Cursor c = mContentResolver.query(baseUri,
    CONTACTS_SUMMARY_PROJECTION, selection, null, sortOrder);
        return c;
    }
});

И по какой-то причинеrunQuery() метод вызывается дважды из TextWatcher'sonTextChanged() метод:

public void onTextChanged(CharSequence s, int start, int before,
                int count) {
    Log.d(DEBUG_TAG, "onTextChanged()  : s " + s);
    mAdapter.getFilterQueryProvider().runQuery(s);
}

Так что в моем предыдущем примереconstraint переменная, которая передается вrunQuery() метод первый разJoe Johnson al, Потом второй разrunQuery() Метод называется значениемconstraint переменнаяal, Я не знаю почемуrunQuery() метод выполняется дважды, когда его вызывают только один раз вonTextChanged() метод.

 Olaf Dietsche22 окт. 2012 г., 11:15
В вашем случае вы можете использоватьsetStringConversionColumn() вместоsetCursorToStringConverter().
 JanithaR27 апр. 2014 г., 20:23
Просто чтобы понять, что вы делаете, вы запрашиваете очень большой объем данных на изменение текста, верно? Это действительно необходимо в вашем случае?

Ответы на вопрос(1)

автоматическое заполнение текста в Android не очень эффективно, когда мне приходится иметь дело с большими объемами данных, я делаю так, чтобы я оставлял слушателя изменения текста в редактируемом тексте для поиска, а затем всякий раз, когда что-то меняется в редактируемом тексте, это запрашивает базу данных.

Если это может кому-то помочь, разместив текст редактирования на onCreate

EditText etSearch = (EditText)findViewById(R.id.etSearchBox);
etSearch.addTextChangedListener(filterTextWatcher);

//The filterTextWatcher is 

private TextWatcher filterTextWatcher = new TextWatcher() {
     @Override
    public void afterTextChanged(Editable s) {
    }

    @Override
    public void beforeTextChanged(CharSequence s, int start, int count,int after) {
    }

    @Override
    public void onTextChanged(CharSequence s, int start, int before,int count) {
        adapter.getFilter().filter(s.toString());
        }
    };  

Итак, в вашем адаптере вам нужно создать метод getFilter () ...

@Override
    public Filter getFilter() {
    if (nameFilter == null) {
        nameFilter = new NameFilter();
    }
    return nameFilter;
}

    private class NameFilter extends Filter {

    @Override
    protected FilterResults performFiltering(CharSequence constraint) {
    FilterResults results = new FilterResults();
    Cursor cursor = null;
    // get your cursor by passing appropriate query here
    results.values = cursor;
    results.count = cursor.getCount();
    return results;
    }

    @Override
    protected void publishResults(CharSequence constraint, FilterResults results) {
    notifyDataSetChanged();
        }
    }

Ваш ответ на вопрос