Puisque votre question dit "Comment utiliser RecyclerView
avec une base de données" et que vous ne spécifiez pas si vous voulez SQLite ou quoi que ce soit d'autre avec le RecyclerView
, je vais vous donner une solution hautement optimale. J'utiliserai Realm comme base de données et vous laisserai afficher toutes les données dans votre fichier RecyclerView
. Il prend également en charge les requêtes asynchrones sans utiliser Loaders
ni AsyncTask
.
Pourquoi royaume?
Étape 1
Ajoutez la dépendance gradle pour Realm, la dépendance pour la dernière version se trouve ici
Étape 2
Créez votre classe de modèle, par exemple, disons quelque chose de simple comme Data
qui a 2 champs, une chaîne à afficher à l'intérieur de la RecyclerView
ligne et un horodatage qui sera utilisé comme itemId pour permettre RecyclerView
à l'animation des éléments. Notez que j'étends RealmObject
ci - dessous en raison de laquelle votre Data
classe sera stockée sous forme de table et toutes vos propriétés seront stockées sous forme de colonnes de cette table Data
. J'ai marqué le texte de données comme clé primaire dans mon cas car je ne veux pas qu'une chaîne soit ajoutée plus d'une fois. Mais si vous préférez avoir des doublons, créez l'horodatage sous la forme @PrimaryKey. Vous pouvez avoir une table sans clé primaire, mais cela posera des problèmes si vous essayez de mettre à jour la ligne après l'avoir créée. Une clé primaire composite au moment de la rédaction de cette réponse n'est pas prise en charge par 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;
}
}
Étape 3
Créez votre mise en page pour savoir comment une seule ligne doit apparaître dans le RecyclerView
. La disposition d'un élément à une seule ligne à l'intérieur de notre Adapter
est la suivante
<?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>
Remarquez que j'ai gardé un en FrameLayout
tant que racine même si j'ai un TextView
intérieur. Je prévois d'ajouter plus d'éléments dans cette mise en page et donc de la rendre flexible pour le moment :)
Pour les curieux, voici à quoi ressemble actuellement un seul élément.
Étape 4
Créez votre RecyclerView.Adapter
implémentation. Dans ce cas, l'objet de source de données est un objet spécial appelé RealmResults
qui est essentiellement un LIVE ArrayList
, en d'autres termes, lorsque des éléments sont ajoutés ou supprimés de votre table, cet RealmResults
objet se met à jour automatiquement.
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);
}
}
}
Notez que notifyItemRemoved
j'appelle avec la position à laquelle la suppression a eu lieu mais je n'appelle PAS notifyItemInserted
ou notifyItemRangeChanged
parce qu'il n'y a pas de moyen direct de savoir à quelle position l'élément a été inséré dans la base de données puisque les entrées de royaume ne sont pas stockées de manière ordonnée. L' RealmResults
objet se met à jour automatiquement chaque fois qu'un nouvel élément est ajouté, modifié ou supprimé de la base de données, nous l'appelons notifyDataSetChanged
lors de l'ajout et de l'insertion d'entrées en bloc. À ce stade, vous êtes probablement préoccupé par les animations qui ne seront pas déclenchées car vous appelez notifyDataSetChanged
à la place des notifyXXX
méthodes. C'est exactement pourquoi la getItemId
méthode renvoie l'horodatage de chaque ligne à partir de l'objet de résultats. L'animation est réalisée en 2 étapes avec notifyDataSetChanged
si vous appelez setHasStableIds(true)
puis remplacezgetItemId
pour fournir autre chose que juste la position.
Étape 5
Ajoutons le RecyclerView
à notre Activity
ou Fragment
. Dans mon cas, j'utilise un Activity
. Le fichier de mise en page contenant le RecyclerView
est assez simple et ressemblerait à quelque chose comme ça.
<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" />
J'ai ajouté un app:layout_behavior
depuis mon RecyclerView
entrée dans un CoordinatorLayout
que je n'ai pas posté dans cette réponse par souci de concision.
Étape 6
Construisez le RecyclerView
code in et fournissez les données dont il a besoin. Créez et initialisez un objet Realm à l'intérieur onCreate
et fermez-le à l'intérieur onDestroy
un peu comme la fermeture d'une SQLiteOpenHelper
instance. Au plus simple, votre onCreate
intérieur Activity
ressemblera à ceci. La initUi
méthode est l'endroit où toute la magie se produit. J'ouvre une instance de Realm à l'intérieur 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);
}
Notez que dans la première étape, je demande à Realm de me donner tous les objets de la Data
classe triés par leur nom de variable appelé data de manière asynchrone. Cela me donne un RealmResults
objet avec 0 éléments sur le thread principal que je mets sur le Adapter
. J'ai ajouté un RealmChangeListener
pour être averti lorsque les données ont fini de charger à partir du fil d'arrière-plan où j'appelle notifyDataSetChanged
avec mon Adapter
. J'ai également appelé setHasStableIds
true pour permettre à l' RecyclerView.Adapter
implémentation de suivre les éléments ajoutés, supprimés ou modifiés. Le onDestroy
for my Activity
ferme l'instance Realm
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.close();
}
Cette méthode initUi
peut être appelée à l'intérieur onCreate
de votre Activity
ou onCreateView
ou onViewCreated
de votre Fragment
. Notez les choses suivantes.
Étape 7
BAM! L' intérieur de votre Il y a des données de base de données RecyclerView
asynchonously chargée sans CursorLoader
, CursorAdapter
, SQLiteOpenHelper
avec des animations. L'image GIF présentée ici est un peu lente, mais les animations se produisent lorsque vous ajoutez des éléments ou les supprimez.