33

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().

+0

Nel tuo caso, puoi usare 'setStringConversionColumn()' invece di 'setCursorToStringConverter()'. –

+0

Giusto per capire cosa stai facendo, stai interrogando una grande quantità di dati per cambio di testo, giusto? È davvero necessario nel tuo caso? – JanithaR

risposta

3

Fondamentalmente, la visualizzazione testo a completamento automatico di androidi non è molto potente, quando devo gestire quantità maggiori di dati, quello che faccio è mantenere un listener di modifica del testo sul testo di modifica per la ricerca, e poi ogni volta che qualcosa viene modificato su il testo di modifica, interroga il database.

Se questo potrebbe aiutare qualcuno, mettendo un EditText su 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()); 
     } 
    }; 

Quindi, l'adattatore, è necessario creare un metodo 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(); 
     } 
    } 
Problemi correlati