そのため、カスタムMultiAutoCompleteTextView
を同時に使用しながら、CursorLoader
を拡張してTokenizer
でバックアップするのに問題があります。この問題は、特にmAdapter.setCursorToStringConverter();
呼び出しで発生します。引数としてカーソルを持つconvertToString()
メソッドには、このメソッドの最初の呼び出し時に有効で閉じられていないカーソルがあります。ただし、後続の呼び出しでは、カーソルがnullまたは閉じられます。これは、LoaderManager
がCursorLoader
を管理する方法と関係があると思います。
setCursorToStringConverter()
メソッドをコメントアウトすると、このビューに入力したテキストに基づいて使用可能な選択肢のリストが表示されます。ただし、convertToString()
メソッドが実装されていないため、カスタムTokenizer
のterminateToken()
メソッドは、意図した文字列ではなく、カーソルオブジェクトの代表的な文字列を受け取ります。結果のクエリで目的の列の現在の文字列値を取得するためにカーソルが使用されていません。
3つのクラス(_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
@Olafが提案したように、現在、setStringConversionColumn()
ではなくsetCursorToStringConverter()
メソッドを呼び出しています。これはCursor
を実装しているため、LoaderManger
が使用できるのはこれだけなので、これをonLoadFinished()
に設定しました。
_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
に対して1つのアイテムを選択する場合に機能しますが、MultiAutoCompleteTextView
で複数のアイテムを選択することはできません。
onTextChanged()
を呼び出すため、restartLoader()
メソッドに問題があると思います。これは、このビューの最初のエントリでは機能しますが、後続のエントリでは機能しません。現時点では何が悪いのかよくわかりません。
更新2
だから私は問題を特定しました。問題は、TextWatcherのonTextChanged()
メソッドです。最初のトークンを終了するように選択した後(トークンが「JoeJohnson」だったとしましょう)、このMultiAutoCompleteTextView
(al
など)にさらに文字を入力すると、onTextChanged()
メソッドに渡されるargs
の値になります。追加された文字だけでなく、以前に終了したトークンの文字も含まれるようになりました(この時点でのs
の値は_Joe Johnson al
_です)。これで、mCursor
の値が_Joe Johnson al
_に設定され、その後onCreateLoader()
のクエリに渡され、明らかに結果が返されません。この状況を回避する方法はありますか?私はどんな提案にもオープンです。
更新
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のonTextChanged()
メソッドから2回呼び出されます。
_public void onTextChanged(CharSequence s, int start, int before,
int count) {
Log.d(DEBUG_TAG, "onTextChanged() : s " + s);
mAdapter.getFilterQueryProvider().runQuery(s);
}
_
したがって、前の例では、最初にrunQuery()
メソッドに渡されるconstraint
変数は_Joe Johnson al
_です。次に、2回目のrunQuery()
メソッドが呼び出されると、constraint
変数の値はal
になります。 runQuery()
メソッドで1回だけ呼び出されたのに、なぜonTextChanged()
メソッドが2回実行されるのかわかりません。
基本的に、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();
}
}