Vì câu hỏi của bạn nói "Cách sử dụng RecyclerView
với cơ sở dữ liệu" và bạn không cụ thể cho dù bạn muốn SQLite hay bất cứ điều gì khác với RecyclerView
, tôi sẽ cung cấp cho bạn một giải pháp tối ưu cao. Tôi sẽ sử dụng Realm làm cơ sở dữ liệu và cho phép bạn hiển thị tất cả dữ liệu bên trong RecyclerView
. Nó cũng có hỗ trợ truy vấn không đồng bộ mà không cần sử dụng Loaders
hoặc AsyncTask
.
Tại sao cảnh giới?
Bước 1
Thêm phụ thuộc lớp cho Realm, phụ thuộc cho phiên bản mới nhất được tìm thấy ở đây
Bước 2
Ví dụ, tạo lớp mô hình của bạn, giả sử một cái gì đó đơn giản như Data
có 2 trường, một chuỗi được hiển thị bên trong RecyclerView
hàng và dấu thời gian sẽ được sử dụng làm itemId để cho phép các RecyclerView
mục động. Lưu ý rằng tôi mở rộng RealmObject
bên dưới vì Data
lớp này sẽ được lưu dưới dạng bảng và tất cả các thuộc tính của bạn sẽ được lưu dưới dạng cột của bảng đó Data
. Tôi đã đánh dấu văn bản dữ liệu là khóa chính trong trường hợp của mình vì tôi không muốn một chuỗi được thêm nhiều lần. Nhưng nếu bạn muốn có các bản sao, thì hãy tạo dấu thời gian là @PrimaryKey. Bạn có thể có một bảng không có khóa chính nhưng nó sẽ gây ra vấn đề nếu bạn thử cập nhật hàng sau khi tạo nó. Khóa chính tổng hợp tại thời điểm viết câu trả lời này không được Realm hỗ trợ.
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;
}
}
Bước 3
Tạo bố cục của bạn cho cách một hàng đơn sẽ xuất hiện bên trong RecyclerView
. Bố cục cho một mục hàng bên trong của chúng tôi Adapter
như sau
<?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>
Lưu ý rằng tôi đã giữ một FrameLayout
root như mặc dù tôi có một TextView
bên trong. Tôi dự định thêm nhiều mục trong bố cục này và từ đó làm cho nó linh hoạt ngay bây giờ :)
Đối với những người tò mò ngoài kia, đây là cách một mặt hàng duy nhất hiện tại.
Bước 4
Tạo RecyclerView.Adapter
triển khai của bạn . Trong trường hợp này, đối tượng nguồn dữ liệu là một đối tượng đặc biệt được gọi RealmResults
là về cơ bản là TRỰC TIẾP ArrayList
, nói cách khác, khi các mục được thêm hoặc xóa khỏi bảng của bạn, RealmResults
đối tượng này sẽ tự động cập nhật.
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);
}
}
}
Lưu ý rằng tôi đang gọi notifyItemRemoved
với vị trí mà việc xóa xảy ra nhưng tôi KHÔNG gọi notifyItemInserted
hoặc notifyItemRangeChanged
vì không có cách nào trực tiếp để biết vị trí của mục được chèn vào cơ sở dữ liệu vì các mục Realm không được lưu theo kiểu được đặt hàng. Đối RealmResults
tượng tự động cập nhật bất cứ khi nào một mục mới được thêm, sửa đổi hoặc xóa khỏi cơ sở dữ liệu để chúng tôi gọi notifyDataSetChanged
trong khi thêm và chèn các mục hàng loạt. Tại thời điểm này, có lẽ bạn lo ngại về các hình ảnh động sẽ không được kích hoạt bởi vì bạn đang gọi notifyDataSetChanged
thay cho các notifyXXX
phương thức. Đó chính xác là lý do tại sao tôi có getItemId
phương thức trả về dấu thời gian cho mỗi hàng từ đối tượng kết quả. Hoạt ảnh đạt được trong 2 bước với notifyDataSetChanged
nếu bạn gọi setHasStableIds(true)
và sau đó ghi đègetItemId
để cung cấp một cái gì đó ngoài vị trí.
Bước 5
Cho phép thêm RecyclerView
vào Activity
hoặc của chúng tôi Fragment
. Trong trường hợp của tôi, tôi đang sử dụng một Activity
. Các tập tin bố trí có chứa RecyclerView
khá tầm thường và sẽ trông giống như thế này.
<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" />
Tôi đã thêm một app:layout_behavior
từ khi tôi RecyclerView
đi vào bên trong CoordinatorLayout
mà tôi chưa đăng trong câu trả lời này cho ngắn gọn.
Bước 6
Xây dựng RecyclerView
mã trong và cung cấp dữ liệu cần thiết. Tạo và khởi tạo một đối tượng Realm bên trong onCreate
và đóng nó bên trong onDestroy
khá giống như đóng một SQLiteOpenHelper
thể hiện. Đơn giản nhất onCreate
bên trong của bạn Activity
sẽ trông như thế này. Các initUi
phương pháp là nơi mà tất cả sự kỳ diệu xảy ra. Tôi mở một ví dụ của Realm bên trong 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);
}
Lưu ý rằng trong bước đầu tiên, tôi truy vấn Realm để cung cấp cho tôi tất cả các đối tượng từ Data
lớp được sắp xếp theo tên biến của chúng được gọi là dữ liệu theo cách không đồng bộ. Điều này mang lại cho tôi một RealmResults
đối tượng có 0 mục trên luồng chính mà tôi đang thiết lập trên Adapter
. Tôi đã thêm một RealmChangeListener
thông báo khi dữ liệu đã tải xong từ luồng nền nơi tôi gọi notifyDataSetChanged
với Adapter
. Tôi cũng đã gọi setHasStableIds
đúng để cho phép RecyclerView.Adapter
triển khai theo dõi các mục được thêm, xóa hoặc sửa đổi. Cái onDestroy
của tôi Activity
đóng ví dụ Realm
@Override
protected void onDestroy() {
super.onDestroy();
mRealm.close();
}
Phương pháp này initUi
có thể được gọi bên trong onCreate
của bạn Activity
hoặc onCreateView
hoặc onViewCreated
của bạn Fragment
. Lưu ý những điều sau đây.
Bước 7
BAM! Có dữ liệu từ cơ sở dữ liệu bên trong của bạn RecyclerView
asynchonously nạp không CursorLoader
, CursorAdapter
, SQLiteOpenHelper
với hình ảnh động. Hình ảnh GIF hiển thị ở đây hơi lag nhưng hình ảnh động đang xảy ra khi bạn thêm các mục hoặc xóa chúng.