33

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.

+0

In Ihrem Fall, Sie können 'setStringConversionColumn()' anstelle von 'setCursorToStringConverter()' verwenden. –

+0

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

Antwort

3

Im Grunde ist Android Autocomplete Textview nicht sehr mächtig, wenn ich mit größeren Mengen von Daten zu tun habe, was ich tue, halte ich einen Text ändern Listener auf den Text bearbeiten für die Suche und dann, wenn etwas geändert wird der Bearbeitungstext fragt Datenbank ab.

Wenn dies jemand helfen könnte, eine EditText auf onCreate Platzierung

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()); 
     } 
    }; 

Also, in Ihrem Adapter benötigen Sie einen getFilter() -Methode erstellen ...

@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(); 
     } 
    } 
Verwandte Themen