So habe ich Probleme, die MultiAutoCompleteTextView
zu erweitern und es mit einer CursorLoader
, während gleichzeitig eine benutzerdefinierte Tokenizer
verwenden. Das Problem steigt speziell mit dem mAdapter.setCursorToStringConverter();
Anruf. Die convertToString()
-Methode, die einen Cursor als Argument hat, hat einen gültigen und nicht geschlossenen Cursor beim ersten Aufruf dieser Methode. Nachfolgende Aufrufe führen jedoch entweder zu einem Null-Cursor oder einem geschlossenen Cursor. Ich vermute, das hat etwas damit zu tun, wie die LoaderManager
die CursorLoader
verwaltet.AutoCompleteTextView unterstützt von CursorLoader
Wenn ich die setCursorToStringConverter()
Methode auskommentieren, dann sehe ich eine Liste der verfügbaren Auswahlmöglichkeiten basierend auf dem Text, den ich in diese Ansicht eingegeben habe. Da jedoch keine convertToString()
-Methode implementiert ist, erhält die terminateToken()
-Methode der benutzerdefinierten Tokenizer
nicht die Zeichenfolge, die ich beabsichtige, sondern eine repräsentative Zeichenfolge des Cursor-Objekts, da der Cursor nicht zum Abrufen der Zeichenfolge verwendet wurde aktueller Stringwert einer gewünschten Spalte in der resultierenden Abfrage
Hat jemand die Kombination der drei Klassen (CursorLoader/LoaderManger
, MultiAutoCompleteTextView
und Tokenizer
) implementieren können?
Gehe ich damit in die richtige Richtung, oder ist das einfach nicht möglich?
Ich war in der Lage, eine benutzerdefinierte MultiAutoCompleteTextView
durch eine SimpleCursorAdapter
zusammen mit einer benutzerdefinierten Tokenizer
unterstützt zu implementieren. Ich habe mich nur gefragt, ob es möglich ist, dies mit einem CursorLoader
stattdessen zu implementieren, da Strict Mode beschwert über den Cursor in MultiAutoCompleteTextView
nicht explizit geschlossen wird.
Jede Hilfe würde sehr geschätzt werden.
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;
}
}
}
}
UPDATE 1
ich jetzt vorgeschlagen nenne die setStringConversionColumn()
Methode anstelle der setCursorToStringConverter()
als @Olaf. Ich habe dies in der onLoadFinished()
eingestellt, da dies das einzige Mal ist, Cursor
ist verfügbar, da dies eine LoaderManger
implementiert.
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);
}
Dies funktioniert ein Element für die MultiAutoCompleteTextView
bei der Auswahl, aber wird nicht zulassen, mehrere Elemente in den MultiAutoCompleteTextView
ausgewählt werden.
Ich rate, es gibt ein Problem mit der onTextChanged()
Methode, da es restartLoader()
ruft. Dies funktioniert für den ersten Eintrag in dieser Ansicht, jedoch nicht für nachfolgende Einträge. Ich bin mir jetzt nicht sicher, was falsch ist.
UPDATE 2
So habe ich das Problem identifiziert. Das Problem ist die onTextChanged()
Methode des TextWatcher. Nach der Auswahl, das erste Token zu beenden (sagen wir, das Token war "Joe Johnson"), dann geben Sie weitere Zeichen in diesen MultiAutoCompleteTextView
(wie al
) den Wert s
, der in die onTextChanged()
Methode übergeben wird jetzt nicht nur enthält die zusätzlich hinzugefügten Zeichen, aber auch die Zeichen aus dem Token, das zuvor beendet wurde (der Wert von s
an diesem Punkt ist Joe Johnson al
).Jetzt wird der Wert von mCursor
auf Joe Johnson al
gesetzt, der anschließend in die Abfrage in onCreateLoader()
übergeben wird, die offensichtlich keine Ergebnisse zurückgibt. Gibt es irgendwelche Möglichkeiten in dieser Situation? Ich bin offen für irgendwelche Vorschläge.
UPDATE 3
Wenn ich eine benutzerdefinierte MultiAutoCompleteTextView
durch eine SimpleCursorAdapter
zusammen mit einem benutzerdefinierten Tokenizer
ich einen FilterQueryProvider
wie diese gesichert implementiert:
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;
}
});
Und aus irgendeinem Grund die runQuery()
Methode aufgerufen wird zweimal aus dem TextWatcher onTextChanged()
Methode:
public void onTextChanged(CharSequence s, int start, int before,
int count) {
Log.d(DEBUG_TAG, "onTextChanged() : s " + s);
mAdapter.getFilterQueryProvider().runQuery(s);
}
Also in meinem vorherigen Beispiel ist die constraint
Variable, die in die runQuery()
Methode das erste Mal übergibt, Joe Johnson al
. Dann das zweite Mal runQuery()
Methode heißt der Wert der constraint
Variable ist al
. Ich weiß nicht, warum die runQuery()
-Methode zweimal ausgeführt wird, wenn sie nur einmal in der onTextChanged()
-Methode aufgerufen wird.
In Ihrem Fall, Sie können 'setStringConversionColumn()' anstelle von 'setCursorToStringConverter()' verwenden. –
Nur um zu verstehen, was Sie tun, fragen Sie eine sehr große Menge an Daten pro Textänderung richtig ab? Ist das in Ihrem Fall wirklich notwendig? – JanithaR