Dal momento che la tua domanda dice "Come usare RecyclerView
con un database" e non sei specifico se vuoi SQLite o qualsiasi altra cosa con il RecyclerView
, ti darò una soluzione che è altamente ottimale. Userò Realm come database e ti permetterò di visualizzare tutti i dati all'interno del tuo RecyclerView
. Ha anche il supporto di query asincrone senza usare Loaders
o AsyncTask
.
Perché regno?
Passo 1
Aggiungi la dipendenza gradle per Realm, la dipendenza per l'ultima versione si trova qui
Passo 2
Crea la tua classe di modello, ad esempio, diciamo qualcosa di semplice come Data
che ha 2 campi, una stringa da visualizzare all'interno della RecyclerView
riga e un timestamp che verrà utilizzato come itemId per consentire l' RecyclerView
animazione degli elementi. Si noti che estendo di RealmObject
seguito a causa della quale la Data
classe verrà archiviata come tabella e tutte le proprietà verranno archiviate come colonne di tale tabella Data
. Ho contrassegnato il testo dei dati come chiave primaria nel mio caso poiché non voglio che una stringa venga aggiunta più di una volta. Ma se preferisci avere dei duplicati, crea il timestamp come @PrimaryKey. Puoi avere una tabella senza una chiave primaria ma causerà problemi se provi ad aggiornare la riga dopo averla creata. Una chiave primaria composita al momento della stesura di questa risposta non è supportata da Realm.
import io.realm.RealmObject;
import io.realm.annotations.PrimaryKey;
public class Data extends RealmObject {
@PrimaryKey
private String data;
//The time when this item was added to the database
private long timestamp;
public String getData() {
return data;
}
public void setData(String data) {
this.data = data;
}
public long getTimestamp() {
return timestamp;
}
public void setTimestamp(long timestamp) {
this.timestamp = timestamp;
}
}
Passaggio 3
Crea il tuo layout per come dovrebbe apparire una singola riga all'interno di RecyclerView
. Il layout per un articolo a riga singola all'interno del nostro Adapter
è il seguente
<?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent">
<TextView
android:id="@+id/area"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:background="@android:color/white"
android:padding="16dp"
android:text="Data"
android:visibility="visible" />
</FrameLayout>
Si noti che ho mantenuto un FrameLayout
root anche se ho un TextView
interno. Ho in programma di aggiungere altri elementi in questo layout e, quindi, lo ho reso flessibile per ora :)
Per le persone curiose là fuori, ecco come appare un singolo oggetto al momento.
Passaggio 4
Crea la tua RecyclerView.Adapter
implementazione. In questo caso, l'oggetto origine dati è un oggetto speciale chiamato RealmResults
che è fondamentalmente un LIVE ArrayList
, in altre parole, poiché gli elementi vengono aggiunti o rimossi dalla tabella, questo RealmResults
oggetto si aggiorna automaticamente.
import android.content.Context;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import io.realm.Realm;
import io.realm.RealmResults;
import slidenerd.vivz.realmrecycler.R;
import slidenerd.vivz.realmrecycler.model.Data;
public class DataAdapter extends RecyclerView.Adapter<DataAdapter.DataHolder> {
private LayoutInflater mInflater;
private Realm mRealm;
private RealmResults<Data> mResults;
public DataAdapter(Context context, Realm realm, RealmResults<Data> results) {
mRealm = realm;
mInflater = LayoutInflater.from(context);
setResults(results);
}
public Data getItem(int position) {
return mResults.get(position);
}
@Override
public DataHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View view = mInflater.inflate(R.layout.row_data, parent, false);
DataHolder dataHolder = new DataHolder(view);
return dataHolder;
}
@Override
public void onBindViewHolder(DataHolder holder, int position) {
Data data = mResults.get(position);
holder.setData(data.getData());
}
public void setResults(RealmResults<Data> results) {
mResults = results;
notifyDataSetChanged();
}
@Override
public long getItemId(int position) {
return mResults.get(position).getTimestamp();
}
@Override
public int getItemCount() {
return mResults.size();
}
public void add(String text) {
//Create a new object that contains the data we want to add
Data data = new Data();
data.setData(text);
//Set the timestamp of creation of this object as the current time
data.setTimestamp(System.currentTimeMillis());
//Start a transaction
mRealm.beginTransaction();
//Copy or update the object if it already exists, update is possible only if your table has a primary key
mRealm.copyToRealmOrUpdate(data);
//Commit the transaction
mRealm.commitTransaction();
//Tell the Adapter to update what it shows.
notifyDataSetChanged();
}
public void remove(int position) {
//Start a transaction
mRealm.beginTransaction();
//Remove the item from the desired position
mResults.remove(position);
//Commit the transaction
mRealm.commitTransaction();
//Tell the Adapter to update what it shows
notifyItemRemoved(position);
}
public static class DataHolder extends RecyclerView.ViewHolder {
TextView area;
public DataHolder(View itemView) {
super(itemView);
area = (TextView) itemView.findViewById(R.id.area);
}
public void setData(String text) {
area.setText(text);
}
}
}
Si noti che sto chiamando notifyItemRemoved
con la posizione in cui è avvenuta la rimozione ma NON chiamo notifyItemInserted
o notifyItemRangeChanged
perché non esiste un modo diretto per sapere quale posizione è stata inserita nel database poiché le voci di Realm non sono memorizzate in modo ordinato. L' RealmResults
oggetto si aggiorna automaticamente ogni volta che un nuovo elemento viene aggiunto, modificato o rimosso dal database, pertanto viene chiamato notifyDataSetChanged
durante l'aggiunta e l'inserimento di voci in blocco. A questo punto, probabilmente sei preoccupato per le animazioni che non verranno attivate perché stai chiamando notifyDataSetChanged
al posto dei notifyXXX
metodi. Questo è esattamente il motivo per cui ho il getItemId
metodo restituire il timestamp per ogni riga dall'oggetto risultati. L'animazione si ottiene in 2 passaggi notifyDataSetChanged
se si chiama setHasStableIds(true)
e si esegue l'overridegetItemId
per fornire qualcosa di diverso dalla semplice posizione.
Passaggio 5
Aggiungiamo il RecyclerView
nostro Activity
o Fragment
. Nel mio caso, sto usando un Activity
. Il file di layout che contiene il file RecyclerView
è piuttosto banale e sarebbe simile a questo.
<android.support.v7.widget.RecyclerView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/recycler"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_margin="@dimen/text_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior" />
Ho aggiunto un app:layout_behavior
dato che il mio RecyclerView
va dentro un CoordinatorLayout
che non ho pubblicato in questa risposta per brevità.
Passaggio 6
Costruisci il RecyclerView
codice in e fornisci i dati di cui ha bisogno. Crea e inizializza un oggetto Realm al suo interno onCreate
e chiudilo al suo interno onDestroy
praticamente come chiudere SQLiteOpenHelper
un'istanza. Al più semplice il tuo onCreate
interno Activity
sarà simile a questo. Il initUi
metodo è dove accade tutta la magia. Apro un'istanza di Realm all'interno onCreate
.
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mRealm = Realm.getInstance(this);
initUi();
}
private void initUi() {
//Asynchronous query
RealmResults<Data> mResults = mRealm.where(Data.class).findAllSortedAsync("data");
//Tell me when the results are loaded so that I can tell my Adapter to update what it shows
mResults.addChangeListener(new RealmChangeListener() {
@Override
public void onChange() {
mAdapter.notifyDataSetChanged();
Toast.makeText(ActivityMain.this, "onChange triggered", Toast.LENGTH_SHORT).show();
}
});
mRecycler = (RecyclerView) findViewById(R.id.recycler);
mRecycler.setLayoutManager(new LinearLayoutManager(this));
mAdapter = new DataAdapter(this, mRealm, mResults);
//Set the Adapter to use timestamp as the item id for each row from our database
mAdapter.setHasStableIds(true);
mRecycler.setAdapter(mAdapter);
}
Si noti che nel primo passaggio, chiedo a Realm di fornirmi tutti gli oggetti della Data
classe ordinati in base al loro nome variabile chiamato data in modo asincrono. Questo mi dà un RealmResults
oggetto con 0 elementi sul thread principale che sto impostando su Adapter
. Ho aggiunto un messaggio RealmChangeListener
di notifica quando il caricamento dei dati è terminato dal thread in background dove chiamo notifyDataSetChanged
con il mio Adapter
. Ho anche chiamato setHasStableIds
true per consentire RecyclerView.Adapter
all'implementazione di tenere traccia degli elementi aggiunti, rimossi o modificati. Il onDestroy
per mio Activity
chiude l'istanza Realm
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.close();
}
Questo metodo initUi
può essere chiamato all'interno onCreate
della vostra Activity
o di onCreateView
o onViewCreated
della vostra Fragment
. Nota le seguenti cose.
Passaggio 7
BAM! Non ci sono dati dal database all'interno del vostro RecyclerView
asynchonously caricato senza CursorLoader
, CursorAdapter
, SQLiteOpenHelper
con animazioni. L'immagine GIF mostrata qui è piuttosto lenta ma le animazioni si verificano quando aggiungi elementi o li rimuovi.