Android Essentials ListView Item State Management un flag Leggi elemento

Una caratteristica comune delle applicazioni di tipo "lettore" è tenere traccia degli elementi che sono stati letti o visualizzati in precedenza. Questo tutorial ti mostrerà, attraverso l'implementazione in un'applicazione esistente, come procedere per incorporare una funzione di flag di lettura in un controllo ListView.

Il? Leggere? flag è un ottimo esempio di una funzione concettualmente semplice che può avere conseguenze di vasta portata per l'applicazione nel suo complesso. L'implementazione di tale funzionalità può attraversare molte discipline e molte parti di una base di codice. Useremo l'applicazione TutList per dimostrare questo fenomeno. Innanzitutto, aggiorneremo il database, aggiungeremo alcuni metodi di supporto al fornitore di contenuti dell'applicazione, aggiorneremo le sue preferenze condivise, aggiungeremo una nuova voce di menu opzioni, aggiorneremo il cursore che fornisce i dati all'adattatore per il controllo ListView e infine modificherò il raccoglitore per cambiare la modalità di visualizzazione degli elementi nell'elenco. Imparerai anche cosa fare quando hai una lista vuota. Tutte queste modifiche sono necessarie per l'implementazione completa di ciò che prima sembra essere una richiesta di funzionalità molto semplice: un flag per gli elementi letti.

Quando avrai finito tutto ciò, ti daremo una rapida sfida.

Pronto?

Passaggio 0: Introduzione

L'applicazione TutList è un progetto di tutorial lettore in corso. Questo tutorial si basa su molte esercitazioni precedenti, tra cui il corso SQLite Crash per sviluppatori Android e le serie continue sulla nostra applicazione TutList con il tutorial più recente, Fondamenti di Android: Date e ordinamenti del database. Se hai problemi a tenere il passo, sentiti libero di postare domande nella sezione commenti - molte persone leggono e rispondono, incluso noi stessi. Inoltre, non dimenticare il riferimento all'SDK di Android.
Il codice di esempio finale che accompagna questo tutorial è disponibile per la navigazione e il download come open-source dall'hosting del codice Google.

Questo tutorial presume che inizierai la codifica in cui il precedente tutorial della serie, Concetti fondamentali di Android: Date e ordinamenti del database, era stato disattivato. Puoi scaricare quel codice e lavorare da lì oppure puoi scaricare il codice per questo tutorial e seguirlo. Se lavori con il codice precedente, tieni presente che occasionalmente apportiamo modifiche al di fuori dell'ambito di qualsiasi tutorial. Il tuo risultato finale potrebbe non apparire o comportarsi esattamente allo stesso modo. In ogni caso, tuttavia, tieniti pronto scaricando uno o l'altro progetto e importandolo in Eclipse se non lo hai già fatto.

Passaggio 1: aggiornamento del database

Per prima cosa abbiamo bisogno di un posto dove archiviare il flag read / unread per ogni articolo tutorial visualizzato nell'applicazione. Per mantenere queste informazioni, aggiungeremo un'altra colonna al database dell'applicazione.

Inizia aggiornando la versione del database, aggiungendo un nuovo nome di colonna e aggiornando lo schema, in questo modo.

 private static final int DB_VERSION = 4 ;? public static final String COL_READ = "read" ;? private static final String CREATE_TABLE_TUTORIALS = "CREATE TABLE" + TABLE_TUTORIALS + "(" + ID + "intero PRINCIPALE CHIAVE AUTOINCREMENT," + COL_TITLE + "testo NON NULL," + COL_URL + "testo UNIQUE NOT NULL," + COL_DATE + "INTEGER NOT NULL DEFAULT (strftime ('% s', 'now')), "+ COL_READ +" INTEGER NOT NULL predefinito 0 "+"); ";

SQLite non supporta i valori booleani. La maggior parte degli sviluppatori usa interi con valore 0 per false e 1 per vero, per convenzione.

Ora modifica il metodo onUpgrade (). Le vecchie versioni supportate potevano essere 2 o 3. Dal momento che stiamo compilando la nuova versione, la verificheremo una sola volta (che potrebbe anche non essere necessaria):

 stringa finale statica privata ALTER_ADD_COL_DATE = "ALTER TABLE" + TABLE_TUTORIALS + "ADD COLUMN" + COL_DATE + "INTEGER NOT NULL DEFAULT '1297728000'";? stringa finale statica privata ALTER_ADD_COL_READ = "ALTER TABLE" + TABLE_TUTORIALS + "ADD COLUMN" + COL_READ + "INTEGER NOT NULL DEFAULT 0" ;? @Override public void onUpgrade (SQLiteDatabase db, int oldVersion, int newVersion) if (newVersion == 4) // fai del nostro meglio per mantenere la data, usando alter tables if (oldVersion == 3) db.execSQL (ALTER_ADD_COL_READ );  else if (oldVersion == 2) db.execSQL (ALTER_ADD_COL_DATE); db.execSQL (ALTER_ADD_COL_READ);  else Log.w (DEBUG_TAG, "Aggiornamento del database. I contenuti esistenti saranno persi. [" + oldVersion + "] -> [" + newVersion + "]"); db.execSQL ("DROP TABLE IF EXISTS" + TABLE_TUTORIALS); onCreate (db); 

Abbiamo anche ripulito il metodo un po '. Poiché ci sono alcune linee "ALTER" ridondanti. (Potremmo rimuovere la ridondanza, ma in questo modo ogni aggiornamento è chiaro e facile da testare.)

Passaggio 2: aggiornamento del provider di contenuti

Sebbene non siano necessarie modifiche sostanziali al fornitore di contenuti, è generalmente consigliabile aggiungere alcuni metodi di supporto statici quando si aggiorna lo schema del database in modo che le nuove funzionalità siano facilmente accessibili. Li useremo più tardi, ma avrai un'idea di cosa succederà:

 public static void markAllItemsRead (Context context) ContentValues ​​values ​​= new ContentValues ​​(); values.put (TutListDatabase.COL_READ, "1"); int updated = context.getContentResolver (). update (CONTENT_URI, values, TutListDatabase.COL_READ + "= '0'", null); Log.d (DEBUG_TAG, "Righe aggiornate:" + aggiornate);  public static void markItemRead (Context context, long item) Uri viewedTut = Uri.withAppendedPath (TutListProvider.CONTENT_URI, String.valueOf (item)); Valori ContentValues ​​= new ContentValues ​​(); values.put (TutListDatabase.COL_READ, "1"); int updated = context.getContentResolver (). update (viewedTut, valori, null, null); Log.d (DEBUG_TAG, aggiornato + "righe aggiornate. Contrassegnato" + elemento + "come letto."); 

L'implementazione di questi metodi di supporto dovrebbe essere relativamente semplice.

Passaggio 3: Aggiunta di una visualizzazione sola lettura? Preferenza

Sebbene una rappresentazione visiva di una flag non letta (che potremo ottenere) sia utile, è anche utile avere un modo per filtrare l'elenco di tutorial solo per quelli non letti. A tale scopo, è necessario aggiungere una preferenza alle preferenze dell'applicazione per impostare questa modalità di visualizzazione.

Modifica il file prefs.xml che controlla la schermata delle impostazioni e aggiungi una nuova categoria di preferenze con una preferenza casella di controllo, come questa:

   

La schermata delle impostazioni aggiornate apparirà ora come segue:

Quindi aggiorna la classe TutListSharedPrefs per aggiungere un altro metodo di guida per il recupero di questo valore di preferenza:

 public static boolean getOnlyUnreadFlag (Context context) SharedPreferences prefs = context.getSharedPreferences (PREFS_NAME, 0); return prefs.getBoolean (context.getString (R.string.pref_key_only_unread), false); 

L'applicazione può verificare questa nuova preferenza e operare di conseguenza.

Passaggio 4: aggiornamento del cursore TutListFragment

Abbiamo aggiunto una nuova colonna di database, ma ListView non ne sa nulla. Innanzitutto, dovremo aggiungere la colonna al Cursore che ListView utilizza nel suo adattatore. Questo è dove dovrebbe avvenire qualsiasi filtraggio.

Aggiornare il metodo onCreateLoader () della classe TutListFragment per aggiungere la colonna TutListDatabase.COL_READ alla proiezione. Inoltre, aggiungi condizionatamente una selezione per se l'utente ha l'impostazione "solo visualizzazione non letta" attivata.

 @Override caricatore pubblico onCreateLoader (int id, Bundle args) String [] projection = TutListDatabase.ID, TutListDatabase.COL_TITLE, TutListDatabase.COL_DATE, TutListDatabase.COL_READ; Uri content = TutListProvider.CONTENT_URI; Selezione stringa = null; if (TutListSharedPrefs.getOnlyUnreadFlag (getActivity ())) selection = TutListDatabase.COL_READ + "= '0'";  CursorLoader cursorLoader = new CursorLoader (getActivity (), content, projection, selection, null, TutListDatabase.COL_DATE + "desc"); return cursorLoader; 

Ora lo stato di ogni elemento è parte dei dati del cursore. Tuttavia, l'utente non ha modo di modificare lo stato di lettura su un elemento specifico, né l'applicazione aggiorna automaticamente un elemento come letto una volta che viene visualizzato.

Passaggio 5: aggiunta di un segno a tutti come opzione di lettura

Per risolvere questo primo problema e testare rapidamente il nostro cursore, aggiungiamo un'opzione per contrassegnare tutti gli elementi in ListView come letti. Il modo più semplice per ottenere ciò è aggiungere una nuova voce di menu opzioni. Aggiorna il file options_menu.xml e aggiungi un terzo elemento:

 

Dovrai aggiungere stringhe e risorse grafiche appropriate per supportare anche questa nuova voce di menu. Abbiamo preso in prestito il grafico ic_menu_mark dall'SDK di Android e lo abbiamo inserito direttamente nel nostro progetto. Questa voce di menu non necessita di regolazioni effettuate su onCreateOptionsMenu (). Nel metodo onOptionsItemSelected (), basta aggiungere una chiamata al metodo helper che abbiamo creato in precedenza, chiamato markAllItemsRead ():

 caso R.id.mark_all_read_item: TutListProvider.markAllItemsRead (getActivity () .getApplicationContext ()); rompere;

Ora l'utente può contrassegnare tutti gli articoli come letti. In questo modo si crea un problema "interessante": l'elenco è ora completamente vuoto. Che noioso.

Passaggio 6: gestire una lista vuota con garbo

Fortunatamente, ListFragment ha un metodo semplice per gestire questo. Il metodo setEmptyText () consente di aggiungere del testo da visualizzare quando non ci sono elementi disponibili. Chiama questo metodo da onActivityCreated () con una stringa da visualizzare. Abbiamo usato una risorsa stringa:

 setEmptyText (. getResources () getText (R.string.empty_list_label));

Ora quando l'utente segna tutti gli elementi come letti, vedranno un elenco simile al seguente:

Molto più user-friendly.

Passaggio 7: aggiornamento del flag di lettura mentre gli elementi vengono letti

Potresti essere incline a aggiungere una chiamata a markItemRead () dal metodo onListItemClicked (). Mentre questo è un posto ragionevole per quella chiamata, introduce un piccolo problema. Pensa a questo: cosa succede quando contrassegni un oggetto che l'utente ha appena selezionato come letto e hanno la preferenza impostata per mostrare solo gli elementi non letti? Mentre il tutorial rimane visibile, l'elemento della lista scompare. Ciò si traduce in una strana esperienza.

Invece, segniamo il scorso elemento che l'utente stava visualizzando come letto quando fa clic su un nuovo oggetto (se in precedenza avevano letto un elemento). Aggiungiamo questa logica al metodo onListItemClick ().

 private long lastItemClicked = -1 ;? public void onListItemClick (ListView l, Visualizza v, int position, long id) if (lastItemClicked! = -1) TutListProvider.markItemRead (getActivity (). getApplicationContext (), lastItemClicked); Log.d (DEBUG_TAG, "Marcatura" + lastItemClicked + "come letto. Ora mostra" + id + ".");  lastItemClicked = id; 

Ci stiamo arrivando. Tuttavia, a meno che l'utente non stia usando la preferenza della modalità di visualizzazione non letta, ListView continua a non fornire indicazioni sullo stato di lettura di un oggetto.

Passaggio 8: Indicazione degli elementi letti

Esistono molti modi per indicare le differenze di stato tra gli elementi di ListView. Potresti usare icone, colori di sfondo o differenze di testo. Un modo semplice per indicare se un elemento è stato letto o meno è utilizzando un carattere distintivo per distinguere tra gli stati letti e non letti. Usiamo grassetto per indicare un elemento non letto e il carattere predefinito per indicare un elemento letto. Per supportare questa modifica, è necessario modificare i collegamenti dell'adattatore e il raccoglitore di viste.

Modifica i binding in modo che la colonna di lettura sia associata al titolo, in questo modo:

 private static final String [] UI_BINDING_FROM = TutListDatabase.COL_TITLE, TutListDatabase.COL_DATE, TutListDatabase.COL_READ; private static final int [] UI_BINDING_TO = R.id.title, R.id.date, R.id.title;

Ora aggiorna il raccoglitore per controllare questo abbinamento e cambia lo stile del carattere aggiungendo un nuovo caso al metodo setViewValue () della classe TutorialViewBinder:

 if (index == cursor.getColumnIndex (TutListDatabase.COL_READ)) boolean read = cursor.getInt (index)> 0? vero falso; Vista TextView title = (Vista TextView); if (! read) title.setTypeface (Typeface.DEFAULT_BOLD, 0);  else title.setTypeface (Typeface.DEFAULT);  return true; 

Ora tutti gli elementi non letti verranno visualizzati in grassetto, indicando chiaramente che sono nuovi e pronti per la lettura!

Passo 9: Passi successivi e una sfida!

Fino ad ora, questo tutorial ha superato il territorio nuovo e familiare in termini di gestione degli articoli della lista. Ma ci sono molti problemi persistenti che non abbiamo discusso in dettaglio, a causa della lunghezza. Ad esempio, se l'utente visualizza gli elementi letti e non letti in modalità di visualizzazione, modifica la modalità di visualizzazione solo da leggere, quindi tocca il pulsante Indietro, cosa succede? Allo stesso modo, cosa succede durante un cambio di orientamento? Cosa succede se un utente non fa mai clic su un altro elemento?

La nostra sfida per te, se dovessi scegliere di accettarla, è identificare i problemi persistenti con la funzione di stato letto / non letto e, se lo desideri, identificare le soluzioni e pubblicarle nel feed dei commenti per la discussione.

Alcuni suggerimenti: pensa ai cicli di vita delle attività e dei frammenti. Considera quel parametro non utilizzato nel metodo onActivityCreated (). Il codice di esempio risolve già molti di questi problemi persistenti.

Conclusione

In questo tutorial, hai aggiunto un flag "read" all'applicazione TutList esistente. Hai imparato come aggiornare un database, aggiungere nuovi elementi di menu e impostazioni delle preferenze, modificare uno stile piuttosto che un valore in un raccoglitore di viste e molto altro ancora. Abbiamo anche lasciato intendere che c'è ancora molto lavoro da fare per rendere la funzione di lettura stabile e priva di errori. Infine, hai appreso che una richiesta di una funzione relativamente semplice può toccare il codice e i file di risorse in un intero progetto, sfruttando diverse abilità diverse.

Come sempre, non vediamo l'ora di ricevere il tuo feedback.

Riguardo agli Autori

Gli sviluppatori mobili Lauren Darcey e Shane Conder hanno coautore diversi libri sullo sviluppo di Android: un libro di programmazione approfondito intitolato Sviluppo di applicazioni wireless Android, seconda edizione e Sams ti insegna a sviluppare applicazioni Android in 24 ore, seconda edizione. Quando non scrivono, passano il loro tempo a sviluppare software mobile presso la loro azienda ea fornire servizi di consulenza. Possono essere contattati via email a [email protected], tramite il loro blog su androidbook.blogspot.com e su Twitter @androidwireless.

Hai bisogno di più aiuto nella scrittura di app per Android? Consulta i nostri ultimi libri e risorse!

я я