由CursorLoader支持的AutoCompleteTextView
所以我无法扩展MultiAutoCompleteTextView
并使用CursorLoader
备份,同时使用自定义Tokenizer
。 这个问题会随着mAdapter.setCursorToStringConverter();
呼叫。 将Cursor作为参数的convertToString()
方法在首次调用此方法时具有有效且未closures的游标。 但是,随后的调用会导致null游标或closures的游标。 我猜这与LoaderManager
如何pipe理CursorLoader
。
如果我注释了setCursorToStringConverter()
方法,那么我会看到一个基于我input到这个视图中的文本的可用选项列表。 但是,由于没有实现convertToString()
方法,因此自定义Tokenizer
的terminateToken()
方法没有收到我想要的string,而是游标对象的代表string,因为游标不是用于在生成的查询中获取所需列的当前string值。
有没有人能够实现三个类的组合( CursorLoader/LoaderManger
, MultiAutoCompleteTextView
和Tokenizer
)?
我正在朝着这个正确的方向前进,还是这根本不可能?
我已经能够实现由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()
设置了这个,因为这是实现LoaderManger
的Cursor
可用的唯一时间。
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
select一个项目,但不会允许在MultiAutoCompleteTextView
select多个项目。
我猜测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()
方法的constraint
variables是Joe Johnson al
。 那么第二次runQuery()
方法被称为constraint
variables的值是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(); } }