AutoCompleteTextView, das von CursorLoader unterstützt wird

Ich habe also Probleme, die zu verlängernMultiAutoCompleteTextView und mit a hinterlegenCursorLoader, während gleichzeitig eine benutzerdefinierteTokenizer. Die Ausgabe steigt spezifisch mit demmAdapter.setCursorToStringConverter(); Anruf. DasconvertToString() Methode, die einen Cursor als Argument hat, hat beim ersten Aufruf dieser Methode einen gültigen und nicht geschlossenen Cursor. Nachfolgende Aufrufe führen jedoch entweder zu einem Null-Cursor oder einem geschlossenen Cursor. Ich vermute, das hat etwas damit zu tun, wie dieLoaderManager verwaltet dieCursorLoader.

Wenn ich das kommentieresetCursorToStringConverter() Methode aus, dann sehe ich eine Liste der verfügbaren Optionen basierend auf dem Text, den ich in diese Ansicht eingegeben habe. Da gibt es jedoch keineconvertToString() Methode implementiert, dann dieterminateToken() Methode des BrauchesTokenizer empfängt nicht die Zeichenfolge, die ich beabsichtige, sondern eine repräsentative Zeichenfolge des Cursorobjekts, da der Cursor nicht verwendet wurde, um den aktuellen Zeichenfolgenwert einer gewünschten Spalte in der resultierenden Abfrage abzurufen.

War jemand in der Lage, die Kombination der drei Klassen zu implementieren (CursorLoader/LoaderManger, MultiAutoCompleteTextView, undTokenizer)?

Gehe ich damit in die richtige Richtung oder ist das einfach nicht möglich?

Ich konnte ein Custom implementierenMultiAutoCompleteTextView unterstützt von aSimpleCursorAdapter zusammen mit einem BrauchTokenizer. Ich habe mich nur gefragt, ob es möglich ist, dies mit a zu implementierenCursorLoader Stattdessen beschwert sich Strict Mode über den Cursor inMultiAutoCompleteTextView nicht explizit geschlossen.

Jede Hilfe wäre sehr dankbar.

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 rufe jetzt diesetStringConversionColumn() Methode anstelle dersetCursorToStringConverter() wie @Olaf vorgeschlagen hat. Ich habe das in die gesetztonLoadFinished() da dies das einzige mal ist dasCursor ist verfügbar, da dies eine implementiertLoaderManger.

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 bei der Auswahl eines Elements für dieMultiAutoCompleteTextView, erlaubt jedoch nicht die Auswahl mehrerer Elemente inMultiAutoCompleteTextView.

Ich vermute, es gibt ein Problem mit deronTextChanged() Methode, da es aufruftrestartLoader(). Dies funktioniert für den ersten Eintrag in dieser Ansicht, jedoch nicht für nachfolgende Einträge. Ich bin mir zu diesem Zeitpunkt nicht sicher, was los ist.

UPDATE 2

Also habe ich das Problem identifiziert. Das Problem ist der TextWatcheronTextChanged() Methode. Nachdem Sie die Auswahl zum Beenden des ersten Tokens getroffen haben (sagen wir, das Token war "Joe Johnson"), geben Sie weitere Zeichen einMultiAutoCompleteTextView ( sowieal ) der Wert des Args das wird in die übergebenonTextChanged() Methode enthält jetzt nicht nur die zusätzlich hinzugefügten Zeichen, sondern auch die Zeichen aus dem zuvor abgeschlossenen Token (der Wert vons An diesem Punkt istJoe Johnson al ). Nun ist der Wert vonmCursor wird eingestellt aufJoe Johnson al die anschließend in die Abfrage in übergeben wirdonCreateLoader() das wird offensichtlich keine Ergebnisse zurückgeben. Gibt es irgendwelche Möglichkeiten, um diese Situation zu umgehen? Ich bin offen für Vorschläge.

UPDATE 3

Als ich einen Brauch implementiert habeMultiAutoCompleteTextView unterstützt von aSimpleCursorAdapter zusammen mit einem BrauchTokenizer Ich setze einFilterQueryProvider so was:

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 dierunQuery() Methode wird zweimal vom TextWatcher aufgerufenonTextChanged() 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 dieconstraint Variable, die in die übergeben wirdrunQuery() Methode ist das erste MalJoe Johnson al. Dann das zweite malrunQuery() Methode heißt der Wert derconstraint Variable istal. Ich weiß nicht warumrunQuery() Methode wird zweimal ausgeführt, wenn sie nur einmal in der aufgerufen wirdonTextChanged() Methode.

Antworten auf die Frage(1)

Ihre Antwort auf die Frage