Поскольку в вашем вопросе говорится «Как использовать RecyclerView
с базой данных», и вы не уточняете, хотите ли вы SQLite или что-то еще с ним RecyclerView
, я дам вам решение, которое является оптимальным. Я буду использовать Realm в качестве базы данных и позволю вам отображать все данные внутри вашего RecyclerView
. Он также поддерживает асинхронные запросы без использования Loaders
или AsyncTask
.
Почему царство?
Шаг 1
Добавьте зависимость Gradle для Realm, зависимость для последней версии находится здесь
Шаг 2
Например, создайте свой класс модели, скажем что-то простое, например, у Data
которого есть 2 поля, строка, которая будет отображаться внутри RecyclerView
строки, и отметка времени, которая будет использоваться как itemId для разрешения RecyclerView
анимации элементов. Обратите внимание, что я расширяю RealmObject
ниже, из-за чего ваш Data
класс будет храниться как таблица, а все ваши свойства будут храниться как столбцы этой таблицы Data
. В моем случае я пометил текст данных как первичный ключ, так как не хочу, чтобы строка добавлялась более одного раза. Но если вы предпочитаете иметь дубликаты, сделайте отметку времени как @PrimaryKey. У вас может быть таблица без первичного ключа, но это вызовет проблемы, если вы попытаетесь обновить строку после ее создания. Составной первичный ключ на момент написания этого ответа 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;
}
}
Шаг 3
Создайте свой макет для того, как одна строка должна отображаться внутри RecyclerView
. Макет для элемента с одной строкой внутри нашего Adapter
выглядит следующим образом
<?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>
Обратите внимание, что я сохранил FrameLayout
корневую папку, хотя у меня есть TextView
внутренняя. Я планирую добавить больше элементов в этот макет и, следовательно, пока сделал его гибким :)
Для любопытных вот как сейчас выглядит отдельный предмет.
Шаг 4
Создайте свою RecyclerView.Adapter
реализацию. В этом случае объект источника данных - это специальный вызываемый объект, RealmResults
который по сути является LIVE ArrayList
, другими словами, когда элементы добавляются или удаляются из вашей таблицы, этот RealmResults
объект обновляется автоматически.
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);
}
}
}
Обратите внимание, что я звоню notifyItemRemoved
с позиции, в которой произошло удаление, но Я НЕ звоню, notifyItemInserted
или notifyItemRangeChanged
потому что нет прямого способа узнать, в какую позицию элемент был вставлен в базу данных, поскольку записи Realm не хранятся в упорядоченном виде. RealmResults
Объект автоматически обновляется каждый раз , когда новый элемент добавляется, изменен или удален из базы данных , поэтому мы называем в notifyDataSetChanged
то время как добавление и вставкой записей сыпучими. На этом этапе вас, вероятно, беспокоят анимации, которые не будут запускаться из-за того, что вы вызываете notifyDataSetChanged
вместо notifyXXX
методов. Именно поэтому у меня getItemId
метод возвращает временную метку для каждой строки из объекта результатов. Анимация достигается в 2 этапа, notifyDataSetChanged
если вы вызываете, setHasStableIds(true)
а затем отменяетеgetItemId
предоставить что-то иное, чем просто должность.
Шаг 5
Давайте добавим RecyclerView
к нашему Activity
или Fragment
. В моем случае я использую Activity
. Файл макета, содержащий RecyclerView
элемент, довольно тривиален и будет выглядеть примерно так.
<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" />
Я добавил, app:layout_behavior
так как я RecyclerView
перешел внутрь, CoordinatorLayout
который я не публиковал в этом ответе для краткости.
Шаг 6
Создайте RecyclerView
код и предоставьте необходимые данные. Создайте и инициализируйте объект Realm внутри onCreate
и закройте его внутри onDestroy
почти так же, как закрытие SQLiteOpenHelper
экземпляра. В самом простом случае ваша onCreate
внутренняя часть Activity
будет выглядеть так. В initUi
методе происходит все волшебство. Я открываю экземпляр Realm внутри 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);
}
Обратите внимание, что на первом этапе я запрашиваю Realm, чтобы предоставить мне все объекты из Data
класса, отсортированные по имени их переменной, называемой данными, асинхронным образом. Это дает мне RealmResults
объект с 0 элементами в основном потоке, который я устанавливаю в Adapter
. Я добавил, RealmChangeListener
чтобы получать уведомления о завершении загрузки данных из фонового потока, в который я вызываю notifyDataSetChanged
свой Adapter
. Я также вызвал setHasStableIds
значение true, чтобы позволить RecyclerView.Adapter
реализации отслеживать элементы, которые добавляются, удаляются или изменяются. The onDestroy
for my Activity
закрывает экземпляр Realm
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.close();
}
Этот метод initUi
можно вызвать внутри onCreate
вашего Activity
или onCreateView
или onViewCreated
вашего Fragment
. Обратите внимание на следующие вещи.
Шаг 7
BAM! Существуют данные из базы данных внутри вашей RecyclerView
asynchonously загружены без CursorLoader
, CursorAdapter
, SQLiteOpenHelper
с анимацией. Показанное здесь изображение в формате GIF немного тормозит, но анимация появляется, когда вы добавляете или удаляете элементы.