In questo modo sto riscontrando problemi nell'estensione dello MultiAutoCompleteTextView
e nel suo backup con uno CursorLoader
, mentre contemporaneamente utilizzo un numero personalizzato Tokenizer
. Il problema sorge in particolare con la chiamata mAdapter.setCursorToStringConverter();
. Il metodo convertToString()
che ha come argomento un cursore ha un cursore valido e non chiuso sulla prima chiamata a questo metodo. Tuttavia, le chiamate successive determinano un cursore null o un cursore chiuso. Sto indovinando che questo ha qualcosa a che fare con come il LoaderManager
gestisce il CursorLoader
.AutoCompleteTextView supportato da CursorLoader
Se commento il metodo setCursorToStringConverter()
, vedo un elenco di scelte disponibili in base al testo inserito in questa vista. Tuttavia, poiché non è implementato il metodo convertToString()
, il metododell'oggetto personalizzato Tokenizer
non riceve la stringa che intendo sia, ma piuttosto una stringa rappresentativa dell'oggetto cursore, poiché il cursore non è stato utilizzato per ottenere valore di stringa corrente di una colonna desiderata nella query risultante.
Qualcuno è stato in grado di implementare la combinazione delle tre classi (CursorLoader/LoaderManger
, MultiAutoCompleteTextView
e Tokenizer
)?
Sto andando nella giusta direzione con questo, o semplicemente non è possibile?
Sono stato in grado di implementare un numero personalizzato MultiAutoCompleteTextView
supportato da un SimpleCursorAdapter
insieme a un numero personalizzato Tokenizer
. Mi stavo solo chiedendo se è possibile implementarlo usando uno , poiché la Modalità Strict si lamenta del fatto che il cursore in MultiAutoCompleteTextView
non venga chiuso esplicitamente.
Qualsiasi aiuto sarebbe molto apprezzato.
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
ora sto chiamando il metodo al posto del setCursorToStringConverter()
setStringConversionColumn()
come suggerito @Olaf. L'ho impostato su onLoadFinished()
poiché questa è l'unica volta che è disponibile l'Cursor
poiché sta implementando uno 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);
}
questo funziona nella scelta di una voce per il MultiAutoCompleteTextView
, ma non consentirà più elementi da selezionare nel MultiAutoCompleteTextView
.
Suppongo che ci sia qualche problema con il metodo onTextChanged()
poiché chiama restartLoader()
. Funziona per la prima voce in questa vista ma non per le voci successive. Non sono troppo sicuro a questo punto di ciò che è sbagliato.
UPDATE 2
Così ho identificato il problema. Il problema è il metodo onTextChanged()
di TextWatcher. Dopo aver effettuato la selezione per terminare il primo token (diciamo che il token era "Joe Johnson"), quindi inserire più caratteri in questo MultiAutoCompleteTextView
(come al
) il valore dell'arg s
che viene passato nel metodo onTextChanged()
ora contiene non solo i caratteri aggiunti in aggiunta, ma anche i caratteri del token che è stato precedentemente terminato (il valore di s
a questo punto è Joe Johnson al
).Ora il valore di mCursor
viene impostato su Joe Johnson al
che successivamente viene passato nella query in onCreateLoader()
che ovviamente non restituirà alcun risultato. Ci sono modi per aggirare questa situazione? Sono aperto a qualsiasi suggerimento.
UPDATE 3
Quando ho implementato una consuetudine MultiAutoCompleteTextView
sostenuta da una SimpleCursorAdapter
con un costume Tokenizer
Ho impostato un FilterQueryProvider
come questo:
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;
}
});
E per qualche ragione il metodo runQuery()
viene chiamato due volte dal metodo onTextChanged()
di TextWatcher:
public void onTextChanged(CharSequence s, int start, int before,
int count) {
Log.d(DEBUG_TAG, "onTextChanged() : s " + s);
mAdapter.getFilterQueryProvider().runQuery(s);
}
Nel mio esempio precedente, la variabile constraint
che viene passata per la prima volta nel metodo runQuery()
è Joe Johnson al
. Quindi il secondo metodo runQuery()
viene chiamato il valore della variabile constraint
è al
. Non so perché il metodo runQuery()
viene eseguito due volte quando è chiamato solo una volta nel metodo onTextChanged()
.
Nel tuo caso, puoi usare 'setStringConversionColumn()' invece di 'setCursorToStringConverter()'. –
Giusto per capire cosa stai facendo, stai interrogando una grande quantità di dati per cambio di testo, giusto? È davvero necessario nel tuo caso? – JanithaR