由CursorLoader支持的AutoCompleteTextView

所以我无法扩展MultiAutoCompleteTextView并使用CursorLoader备份,同时使用自定义Tokenizer 。 这个问题会随着mAdapter.setCursorToStringConverter(); 呼叫。 将Cursor作为参数的convertToString()方法在首次调用此方法时具有有效且未closures的游标。 但是,随后的调用会导致null游标或closures的游标。 我猜这与LoaderManager如何pipe理CursorLoader

如果我注释了setCursorToStringConverter()方法,那么我会看到一个基于我input到这个视图中的文本的可用选项列表。 但是,由于没有实现convertToString()方法,因此自定义TokenizerterminateToken()方法没有收到我想要的string,而是游标对象的代表string,因为游标不是用于在生成的查询中获取所需列的当前string值。

有没有人能够实现三个类的组合( CursorLoader/LoaderMangerMultiAutoCompleteTextViewTokenizer )?

我正在朝着这个正确的方向前进,还是这根本不可能?

我已经能够实现由MultiAutoCompleteTextView支持的自定义SimpleCursorAdapter以及自定义Tokenizer 。 我只是想知道是否可以实现这个使用CursorLoader而不是,因为严格模式抱怨MultiAutoCompleteTextView的游标没有被显式closures。

任何帮助将不胜感激。

 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()方法,而不是@Olafbuild议的setCursorToStringConverter()方法。 我已经在onLoadFinished()设置了这个,因为这是实现LoaderMangerCursor可用的唯一时间。

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

这为MultiAutoCompleteTextViewselect一个项目,但不会允许在MultiAutoCompleteTextViewselect多个项目。

我猜测onTextChanged()方法有一些问题,因为它调用了restartLoader() 。 这适用于此视图中的第一个条目,但不适用于后续条目。 我不太确定这一点是什么错误。

更新2

所以我已经确定了这个问题。 问题是TextWatcher的onTextChanged()方法。 在select终止第一个标记之后(假设标记是“Joe Johnson”),然后在这个MultiAutoCompleteTextView (比如al )中input更多的字符,传入onTextChanged()方法的arg s值现在包含不仅增加了额外的字符,而且还添加了先前被终止的令牌中的字符(此时s的值是Joe Johnson al )。 现在, mCursor的值被设置为Joe Johnson al ,随后被传递给onCreateLoader()的查询,这显然不会返回任何结果。 有没有办法解决这个问题? 我接受任何build议。

更新3

当我实现一个MultiAutoCompleteTextView支持的定制MultiAutoCompleteTextView和一个自定义的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的onTextChanged()方法中调用两次:

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

所以在我之前的例子中,第一次传递给runQuery()方法的constraintvariables是Joe Johnson al 。 那么第二次runQuery()方法被称为constraintvariables的值是al 。 我不知道为什么runQuery()方法在runQuery()方法中只调用一次时运行两次。

基本上,android的自动完成textview不是很强大,当我不得不处理大量的数据时,我所做的是,我保留一个文本更改侦听器的编辑文本进行search,然后当编辑文本中的任何东西被改变,它查询数据库。

如果这可以帮助某人,在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()); } }; 

所以,在你的适配器中,你需要创build一个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(); } }