2012-02-24 10 views
11

Non sono mai stato contento del codice sul mio CursorAdapter personalizzato fino ad oggi ho deciso di rivederlo e risolvere un piccolo problema che mi dava fastidio per molto tempo (cosa abbastanza interessante, nessuno degli utenti della mia app ha mai segnalato un problema simile).Questo CursorAdapter personalizzato per un ListView è correttamente codificato per Android?

Ecco una piccola descrizione della mia domanda:

mio CursorAdapter personalizzato ignora newView() e bindView() invece di getView() come maggior parte degli esempi che vedo. Io uso il modello ViewHolder tra questi 2 metodi. Ma il mio problema principale era con il layout personalizzato che sto usando per ogni elemento della lista, contiene uno ToggleButton.

Il problema era che lo stato del pulsante non veniva mantenuto quando una vista di una voce di elenco scorreva fuori dalla vista e poi tornava alla vista. Questo problema si verificava perché il cursor non era mai a conoscenza del fatto che i dati del database venivano modificati quando veniva premuto ToggleButton e gli stessi dati venivano sempre estratti. Ho provato a richiedere il cursore quando si fa clic su ToggleButton e questo ha risolto il problema, ma è stato molto lento.

Ho risolto questo problema e sto postando l'intera classe qui per la revisione. Ho commentato a fondo il codice per questa specifica domanda per spiegare meglio le mie decisioni di codifica.

Questo codice ti sembra buono? Vuoi migliorarlo/ottimizzarlo o cambiarlo in qualche modo?

P.S: So che il CursorLoader è un miglioramento evidente ma non ho il tempo di occuparmi di riscritture di codice così grandi per il momento. È qualcosa che ho nella roadmap però.

Ecco il codice:

public class NotesListAdapter extends CursorAdapter implements OnClickListener { 

    private static class ViewHolder { 
     ImageView icon; 
     TextView title; 
     TextView description; 
     ToggleButton visibility; 
    } 

    private static class NoteData { 
     long id; 
     int iconId; 
     String title; 
     String description; 
     int position; 
    } 

    private LayoutInflater mInflater; 

    private NotificationHelper mNotificationHelper; 
    private AgendaNotesAdapter mAgendaAdapter; 

    /* 
    * This is used to store the state of the toggle buttons for each item in the list 
    */ 
    private List<Boolean> mToggleState; 

    private int mColumnRowId; 
    private int mColumnTitle; 
    private int mColumnDescription; 
    private int mColumnIconName; 
    private int mColumnVisibility; 

    public NotesListAdapter(Context context, Cursor cursor, NotificationHelper helper, AgendaNotesAdapter adapter) { 
     super(context, cursor); 

     mInflater = LayoutInflater.from(context); 

     /* 
     * Helper class to post notifications to the status bar and database adapter class to update 
     * the database data when the user presses the toggle button in any of items in the list 
     */ 
     mNotificationHelper = helper; 
     mAgendaAdapter = adapter; 

     /* 
     * There's no need to keep getting the column indexes every time in bindView() (as I see in 
     * a few examples) so I do it once and save the indexes in instance variables 
     */ 
     findColumnIndexes(cursor); 

     /* 
     * Populate the toggle button states for each item in the list with the corresponding value 
     * from each record in the database, but isn't this a slow operation? 
     */ 
     for(mToggleState = new ArrayList<Boolean>(); !cursor.isAfterLast(); cursor.moveToNext()) { 
      mToggleState.add(cursor.getInt(mColumnVisibility) != 0); 
     } 
    } 

    @Override 
    public View newView(Context context, Cursor cursor, ViewGroup parent) { 
     View view = mInflater.inflate(R.layout.list_item_note, null); 

     /* 
     * The ViewHolder pattern is here only used to prevent calling findViewById() all the time 
     * in bindView(), we only need to find all the views once 
     */ 
     ViewHolder viewHolder = new ViewHolder(); 

     viewHolder.icon = (ImageView)view.findViewById(R.id.imageview_icon); 
     viewHolder.title = (TextView)view.findViewById(R.id.textview_title); 
     viewHolder.description = (TextView)view.findViewById(R.id.textview_description); 
     viewHolder.visibility = (ToggleButton)view.findViewById(R.id.togglebutton_visibility); 

     /* 
     * I also use newView() to set the toggle button click listener for each item in the list 
     */ 
     viewHolder.visibility.setOnClickListener(this); 

     view.setTag(viewHolder); 

     return view; 
    } 

    @Override 
    public void bindView(View view, Context context, Cursor cursor) { 
     Resources resources = context.getResources(); 

     int iconId = resources.getIdentifier(cursor.getString(mColumnIconName), 
       "drawable", context.getPackageName()); 

     String title = cursor.getString(mColumnTitle); 
     String description = cursor.getString(mColumnDescription); 

     /* 
     * This is similar to the ViewHolder pattern and it's need to access the note data when the 
     * onClick() method is fired 
     */ 
     NoteData noteData = new NoteData(); 

     /* 
     * This data is needed to post a notification when the onClick() method is fired 
     */ 
     noteData.id = cursor.getLong(mColumnRowId); 
     noteData.iconId = iconId; 
     noteData.title = title; 
     noteData.description = description; 

     /* 
     * This data is needed to update mToggleState[POS] when the onClick() method is fired 
     */ 
     noteData.position = cursor.getPosition(); 

     /* 
     * Get our ViewHolder with all the view IDs found in newView() 
     */ 
     ViewHolder viewHolder = (ViewHolder)view.getTag(); 

     /* 
     * The Html.fromHtml is needed but the code relevant to that was stripped 
     */ 
     viewHolder.icon.setImageResource(iconId); 
     viewHolder.title.setText(Html.fromHtml(title)); 
     viewHolder.description.setText(Html.fromHtml(description)); 

     /* 
     * Set the toggle button state for this list item from the value in mToggleState[POS] 
     * instead of getting it from the database with 'cursor.getInt(mColumnVisibility) != 0' 
     * otherwise the state will be incorrect if it was changed between the item view scrolling 
     * out of view and scrolling back into view 
     */ 
     viewHolder.visibility.setChecked(mToggleState.get(noteData.position)); 

     /* 
     * Again, save the note data to be accessed when onClick() gets fired 
     */ 
     viewHolder.visibility.setTag(noteData); 
    } 

    @Override 
    public void onClick(View view) { 
     /* 
     * Get the new state directly from the toggle button state 
     */ 
     boolean visibility = ((ToggleButton)view).isChecked(); 

     /* 
     * Get all our note data needed to post (or remove) a notification 
     */ 
     NoteData noteData = (NoteData)view.getTag(); 

     /* 
     * The toggle button state changed, update mToggleState[POS] to reflect that new change 
     */ 
     mToggleState.set(noteData.position, visibility); 

     /* 
     * Post the notification or remove it from the status bar depending on toggle button state 
     */ 
     if(visibility) { 
      mNotificationHelper.postNotification(
        noteData.id, noteData.iconId, noteData.title, noteData.description); 
     } else { 
      mNotificationHelper.cancelNotification(noteData.id); 
     } 

     /* 
     * Update the database note item with the new toggle button state, without the need to 
     * requery the cursor (which is slow, I've tested it) to reflect the new toggle button state 
     * in the list because the value was saved in mToggleState[POS] a few lines above 
     */ 
     mAgendaAdapter.updateNote(noteData.id, null, null, null, null, visibility); 
    } 

    private void findColumnIndexes(Cursor cursor) { 
     mColumnRowId = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ROW_ID); 
     mColumnTitle = cursor.getColumnIndex(AgendaNotesAdapter.KEY_TITLE); 
     mColumnDescription = cursor.getColumnIndex(AgendaNotesAdapter.KEY_DESCRIPTION); 
     mColumnIconName = cursor.getColumnIndex(AgendaNotesAdapter.KEY_ICON_NAME); 
     mColumnVisibility = cursor.getColumnIndex(AgendaNotesAdapter.KEY_VISIBILITY); 
    } 

} 

risposta

4

la soluzione è ottimale un aggiungerò ai miei armi :) Forse, cercherò di portare un po 'di ottimizzazione per le chiamate al database.

Infatti, a causa delle condizioni del compito, ci sono solo tre soluzioni:

  1. Aggiorna sola fila, cursore requery e ridisegnare tutti gli elementi. (Forza diretta, forza bruta).
  2. Aggiorna la riga, memorizza i risultati nella cache e utilizza la cache per gli elementi di disegno.
  3. Memorizza i risultati, utilizza la cache per gli elementi di disegno. E quando lasci questa attività/frammento, commetti i risultati nel database.

Per la terza soluzione è possibile utilizzare SparseArray per cercare le modifiche.

private SparseArray<NoteData> mArrayViewHolders; 

public void onClick(View view) { 
    //here your logic with NoteData. 
    //start of my improve 
    if (mArrayViewHolders.get(selectedPosition) == null) { 
     // put the change into array 
     mArrayViewHolders.put(selectedPosition, noteData); 
    } else { 
     // rollback the change 
     mArrayViewHolders.delete(selectedPosition); 
    } 
    //end of my improve 
    //we don't commit the changes to database. 
} 

Ancora una volta: dall'inizio questa matrice è vuota. Quando si commuta il pulsante per la prima volta (c'è una modifica), si aggiunge NoteData all'array. Quando si attiva nuovamente il pulsante (c'è un rollback), si rimuove NoteData dalla matrice. E così via.

Al termine, è sufficiente richiedere l'array e inserire le modifiche nel database.

+0

Mi piaceva l'idea di un 'SparseArray', non sapevo di quella classe. Sembra un modo più efficiente di memorizzare nella cache gli stati del pulsante invece di salvarli tutti in un 'Elenco'. Ma non mi piace l'idea di impegnare i risultati nel database solo quando l'utente lascia l'attività. Ciò richiederebbe un codice aggiuntivo per gestire quella situazione. Quindi, alla fine, suppongo di optare per la seconda soluzione che hai elencato. Che è fondamentalmente ciò che stavo facendo in primo luogo. Mi è comunque piaciuta la tua risposta, ma ho intenzione di passare 1 o 2 giorni in più :) –

1

Quello che stai vedendo è la vista riutilizzo di Android. Non penso che tu stia facendo qualcosa di sbagliato interrogando di nuovo il cursore. Basta non usare la funzione cursor.requery().

Al contrario, impostare sempre l'interruttore su falso, quindi chiedere il cursore e accenderlo se necessario.

Forse lo stavi facendo e ho frainteso qualcosa, tuttavia non penso che dovresti avere risultati lenti a farlo.

pseudo-codice:

getView(){ 
setToggleFalse(); 
boolean value = Boolean.valueOf(cursor.getString('my column')); 
if (value){ 
    setToggleTrue(); 
} 
} 
1

mi avrebbe aspettato prima di andare a CursorLoader. A quanto pare, le derivate di CursorLoader non funzionano con CursorLoader.

Problemi correlati