由CursorLoader支持的AutoCompleteTextView

时间:2012-10-12 07:34:45

标签: android autocompletetextview stringtokenizer android-cursorloader

因此,我无法延长MultiAutoCompleteTextView并使用CursorLoader支持,同时使用自定义Tokenizer。该问题特别针对mAdapter.setCursorToStringConverter();电话。在第一次调用此方法时,具有Cursor作为参数的convertToString()方法具有有效且未闭合的游标。但是,后续调用会导致空游标或关闭游​​标。我猜这与LoaderManager管理CursorLoader的方式有关。

如果我对setCursorToStringConverter()方法发表评论,那么我会根据我在此视图中输入的文字看到可用选项列表。但是,由于没有实现convertToString()方法,因此自定义terminateToken()的{​​{1}}方法不会收到我打算使用的字符串,而是一个代表性的游标字符串object,因为游标尚未用于获取结果查询中所需列的当前字符串值。

是否有人能够实现三个类别(TokenizerCursorLoader/LoaderMangerMultiAutoCompleteTextView)的组合?

我是否朝着正确的方向前进,或者这根本不可能?

我已经能够实现由Tokenizer支持的自定义MultiAutoCompleteTextView以及自定义SimpleCursorAdapter。我只是想知道是否可以使用Tokenizer来实现这一点,因为Strict Mode抱怨CursorLoader中的游标没有被明确关闭。

非常感谢任何帮助。

MultiAutoCompleteTextView

更新1

我现在正在调用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; } } } } 方法,而不是@Olaf建议的setStringConversionColumn()。我已在setCursorToStringConverter()中设置此项,因为这是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()。这适用于此视图中的第一个条目,但不适用于后续条目。我现在不太确定会出现什么问题。

更新2

所以我发现了这个问题。问题是TextWatcher的restartLoader()方法。在选择终止第一个令牌后(假设令牌是“Joe Johnson”),然后在此onTextChanged()中输入更多字符(例如MultiAutoCompleteTextView)arg {{1}的值传递到al方法的方法现在不仅包含额外添加的字符,还包含先前已终止的令牌中的字符(此时s的值为onTextChanged() )。现在,s的值设置为Joe Johnson al,随后会传递到mCursor中的查询,这显然不会返回任何结果。这种情况有什么方法吗?我对任何建议持开放态度。

更新3

当我实施由Joe Johnson al支持的自定义onCreateLoader()以及自定义MultiAutoCompleteTextView时,我设置了SimpleCursorAdapter,如下所示:

Tokenizer

由于某些原因,FilterQueryProvider方法从TextWatcher的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()

因此,在我之前的示例中,第一次传递到onTextChanged()方法的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方法中调用一次时运行两次。

1 个答案:

答案 0 :(得分:3)

基本上,机器人自动完成textview不是很强大,当我必须处理更大量的数据时,我所做的是,我保持文本更改监听器的编辑文本以进行搜索,然后每当更改某些内容时编辑文本,查询数据库。

如果这对某人有帮助,可以在onCreate上放置一个edittext

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();
        }
    }